This sketch demonstrates Internet-based remote communication using a companion ‘bridge’ program running on the attached host which sends and receives messages via an online MQTT server. This allows connecting one Arduino to another over arbitrary distances, which we can use as a starting point for building collaborative telepresence.
This example is configured to provide for up to five channels of output, each ranging from 0 to 100 inclusive. If this is used in conjunction with the MQTT Plotter (PyQt5) utility, the data will be interpreted as “X Y R G B”, where X and Y are point coordinates and R, G, and B define a color.
This example will need to be customized for your hardware. It implements a default mapping from three sensors to the outputs, but at minimum the scaling and data rates will need to be adjusted.
References:
- Arduino-MQTT Bridge (PyQt5), companion app to communicate over the network
- MQTT Plotter (PyQt5), utility app to display broadcast data
- MQTT Monitor (PyQt5), utility app for debugging MQTT programs
Full Source Code
The full code is all in one file RemoteUserInterface.ino.
// RemoteUserInterface.ino : demonstrate communication across the Internet using a // Python-based MQTT bridge and MQTT server. // This example implements a networked user interface by sending sensor data // over the host serial port. These can be forwarded to a remote MQTT server // using the arduino_mqtt_bridge.py Python application on an attached computer. // The messages are broadcast to all clients subscribed to the message stream. // The details of remote connection are managed by the bridge application; all // this Arduino sketch needs to manage is sending and receiving lines of text // over the serial port. // This example is configured to provide for up to five channels of output, each // ranging from 0 to 100 inclusive. If this is used in conjunction with the // qt_mqtt_plotter.py utility, the data will be interpreted as "X Y R G B", // where X and Y are point coordinates and R, G, and B define a color. // This example also supports receiving messages from the network. The default // implementation turns the on-board LED on or off based on an integer input, // but could be extended, e.g. to produce sounds. //================================================================ // Hardware definitions. You will need to customize this for your specific hardware. const int tiltSwitchPin = 6; // Specify a pin for a tilt switch user input. const int sonarTriggerPin = 7; // Specify a pin for a sonar trigger output. const int sonarEchoPin = 8; // Specify a pin for a sonar echo input. const int photoInput = A0; // Specify the analog channel for a photoreflector input. //================================================================ // Current state of the five output channels. Each may range from 0 to 100, // inclusive. Illegal values will be clamped to this range on send. The // specific relationship between your sensor inputs and these values will need // to be customized for your hardware. int x_value = 50; // Initial position is the center of the plot (50, 50). int y_value = 50; int r_value = 0; // Initial color is pure black (0,0,0). int g_value = 0; int b_value = 0; // Set the serial port transmission rate. The baud rate is the number of bits // per second. const long BAUD_RATE = 115200; //================================================================ // This function is called once after reset to initialize the program. void setup() { // Initialize the Serial port for host communication. Serial.begin(BAUD_RATE); // Initialize the digital input/output pins. You will need to customize this // for your specific hardware. pinMode(LED_BUILTIN, OUTPUT); pinMode(tiltSwitchPin, INPUT); pinMode(sonarTriggerPin, OUTPUT); pinMode(sonarEchoPin, INPUT); } //================================================================ // This function is called repeatedly to handle all I/O and periodic processing. // This loop should never be allowed to stall or block so that all tasks can be // constantly serviced. void loop() { serial_input_poll(); hardware_input_poll(); } //================================================================ // Polling function to process messages received over the serial port from the // remote Arduino. Each message is a line of text containing a single integer // as text. void serial_input_poll(void) { while (Serial.available()) { // When serial data is available, process and interpret the available text. // This may be customized for your particular hardware. // The default implementation assumes the line contains a single integer // which controls the built-in LED state. int value = Serial.parseInt(); // Drive the LED to indicate the value. if (value) digitalWrite(LED_BUILTIN, HIGH); else digitalWrite(LED_BUILTIN, LOW); // Once all expected values are processed, flush any remaining characters // until the line end. Note that when using the Arduino IDE Serial Monitor, // you may need to set the line ending selector to Newline. Serial.find('\n'); } } //================================================================ // Polling function to read the inputs and transmit data whenever needed. void hardware_input_poll(void) { // Calculate the interval in milliseconds since the last polling cycle. static unsigned long last_time = 0; unsigned long now = millis(); unsigned long interval = now - last_time; last_time = now; // Poll each hardware device. Each function returns true if the input has // been updated. Each function directly updates the global output state // variables as per your specific hardware. The input_changed flag will be // true if any of the polling functions return true (a logical OR using ||). bool input_changed = (poll_tilt_switch(interval) || poll_sonar(interval) || poll_photosensor(interval) ); // Update the message timer used to guarantee a minimum message rate. static long message_timer = 0; message_timer -= interval; // If either the input changed or the message timer expires, retransmit to the network. if (input_changed || (message_timer < 0)) { message_timer = 1000; // one second timeout to guarantee a minimum message rate transmit_packet(); } } //================================================================ // Poll the tilt switch at regular intervals. Filter out switch bounces by // waiting for a new value to be observed for several cycles. bool poll_tilt_switch(unsigned long interval) { static long switch_timer = 0; switch_timer -= interval; if (switch_timer < 0) { switch_timer = 10; // 100 Hz sampling rate static bool last_value = false; // last stable value static int debounce_counter = 0; // number of samples of changed value observed // Read the digital input. bool value = digitalRead(tiltSwitchPin); // If the value has changed, count samples. if (value != last_value) { debounce_counter += 1; if (debounce_counter > 5) { // Change state if a new stable value has been observed. last_value = value; debounce_counter = 0; // Update the network data. The following will need to be customized for your hardware: r_value = 100 * value; // The data is changed, so report true. return true; } } else { // If the observed value is the same as the last transmitted, keep resetting the debounce counter. debounce_counter = 0; } } return false; // No change in state. } //================================================================ // Poll the sonar at regular intervals. bool poll_sonar(unsigned long interval) { static long sonar_timer = 0; sonar_timer -= interval; if (sonar_timer < 0) { sonar_timer = 250; // 4 Hz sampling rate // Generate a short trigger pulse. digitalWrite(sonarTriggerPin, HIGH); delayMicroseconds(10); digitalWrite(sonarTriggerPin, LOW); // Measure the echo pulse length. The ~6 ms timeout is chosen for a maximum // range of 100 cm assuming sound travels at 340 meters/sec. With a round // trip of 2 meters distance, the maximum ping time is 2/340 = 0.0059 // seconds. You may wish to customize this for your particular hardware. const unsigned long TIMEOUT = 5900; unsigned long ping_time = pulseIn(sonarEchoPin, HIGH, TIMEOUT); // The default implementation only updates the data if a ping was observed, // the no-ping condition is ignored. if (ping_time > 0) { // Update the data output and indicate a change. y_value = map(ping_time, 0, TIMEOUT, 0, 100); return true; } } return false; // No change in state. } //================================================================ // Poll the photosensor at regular intervals. Filter out repeated values to // minimum the network traffic when sitting idle. bool poll_photosensor(unsigned long interval) { static long photosensor_timer = 0; photosensor_timer -= interval; if (photosensor_timer < 0) { photosensor_timer = 200; // 5 Hz sampling rate // Read the analog input. int value = analogRead(photoInput); // If the value has changed, report it. static int last_value = 0; // last stable value if (value != last_value) { last_value = value; // Update the network data. The following will need to be customized for // your hardware: x_value = map(value, 0, 1023, 0, 100); // Data has changed, so report true. return true; } } return false; // No change in state. } //================================================================ // Send the current data to the MQTT server over the serial port. The values // are clamped to the legal range using constrain(). void transmit_packet(void) { Serial.print(constrain(x_value, 0, 100)); Serial.print(" "); Serial.print(constrain(y_value, 0, 100)); Serial.print(" "); Serial.print(constrain(r_value, 0, 100)); Serial.print(" "); Serial.print(constrain(g_value, 0, 100)); Serial.print(" "); Serial.println(constrain(b_value, 0, 100)); } //================================================================
Source: Arduino Sketch Remote User Interface