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 18832protocol mqtt3
4listener 815protocol websockets6
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 WiFi2#include <WiFiClient.h>3#include <ArduinoMqttClient.h>4#include <TFT_eSPI.h>5#include <SPI.h> // Clock specific6#include <Math.h> // Math library7#include <vector> // Vector library8#include "arduino_secrets.h" // SSID and Password9#include "temp_table.h" // Temperature lookup table10
11// WiFi credentials12const char ssid[] = SECRET_SSID;13const char pass[] = SECRET_PASS;14
15// MQTT Broker info16const char broker[] = "192.168.1.14";17const int port = 1883;18const char *mqtt_topic = "arduino/message";19
20// Pins and Thresholds21#define LED_OFFLINE 722#define LED_ONLINE 623
24#define moisture_1 A225#define moisture_2 A126#define moisture_3 A027
28#define temp_1 A529
30
31#define relay_1 232#define relay_2 333#define relay_3 434
35const int dryThreshold_1 = 700; // Yucca gigantea36const int dryThreshold_2 = 600; // Heptapleurum actinophyllum37const int dryThreshold_3 = 600; // Heptapleurum actinophyllum38
39WiFiClient wifiClient;40MqttClient mqttClient(wifiClient);41TFT_eSPI tft = TFT_eSPI();42
43#include "clock.h" // clock44
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 TFT52 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 status60 pinMode(LED_OFFLINE, OUTPUT);61 pinMode(LED_ONLINE, OUTPUT);62 digitalWrite(LED_OFFLINE, LOW); // Start with offline LED on63 digitalWrite(LED_ONLINE, LOW); // Start with offline LED on64
65 // WiFi Connection66 connectToWiFi();67
68 // MQTT Connection69 connectToMQTT();70
71 // Configure relays as output and ensure pumps are off72 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 WiFi101void 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 MQTT115void 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 MQTT128void 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 data144 mqttClient.beginMessage("sensor/soil_moisture");145 mqttClient.print(String(moistureValues[0]) + "," + String(moistureValues[1]) + "," + String(moistureValues[2]));146 mqttClient.endMessage();147
148 // Publish temperature data149 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 moisture154 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 messages166void 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 messages179 if (message == "clock") {180 // Display the clock on the screen181 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 Connection189 connectToWiFi();190 // MQTT Connection191 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 screen228 clock_active = false;229 }230 if (message != "home"){231 // not home screen232 isHome = false;233 }234 }235}236
237// Display the home screen with all plant data238void showHomeScreen(bool isHome) {239 tft.fillScreen(TFT_BLACK);240 tft.setTextColor(TFT_GREEN);241 tft.setTextSize(2); // Reduced text size242 tft.setCursor(0, 0);243 tft.println("Home Plant Control");244
245 // Displaying plant info for each plant246 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 plant252void 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 temperature266 int tempAnalogValue = analogRead(tempPin);267 TempValue temp = closestNumber(temp_table, tempAnalogValue); // Find the closest temperature match268
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 Celsius275
276}277
278// Function to show the message when a pump is activated279void showWateringMessage(const char* plantName) {280 tft.fillScreen(TFT_BLACK);281 tft.setTextSize(2); // Bigger text size for the message282 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 screen287}288
289
290// Show the welcome screen291void 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 override306void controlPump(int relayPin, int moistureValue, int threshold, const char *plantName, bool manualOverride) {307 if (manualOverride) {308 digitalWrite(relayPin, HIGH); // Turn the pump on manually309 Serial.println("Manual Override: Pump ON for " + String(plantName));310 showWateringMessage(plantName); // Show watering message when pump is ON311 } else {312 bool isDry = moistureValue > threshold;313 if (isDry) {314 digitalWrite(relayPin, HIGH); // Turn on the pump315 Serial.print("Pump ON for ");316 Serial.println(plantName);317 showWateringMessage(plantName); // Show watering message when pump is ON318 delay(5000); // Add a delay to ensure that the voltage is distributed319 digitalWrite(relayPin, LOW); // Turn off the pump after the delay320 } else {321 digitalWrite(relayPin, LOW); // Turn off the pump if moisture is sufficient322 Serial.print("Pump OFF for ");323 Serial.println(plantName);324 }325 }326 // Dynamically update the pump status on the TFT327 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 information2struct 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 formula9// 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, 1923};24
25// customize the abs value26int 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 time3 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 0x5AEB12
13
14
15float sx = 0, sy = 1, mx = 1, my = 0, hx = -1, hy = 0; // Saved H, M, S x & y multipliers16float 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 coords18uint16_t x0=0, x1=0, yy0=0, yy1=0;19uint32_t targetTime = 0; // for next 1 second timeout20
21static uint8_t conv2d(const char* p); // Forward declaration needed for IDE 1.6.x22uint8_t hh=conv2d(__TIME__), mm=conv2d(__TIME__+3), ss=conv2d(__TIME__+6); // Get H, M, S from compile time23
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 automatically38
39 // Draw clock face40 tft.fillCircle(120, 120, 118, TFT_GREEN);41 tft.fillCircle(120, 120, 110, TFT_BLACK);42
43 // Draw 12 lines44 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 dots56 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 markers62 tft.drawPixel(x0, yy0, TFT_WHITE);63
64 // Draw main quadrant dots65 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 472 // 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 m73 // 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 second83 if (ss==60) {84 ss=0;85 mm++; // Advance minute86 if(mm>59) {87 mm=0;88 hh++; // Advance hour89 if (hh>23) {90 hh=0;91 }92 }93 }94
95 // Pre-compute hand degrees, x & y coords for a fast screen update96 sdeg = ss*6; // 0-59 -> 0-35497 mdeg = mm*6+sdeg*0.01666667; // 0-59 -> 0-360 - includes seconds98 hdeg = hh*30+mdeg*0.0833333; // 0-11 -> 0-360 - includes minutes and seconds99 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 minute109 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 flicker118 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}