MQTT server setup for IoT Arduino communication | Extraparse

MQTT server setup for IoT Arduino communication

January 13, 202512 min read2356 words

Learn how to setup MQTT server, Arduino and a web UI for displaying data and sending prompts to the Arduino. Based on a case of IoT plant watering system with Arduino UNO R4 WIFI.

Table of Contents

Install Mosquitto

If you haven't already installed Mosquitto, you can use Homebrew to install it:

1brew install mosquitto

Create a MQTT server configuration file and run:

1mosquitto -c /usr/local/etc/mosquitto/mosquitto.conf -v

Or edit the default .conf file located in homebrew dir and add to it:

1listener 1883
2protocol mqtt
3
4listener 81
5protocol websockets
6
7allow_anonymous true

Then start the mqtt server,

1brew services start mosquitto -v

To get broker IP - needed later

1ifconfig | grep "inet " | grep -v 127.0.0.1

Stop service:

1brew services stop mosquitto

Set server and computer sleep

Then let the MQTT worker run while computer sleeps with:

1sudo systemsetup -setcomputersleep Never

Set 30 mins sleep:

1sudo systemsetup -setcomputersleep 30

Check sleep settings:

1sudo systemsetup -getcomputersleep

Check detailed power settings:

1pmset -g

Create a frontend for the mosquitto server

Route the mosquitto server with http - remix and explore the example below and change the IP adress to the one You get with ifconfig.

Arduino setup for IoT plant watering system

Included files:

3 sensors, 3 relays and 1 temp sensor (previously was 3 for each plant), sends sensor data to the mqtt server and lets the user control the arduino with the message textarea from a web interface.

mqtt-browser.ino

1#include <WiFiS3.h> // WiFi library for Arduino Uno R4 WiFi
2#include <WiFiClient.h>
3#include <ArduinoMqttClient.h>
4#include <TFT_eSPI.h>
5#include <SPI.h> // Clock specific
6#include <Math.h> // Math library
7#include <vector> // Vector library
8#include "arduino_secrets.h" // SSID and Password
9#include "temp_table.h" // Temperature lookup table
10
11// WiFi credentials
12const char ssid[] = SECRET_SSID;
13const char pass[] = SECRET_PASS;
14
15// MQTT Broker info
16const char broker[] = "192.168.1.14";
17const int port = 1883;
18const char *mqtt_topic = "arduino/message";
19
20// Pins and Thresholds
21#define LED_OFFLINE 7
22#define LED_ONLINE 6
23
24#define moisture_1 A2
25#define moisture_2 A1
26#define moisture_3 A0
27
28#define temp_1 A5
29
30
31#define relay_1 2
32#define relay_2 3
33#define relay_3 4
34
35const int dryThreshold_1 = 700; // Yucca gigantea
36const int dryThreshold_2 = 600; // Heptapleurum actinophyllum
37const int dryThreshold_3 = 600; // Heptapleurum actinophyllum
38
39WiFiClient wifiClient;
40MqttClient mqttClient(wifiClient);
41TFT_eSPI tft = TFT_eSPI();
42
43#include "clock.h" // clock
44
45// Override flags (added for pump manual control)
46bool manualOverride_1 = false;
47bool manualOverride_2 = false;
48bool manualOverride_3 = false;
49
50void setup() {
51 // Initialize Serial and TFT
52 Serial.begin(115200);
53 tft.begin();
54 tft.setRotation(0);
55 tft.fillScreen(TFT_BLACK);
56 tft.setTextColor(TFT_GREEN);
57 tft.setTextSize(2);
58
59 // Setup LEDs for online/offline status
60 pinMode(LED_OFFLINE, OUTPUT);
61 pinMode(LED_ONLINE, OUTPUT);
62 digitalWrite(LED_OFFLINE, LOW); // Start with offline LED on
63 digitalWrite(LED_ONLINE, LOW); // Start with offline LED on
64
65 // WiFi Connection
66 connectToWiFi();
67
68 // MQTT Connection
69 connectToMQTT();
70
71 // Configure relays as output and ensure pumps are off
72 pinMode(relay_1, OUTPUT);
73 pinMode(relay_2, OUTPUT);
74 pinMode(relay_3, OUTPUT);
75 digitalWrite(relay_1, LOW);
76 digitalWrite(relay_2, LOW);
77 digitalWrite(relay_3, LOW);
78
79 showWelcomeScreen();
80}
81
82bool clock_active = false;
83bool isHome = false;
84
85void loop() {
86 mqttClient.poll();
87
88 static unsigned long lastMillis = 0;
89 if (millis() - lastMillis > 5000) {
90 sendSensorData();
91 lastMillis = millis();
92 }
93
94 handleIncomingMessages();
95 if(clock_active){
96 clock();
97 }
98}
99
100// Connect to WiFi
101void connectToWiFi() {
102 tft.setCursor(0, 0);
103 tft.println("Connecting to WiFi...");
104 while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
105 delay(1000);
106 }
107 tft.setCursor(0, 16);
108 tft.println("Connected to WiFi");
109 tft.setCursor(0, 32);
110 tft.println("IP Address:");
111 tft.println(WiFi.localIP());
112}
113
114// Connect to MQTT
115void connectToMQTT() {
116 while (!mqttClient.connect(broker, port)) {
117 Serial.println("MQTT connection failed, retrying...");
118 delay(2000);
119 }
120 Serial.println("Connected to MQTT");
121 mqttClient.subscribe(mqtt_topic);
122
123 tft.setCursor(0, 64);
124 tft.println("Connected to MQTT");
125}
126
127// Send sensor data to MQTT
128void sendSensorData() {
129 float moistureValues[] = {
130 analogRead(moisture_1),
131 analogRead(moisture_2),
132 analogRead(moisture_3)
133 };
134
135 float tempValues[] = {
136 analogRead(temp_1),
137 };
138
139 TempValue temps[] = {
140 closestNumber(temp_table, tempValues[0]),
141 };
142
143 // Publish soil moisture data
144 mqttClient.beginMessage("sensor/soil_moisture");
145 mqttClient.print(String(moistureValues[0]) + "," + String(moistureValues[1]) + "," + String(moistureValues[2]));
146 mqttClient.endMessage();
147
148 // Publish temperature data
149 mqttClient.beginMessage("sensor/temp_sensor");
150 mqttClient.print(String(temps[0].index) + "," + String(temps[0].index) + "," + String(temps[0].index));
151 mqttClient.endMessage();
152
153 // Control pumps based on moisture
154 controlPump(relay_1, moistureValues[0], dryThreshold_1, "Yucca gigantea", manualOverride_1);
155 controlPump(relay_2, moistureValues[1], dryThreshold_2, "Heptapleurum actinophyllum", manualOverride_2);
156 controlPump(relay_3, moistureValues[2], dryThreshold_3, "Heptapleurum actinophyllum2", manualOverride_3);
157
158
159 if(moistureValues[0] > dryThreshold_1 || moistureValues[1] > dryThreshold_2 || moistureValues[2] > dryThreshold_3 ){
160 showHomeScreen(true);
161 }
162}
163
164
165// Handle incoming MQTT messages
166void handleIncomingMessages() {
167 if (mqttClient.available()) {
168 String message;
169 while (mqttClient.available()) {
170 message += (char)mqttClient.read();
171 }
172
173 Serial.println("Message received: " + message);
174 tft.fillScreen(TFT_BLACK);
175 tft.setTextColor(TFT_GREEN);
176 tft.setCursor(0, 0);
177
178 // Handle specific messages
179 if (message == "clock") {
180 // Display the clock on the screen
181 clock_setup();
182 clock_active = true;
183 } else if (message == "sleep") {
184 tft.fillScreen(TFT_BLACK);
185 tft.setCursor(0, 0);
186 tft.println("sleeping...");
187 }else if (message == "network") {
188 // WiFi Connection
189 connectToWiFi();
190 // MQTT Connection
191 connectToMQTT();
192 } else if (message == "home") {
193 isHome = true;
194 showHomeScreen(isHome);
195 } else if (message == "plant 1") {
196 displayPlantInfo("Yucca gigantea", dryThreshold_1, moisture_1, temp_1, relay_1, 0, false);
197 } else if (message == "plant 2") {
198 displayPlantInfo("Heptapleurum actinophyllum", dryThreshold_2, moisture_2, temp_1, relay_2, 0, false);
199 } else if (message == "plant 3") {
200 displayPlantInfo("Heptapleurum actinophyllum2", dryThreshold_3, moisture_3, temp_1, relay_3, 0, false);
201 } else if (message == "pump 1 ON") {
202 manualOverride_1 = true;
203 controlPump(relay_1, analogRead(moisture_1), dryThreshold_1, "Yucca gigantea", manualOverride_1);
204 } else if (message == "pump 1 OFF") {
205 manualOverride_1 = false;
206 controlPump(relay_1, analogRead(moisture_1), dryThreshold_1, "Yucca gigantea", manualOverride_1);
207 showHomeScreen(true);
208 } else if (message == "pump 2 ON") {
209 manualOverride_2 = true;
210 controlPump(relay_2, analogRead(moisture_2), dryThreshold_2, "Heptapleurum actinophyllum", manualOverride_2);
211 } else if (message == "pump 2 OFF") {
212 manualOverride_2 = false;
213 controlPump(relay_2, analogRead(moisture_2), dryThreshold_2, "Heptapleurum actinophyllum", manualOverride_2);
214 showHomeScreen(true);
215 } else if (message == "pump 3 ON") {
216 manualOverride_3 = true;
217 controlPump(relay_3, analogRead(moisture_3), dryThreshold_3, "Haworthiopsis fasciata", manualOverride_3);
218 } else if (message == "pump 3 OFF") {
219 manualOverride_3 = false;
220 controlPump(relay_3, analogRead(moisture_3), dryThreshold_3, "Haworthiopsis fasciata", manualOverride_3);
221 showHomeScreen(true);
222 } else {
223 tft.println("Network message:");
224 tft.println(message);
225 }
226 if (message != "clock") {
227 // hide the clock from the screen
228 clock_active = false;
229 }
230 if (message != "home"){
231 // not home screen
232 isHome = false;
233 }
234 }
235}
236
237// Display the home screen with all plant data
238void showHomeScreen(bool isHome) {
239 tft.fillScreen(TFT_BLACK);
240 tft.setTextColor(TFT_GREEN);
241 tft.setTextSize(2); // Reduced text size
242 tft.setCursor(0, 0);
243 tft.println("Home Plant Control");
244
245 // Displaying plant info for each plant
246 displayPlantInfo("Yucca gigantea", dryThreshold_1, moisture_1, temp_1, relay_1, 16, isHome);
247 displayPlantInfo("Heptapleurum actinophyllum", dryThreshold_2, moisture_2, temp_1, relay_2, 80, isHome);
248 displayPlantInfo("Heptapleurum actinophyllum2", dryThreshold_3, moisture_3, temp_1, relay_3, 160, isHome);
249}
250
251// Display plant information on the TFT screen with position for each plant
252void displayPlantInfo(const char* plantName, int threshold, int moisturePin, int tempPin, int relayPin, int yPos, bool isHome) {
253 tft.setCursor(0, yPos);
254 tft.println(plantName);
255
256 if (!isHome) {
257 tft.print("Threshold: ");
258 tft.println(threshold);
259 }
260
261 int moistureValue = analogRead(moisturePin);
262 tft.print("Moisture: ");
263 tft.println(moistureValue);
264
265 // Read the temperature analog value and use closestNumber to convert to temperature
266 int tempAnalogValue = analogRead(tempPin);
267 TempValue temp = closestNumber(temp_table, tempAnalogValue); // Find the closest temperature match
268
269 // Convert the index to the actual temperature in Celsius (since temp_table stores resistance values)
270 float temperature = temp.index;
271
272
273 tft.print("Temp: ");
274 tft.println(temperature); // Display temperature in Celsius
275
276}
277
278// Function to show the message when a pump is activated
279void showWateringMessage(const char* plantName) {
280 tft.fillScreen(TFT_BLACK);
281 tft.setTextSize(2); // Bigger text size for the message
282 tft.setTextColor(TFT_CYAN);
283 tft.setCursor(0, 32);
284 tft.println("Watering...");
285 tft.println(String(plantName));
286 delay(5000); // Wait for 2 seconds before going back to home screen
287}
288
289
290// Show the welcome screen
291void showWelcomeScreen() {
292 tft.fillScreen(TFT_BLACK);
293 tft.setTextSize(2);
294 tft.setTextColor(TFT_GREEN);
295 tft.setCursor(0, 0);
296 tft.println("Welcome to");
297 tft.println("Home Plant Control!");
298 tft.setCursor(0, 64);
299 tft.setTextColor(TFT_CYAN);
300 tft.println("Connected, waiting for commands...");
301 delay(5000);
302}
303
304
305// Control a pump based on moisture levels or manual override
306void controlPump(int relayPin, int moistureValue, int threshold, const char *plantName, bool manualOverride) {
307 if (manualOverride) {
308 digitalWrite(relayPin, HIGH); // Turn the pump on manually
309 Serial.println("Manual Override: Pump ON for " + String(plantName));
310 showWateringMessage(plantName); // Show watering message when pump is ON
311 } else {
312 bool isDry = moistureValue > threshold;
313 if (isDry) {
314 digitalWrite(relayPin, HIGH); // Turn on the pump
315 Serial.print("Pump ON for ");
316 Serial.println(plantName);
317 showWateringMessage(plantName); // Show watering message when pump is ON
318 delay(5000); // Add a delay to ensure that the voltage is distributed
319 digitalWrite(relayPin, LOW); // Turn off the pump after the delay
320 } else {
321 digitalWrite(relayPin, LOW); // Turn off the pump if moisture is sufficient
322 Serial.print("Pump OFF for ");
323 Serial.println(plantName);
324 }
325 }
326 // Dynamically update the pump status on the TFT
327 mqttClient.beginMessage("sensor/relay");
328 mqttClient.print(String("Pump ") + (manualOverride ? "ON (Manual)" : (moistureValue > threshold ? "ON" : "OFF")) + " for " + plantName);
329 mqttClient.endMessage();
330}

arduino_secrets.h

1#define SECRET_SSID "Your_WIFI_NAME"
2#define SECRET_PASS "Your_WIFI_PASSWORD"

temp_table.h

1// setting a structure to store tempvalue and index information
2struct TempValue {
3 int value;
4 size_t index;
5};
6
7// here stored the temp_table of the temperatue -> resistor table and it has been convert to ADC reading range from 0-1023.
8// check the table of NTC 10K resister table and convert it to adc reading range by following formula
9// resister number / (resister number + 10000.0)
10// for example: when temperatrue is 25 degree, the NTC's resistor number is 10000.0 ohm, so the adc number will be:
11// 10000.0 / (10000.0 + 10000.0) = 512, so, if you reading from adc and get 512 +/- 10 equals 25 degree.
12// dut to the NTC dose not a linear component, so you need to check the table to grab the temperature value.
13std::vector<int> temp_table = {
14 994, 992, 990, 988, 986, 983, 981, 978, 975, 972, 969, 966, 962, 959, 955, 951, 947, 943, 938, 933,
15 928, 923, 918, 912, 907, 901, 894, 888, 881, 874, 867, 860, 852, 845, 837, 828, 820, 811, 802, 793,
16 784, 774, 765, 755, 745, 735, 724, 714, 703, 692, 682, 671, 659, 648, 637, 626, 614, 603, 592, 580,
17 569, 557, 546, 535, 523, 512, 501, 490, 479, 468, 457, 446, 436, 425, 415, 405, 395, 385, 375, 365,
18 356, 347, 338, 329, 320, 311, 303, 295, 287, 279, 271, 264, 256, 249, 242, 235, 229, 222, 216, 210,
19 204, 198, 193, 187, 182, 176, 172, 167, 162, 157, 153, 148, 144, 140, 136, 132, 128, 125, 121, 118,
20 114, 111, 108, 105, 102, 99, 96, 94, 91, 88, 86, 84, 81, 79, 77, 75, 73, 71, 69, 67, 65, 63, 62, 60,
21 58, 57, 55, 54, 52, 51, 50, 48, 47, 46, 45, 44, 43, 41, 40, 39, 38, 37, 36, 36, 35, 34, 33, 32, 31, 31,
22 30, 29, 28, 28, 27, 26, 26, 25, 25, 24, 23, 23, 22, 22, 21, 21, 20, 20, 20, 19, 19
23};
24
25// customize the abs value
26int custom_abs(int x) {
27 return (x >= 0) ? x : -x;
28}
29
30// function to check the temp_table by sending adc reading value.
31TempValue closestNumber(const std::vector<int>& temp_table, int value) {
32 TempValue result;
33 result.value = temp_table[0];
34 result.index = 0;
35
36 int min_diff = custom_abs(temp_table[0] - value);
37
38 for (size_t i = 1; i < temp_table.size(); ++i) {
39 int diff = custom_abs(temp_table[i] - value);
40 if (diff < min_diff) {
41 min_diff = diff;
42 result.value = temp_table[i];
43 result.index = i;
44 }
45 }
46 result.index -= 40;
47 return result;
48}

clock.h

1/*
2 An example analogue clock using a TFT LCD screen to show the time
3 use of some of the drawing commands with the library.
4
5 For a more accurate clock, it would be better to use the RTClib library.
6 But this is just a demo.
7
8 This sketch uses font 4 only.
9 */
10
11#define TFT_GREY 0x5AEB
12
13
14
15float sx = 0, sy = 1, mx = 1, my = 0, hx = -1, hy = 0; // Saved H, M, S x & y multipliers
16float sdeg=0, mdeg=0, hdeg=0;
17uint16_t osx=120, osy=120, omx=120, omy=120, ohx=120, ohy=120; // Saved H, M, S x & y coords
18uint16_t x0=0, x1=0, yy0=0, yy1=0;
19uint32_t targetTime = 0; // for next 1 second timeout
20
21static uint8_t conv2d(const char* p); // Forward declaration needed for IDE 1.6.x
22uint8_t hh=conv2d(__TIME__), mm=conv2d(__TIME__+3), ss=conv2d(__TIME__+6); // Get H, M, S from compile time
23
24bool initial = 1;
25
26void clock_setup(void) {
27
28 tft.setRotation(0);
29
30 //tft.fillScreen(TFT_BLACK);
31 //tft.fillScreen(TFT_RED);
32 tft.fillScreen(TFT_GREEN);
33 //tft.fillScreen(TFT_BLUE);
34 //tft.fillScreen(TFT_BLACK);
35 //tft.fillScreen(TFT_GREY);
36
37 tft.setTextColor(TFT_WHITE, TFT_GREY); // Adding a background colour erases previous text automatically
38
39 // Draw clock face
40 tft.fillCircle(120, 120, 118, TFT_GREEN);
41 tft.fillCircle(120, 120, 110, TFT_BLACK);
42
43 // Draw 12 lines
44 for(int i = 0; i<360; i+= 30) {
45 sx = cos((i-90)*0.0174532925);
46 sy = sin((i-90)*0.0174532925);
47 x0 = sx*114+120;
48 yy0 = sy*114+120;
49 x1 = sx*100+120;
50 yy1 = sy*100+120;
51
52 tft.drawLine(x0, yy0, x1, yy1, TFT_GREEN);
53 }
54
55 // Draw 60 dots
56 for(int i = 0; i<360; i+= 6) {
57 sx = cos((i-90)*0.0174532925);
58 sy = sin((i-90)*0.0174532925);
59 x0 = sx*102+120;
60 yy0 = sy*102+120;
61 // Draw minute markers
62 tft.drawPixel(x0, yy0, TFT_WHITE);
63
64 // Draw main quadrant dots
65 if(i==0 || i==180) tft.fillCircle(x0, yy0, 2, TFT_WHITE);
66 if(i==90 || i==270) tft.fillCircle(x0, yy0, 2, TFT_WHITE);
67 }
68
69 tft.fillCircle(120, 121, 3, TFT_WHITE);
70
71 // Draw text at position 120,260 using fonts 4
72 // Only font numbers 2,4,6,7 are valid. Font 6 only contains characters [space] 0 1 2 3 4 5 6 7 8 9 : . - a p m
73 // Font 7 is a 7 segment font and only contains characters [space] 0 1 2 3 4 5 6 7 8 9 : .
74 tft.drawCentreString("Time flies",120,260,4);
75
76 targetTime = millis() + 1000;
77}
78
79void clock() {
80 if (targetTime < millis()) {
81 targetTime += 1000;
82 ss++; // Advance second
83 if (ss==60) {
84 ss=0;
85 mm++; // Advance minute
86 if(mm>59) {
87 mm=0;
88 hh++; // Advance hour
89 if (hh>23) {
90 hh=0;
91 }
92 }
93 }
94
95 // Pre-compute hand degrees, x & y coords for a fast screen update
96 sdeg = ss*6; // 0-59 -> 0-354
97 mdeg = mm*6+sdeg*0.01666667; // 0-59 -> 0-360 - includes seconds
98 hdeg = hh*30+mdeg*0.0833333; // 0-11 -> 0-360 - includes minutes and seconds
99 hx = cos((hdeg-90)*0.0174532925);
100 hy = sin((hdeg-90)*0.0174532925);
101 mx = cos((mdeg-90)*0.0174532925);
102 my = sin((mdeg-90)*0.0174532925);
103 sx = cos((sdeg-90)*0.0174532925);
104 sy = sin((sdeg-90)*0.0174532925);
105
106 if (ss==0 || initial) {
107 initial = 0;
108 // Erase hour and minute hand positions every minute
109 tft.drawLine(ohx, ohy, 120, 121, TFT_BLACK);
110 ohx = hx*62+121;
111 ohy = hy*62+121;
112 tft.drawLine(omx, omy, 120, 121, TFT_BLACK);
113 omx = mx*84+120;
114 omy = my*84+121;
115 }
116
117 // Redraw new hand positions, hour and minute hands not erased here to avoid flicker
118 tft.drawLine(osx, osy, 120, 121, TFT_BLACK);
119 osx = sx*90+121;
120 osy = sy*90+121;
121 tft.drawLine(osx, osy, 120, 121, TFT_RED);
122 tft.drawLine(ohx, ohy, 120, 121, TFT_WHITE);
123 tft.drawLine(omx, omy, 120, 121, TFT_WHITE);
124 tft.drawLine(osx, osy, 120, 121, TFT_RED);
125
126 tft.fillCircle(120, 121, 3, TFT_RED);
127 }
128}
129
130static uint8_t conv2d(const char* p) {
131 uint8_t v = 0;
132 if ('0' <= *p && *p <= '9')
133 v = *p - '0';
134 return 10 * v + *++p - '0';
135}