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:
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.
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//================================================================