RemoteUserInterface Arduino Sketch

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:

Full Source Code

The full code is all in one file RemoteUserInterface.ino.

  1// RemoteUserInterface.ino : demonstrate communication across the Internet using a
  2// Python-based MQTT bridge and MQTT server.
  3
  4// This example implements a networked user interface by sending sensor data
  5// over the host serial port.  These can be forwarded to a remote MQTT server
  6// using the arduino_mqtt_bridge.py Python application on an attached computer.
  7// The messages are broadcast to all clients subscribed to the message stream.
  8// The details of remote connection are managed by the bridge application; all
  9// this Arduino sketch needs to manage is sending and receiving lines of text
 10// over the serial port.
 11
 12// This example is configured to provide for up to five channels of output, each
 13// ranging from 0 to 100 inclusive.  If this is used in conjunction with the
 14// qt_mqtt_plotter.py utility, the data will be interpreted as "X Y R G B",
 15// where X and Y are point coordinates and R, G, and B define a color.
 16
 17// This example also supports receiving messages from the network.  The default
 18// implementation turns the on-board LED on or off based on an integer input,
 19// but could be extended, e.g. to produce sounds.
 20
 21//================================================================
 22// Hardware definitions. You will need to customize this for your specific hardware.
 23const int tiltSwitchPin   = 6;    // Specify a pin for a tilt switch user input.
 24const int sonarTriggerPin = 7;    // Specify a pin for a sonar trigger output.
 25const int sonarEchoPin    = 8;    // Specify a pin for a sonar echo input.
 26const int photoInput      = A0;   // Specify the analog channel for a photoreflector input.
 27
 28//================================================================
 29// Current state of the five output channels.  Each may range from 0 to 100,
 30// inclusive.  Illegal values will be clamped to this range on send.  The
 31// specific relationship between your sensor inputs and these values will need
 32// to be customized for your hardware.
 33
 34int x_value = 50;  // Initial position is the center of the plot (50, 50).
 35int y_value = 50;
 36int r_value = 0;   // Initial color is pure black (0,0,0).
 37int g_value = 0;
 38int b_value = 0;
 39
 40// Set the serial port transmission rate. The baud rate is the number of bits
 41// per second.
 42const long BAUD_RATE = 115200;    
 43
 44//================================================================
 45// This function is called once after reset to initialize the program.
 46void setup()
 47{
 48  // Initialize the Serial port for host communication.
 49  Serial.begin(BAUD_RATE);
 50
 51  // Initialize the digital input/output pins.  You will need to customize this
 52  // for your specific hardware.
 53  pinMode(LED_BUILTIN, OUTPUT);
 54  pinMode(tiltSwitchPin, INPUT);
 55  pinMode(sonarTriggerPin, OUTPUT);
 56  pinMode(sonarEchoPin, INPUT);
 57}
 58
 59//================================================================
 60// This function is called repeatedly to handle all I/O and periodic processing.
 61// This loop should never be allowed to stall or block so that all tasks can be
 62// constantly serviced.
 63void loop()
 64{
 65  serial_input_poll();
 66  hardware_input_poll();
 67}
 68
 69//================================================================
 70// Polling function to process messages received over the serial port from the
 71// remote Arduino.  Each message is a line of text containing a single integer
 72// as text.
 73
 74void serial_input_poll(void)
 75{
 76  while (Serial.available()) {
 77    // When serial data is available, process and interpret the available text.
 78    // This may be customized for your particular hardware.
 79
 80    // The default implementation assumes the line contains a single integer
 81    // which controls the built-in LED state.
 82    int value = Serial.parseInt();
 83
 84    // Drive the LED to indicate the value.
 85    if (value)     digitalWrite(LED_BUILTIN, HIGH);
 86    else           digitalWrite(LED_BUILTIN, LOW);
 87
 88    // Once all expected values are processed, flush any remaining characters
 89    // until the line end.  Note that when using the Arduino IDE Serial Monitor,
 90    // you may need to set the line ending selector to Newline.
 91    Serial.find('\n');
 92  }
 93}
 94
 95//================================================================
 96// Polling function to read the inputs and transmit data whenever needed.
 97
 98void hardware_input_poll(void)
 99{
100  // Calculate the interval in milliseconds since the last polling cycle.
101  static unsigned long last_time = 0;
102  unsigned long now = millis();
103  unsigned long interval = now - last_time;
104  last_time = now;
105
106  // Poll each hardware device.  Each function returns true if the input has
107  // been updated.  Each function directly updates the global output state
108  // variables as per your specific hardware.  The input_changed flag will be
109  // true if any of the polling functions return true (a logical OR using ||).
110  bool input_changed = (poll_tilt_switch(interval)
111                        || poll_sonar(interval)
112                        || poll_photosensor(interval)
113                       );
114
115  // Update the message timer used to guarantee a minimum message rate.
116  static long message_timer = 0;
117  message_timer -= interval;
118
119  // If either the input changed or the message timer expires, retransmit to the network.
120  if (input_changed || (message_timer < 0)) {
121    message_timer = 1000;  // one second timeout to guarantee a minimum message rate
122    transmit_packet();
123  }
124}
125//================================================================
126// Poll the tilt switch at regular intervals.  Filter out switch bounces by
127// waiting for a new value to be observed for several cycles.
128
129bool poll_tilt_switch(unsigned long interval)
130{
131  static long switch_timer = 0;
132  switch_timer -= interval;
133  if (switch_timer < 0) {
134    switch_timer = 10; // 100 Hz sampling rate
135
136    static bool last_value = false;   // last stable value
137    static int debounce_counter = 0;  // number of samples of changed value observed
138
139    // Read the digital input.
140    bool value = digitalRead(tiltSwitchPin);
141
142    // If the value has changed, count samples.
143    if (value != last_value) {
144      debounce_counter += 1;
145      if (debounce_counter > 5) {
146        // Change state if a new stable value has been observed.
147        last_value = value;
148        debounce_counter = 0;
149
150        // Update the network data.  The following will need to be customized for your hardware:
151        r_value = 100 * value;
152
153        // The data is changed, so report true.
154        return true;
155      }
156    } else {
157      // If the observed value is the same as the last transmitted, keep resetting the debounce counter.
158      debounce_counter = 0;
159    }
160  }
161  return false; // No change in state.
162}
163
164//================================================================
165// Poll the sonar at regular intervals.
166bool poll_sonar(unsigned long interval)
167{
168  static long sonar_timer = 0;
169  sonar_timer -= interval;
170  if (sonar_timer < 0) {
171    sonar_timer = 250; // 4 Hz sampling rate
172
173    // Generate a short trigger pulse.
174    digitalWrite(sonarTriggerPin, HIGH);
175    delayMicroseconds(10);
176    digitalWrite(sonarTriggerPin, LOW);
177
178    // Measure the echo pulse length.  The ~6 ms timeout is chosen for a maximum
179    // range of 100 cm assuming sound travels at 340 meters/sec.  With a round
180    // trip of 2 meters distance, the maximum ping time is 2/340 = 0.0059
181    // seconds.  You may wish to customize this for your particular hardware.
182    const unsigned long TIMEOUT = 5900;
183    unsigned long ping_time = pulseIn(sonarEchoPin, HIGH, TIMEOUT);
184
185    // The default implementation only updates the data if a ping was observed,
186    // the no-ping condition is ignored.
187    if (ping_time > 0) {
188      // Update the data output and indicate a change.
189      y_value = map(ping_time, 0, TIMEOUT, 0, 100);
190      return true;
191    }
192  }
193  return false; // No change in state.
194}
195
196//================================================================
197// Poll the photosensor at regular intervals.  Filter out repeated values to
198// minimum the network traffic when sitting idle.
199bool poll_photosensor(unsigned long interval)
200{
201  static long photosensor_timer = 0;
202  photosensor_timer -= interval;
203  if (photosensor_timer < 0) {
204    photosensor_timer = 200; // 5 Hz sampling rate
205
206    // Read the analog input.
207    int value = analogRead(photoInput);
208
209    // If the value has changed, report it.
210    static int last_value = 0;    // last stable value
211
212    if (value != last_value) {
213      last_value = value;
214
215      // Update the network data.  The following will need to be customized for
216      // your hardware:
217      x_value = map(value, 0, 1023, 0, 100);
218
219      // Data has changed, so report true.
220      return true;
221    }
222  }
223  return false; // No change in state.
224}
225
226//================================================================
227// Send the current data to the MQTT server over the serial port.  The values
228// are clamped to the legal range using constrain().
229
230void transmit_packet(void)
231{
232  Serial.print(constrain(x_value, 0, 100));
233  Serial.print(" ");
234  Serial.print(constrain(y_value, 0, 100));
235  Serial.print(" ");
236  Serial.print(constrain(r_value, 0, 100));
237  Serial.print(" ");
238  Serial.print(constrain(g_value, 0, 100));
239  Serial.print(" ");
240  Serial.println(constrain(b_value, 0, 100));
241}
242//================================================================