Gbox 4.20
Grow box automation and monitoring - <a href='https://sites.google.com/site/growboxguy/'>https://sites.google.com/site/growboxguy/</a>
 
Loading...
Searching...
No Matches
Gbox420_Mega_Main.ino
Go to the documentation of this file.
1
9#include "Arduino.h"
10#include <avr/wdt.h> // Watchdog timer for detecting a crash and automatically resetting the board
11#include <avr/boot.h> // Watchdog timer related bug fix
12#include <printf.h> // Printing the wireless status message from nRF24L01
13#include <TimerThree.h> // Interrupt handling for the web server
14#include "ELClient.h" // ESP-link
15#include "ELClientWebServer.h" // ESP-link - WebServer API
16#include "ELClientCmd.h" // ESP-link - Get current time from the internet using NTP
17#include "ELClientRest.h" // ESP-link - REST API
18#include "ELClientMqtt.h" // ESP-link MQTT protocol for IoT messaging
19#include <Thread.h> // Logic threading for function timing
20#include <StaticThreadController.h> // Grouping multiple threads
21#include "SerialLog.h" // Logging to Serial and ESP-link console
22#include "Settings.h" // EEPROM settings management
23#include "src/Modules_Web/MainModule_Web.h" // Main box features and web module
24#include <SPI.h> // Hardware SPI communication
25#include <RF24.h> // nRF24L01 wireless radio driver - https://github.com/maniacbug/RF24
26
27// Global variable initialization
31
32// Component initialization
33HardwareSerial &ArduinoSerial = Serial;
34HardwareSerial &ESPSerial = Serial3;
37ELClientCmd ESPCmd(&ESPLink);
38ELClientRest PushingBoxRestAPI(&ESPLink);
39ELClientRest HomeAssistantRestAPI(&ESPLink);
40ELClientMqtt MqttAPI(&ESPLink);
42bool &Debug = *new bool;
43bool &Metric = *new bool;
44bool MqttConnected = false;
45MainModule *Main1;
46
47RF24 Wireless(WirelessCEPin, WirelessCSNPin);
48
49// Thread initialization
50Thread OneSecThread = Thread();
51Thread FiveSecThread = Thread();
52Thread MinuteThread = Thread();
53StaticThreadController<3> ThreadControl(&OneSecThread, &FiveSecThread, &MinuteThread);
54
55void setup()
56{
57 ArduinoSerial.begin(115200);
58 ESPSerial.begin(115200);
59 pinMode(LED_BUILTIN, OUTPUT);
60 printf_begin();
61 logToSerials(F(""), true, 0);
62 logToSerials(F("Main module initializing"), true, 0);
63 wdt_enable(WDTO_8S);
64 boot_rww_enable();
65
66 // Loading settings from EEPROM
67 logToSerials(F("Loading settings"), true, 0);
71
72 logToSerials(F("Setting up ESP-link connection"), true, 0);
73 ESPLink.resetCb = &resetWebServer;
75 setSyncProvider(getNtpTime);
76 setSyncInterval(86400);
77 if ((ModuleSettings->Main1).ReportToMqtt)
78 {
79 setupMqtt(); // MQTT message relay setup. Logs "ConnectedCB is XXXX" to serial if successful
80 }
81 // Threads - Setting up how often threads should be triggered and what functions to call when the trigger fires
82 logToSerials(F("Setting up refresh threads"), false, 0);
83 OneSecThread.setInterval(1000);
84 OneSecThread.onRun(run1sec);
85 FiveSecThread.setInterval(5000);
87 MinuteThread.setInterval(60000);
88 MinuteThread.onRun(run1min);
89 logToSerials(F("done"), true, 3);
90
91 // Start interrupts to handle request from ESP-link firmware
92 logToSerials(F("Setting up interrupt handler"), false, 0);
93 Timer3.initialize(500);
94 Timer3.attachInterrupt(processTimeCriticalStuff);
95 Timer3.start();
96 logToSerials(F("done"), true, 3);
97
98 // Initialize wireless communication with remote Modules
99 logToSerials(F("Setting up wireless transceiver"), false, 0);
100 Wireless.begin();
101 Wireless.setDataRate(RF24_250KBPS);
102 Wireless.setCRCLength(RF24_CRC_16);
103 Wireless.setPALevel(RF24_PA_MAX);
104 Wireless.setPayloadSize(WirelessPayloadSize);
105 Wireless.enableDynamicPayloads();
106 Wireless.enableAckPayload();
107 Wireless.setRetries(WirelessDelay, WirelessRetry);
108 Wireless.stopListening();
109 logToSerials(F("done"), true, 3);
110
111 // Create the Module objects
112 logToSerials(F("Creating Main module"), true, 0);
113 Main1 = new MainModule(F("Main1"), &ModuleSettings->Main1, &Wireless);
114
115 // sendEmailAlert(F("Grow%20box%20(re)started"));
116 logToSerials(F("Setup ready, starting loops:"), true, 0);
117}
118
119void loop()
120{
121 ThreadControl.run();
122}
123
125{
126 ESPLink.Process();
127}
128
129// Threads
130
132{
133 wdt_reset();
134 heartBeat();
135 Main1->run1sec();
136}
137
139{
140 wdt_reset();
141 Main1->run5sec();
142}
143
145{
146 wdt_reset();
147 Main1->run1min();
149}
150
155{
156 static bool ledStatus;
157 ledStatus = !ledStatus;
158 digitalWrite(LED_BUILTIN, ledStatus);
159}
160
161// Website related functions
162
167{
168 logToSerials(F("(re)Connecting ESP-link"), false, 1);
169 while (!ESPLink.Sync())
170 {
171 logToSerials(F("."), false, 0);
172 delay(1000);
173 };
174 logToSerials(F(""), true, 0);
175 if (PushingBoxRestAPI.begin("api.pushingbox.com") == 0)
176 {
177 logToSerials(F("PushingBox RestAPI ready"), true, 2);
178 }
179 else
180 logToSerials(F("PushingBox RestAPI failed"), true, 2);
181 /*
182 if (HomeAssistantRestAPI.begin(HomeAssistantServerIP, HomeAssistantServerPort, true) == 0) ///< Pre-setup RestAPI with Home Assistant (Not actual connection)
183 {
184 HomeAssistantRestAPI.setContentType("application/json");
185 HomeAssistantRestAPI.setHeader(HomeAssistantServerToken);
186
187 logToSerials(F("HomeAssistant RestAPI ready"), true, 2);
188 }
189 else
190 logToSerials(F("HomeAssistant RestAPI failed"), true, 2); ///< If begin returns a negative number the initialization failed
191 */
192 WebServer.setup();
193 URLHandler *GrowBoxHandler = WebServer.createURLHandler("/Main.html.json");
194 GrowBoxHandler->loadCb.attach(&loadCallback);
195 GrowBoxHandler->refreshCb.attach(&refreshCallback);
196 GrowBoxHandler->buttonCb.attach(&buttonCallback);
197 GrowBoxHandler->setFieldCb.attach(&fieldCallback);
198 URLHandler *SettingsHandler = WebServer.createURLHandler("/Settings.html.json");
199 SettingsHandler->loadCb.attach(&settingsLoadCallback);
200 SettingsHandler->refreshCb.attach(&settingsRefreshCallback);
201 SettingsHandler->buttonCb.attach(&settingsButtonCallback);
202 SettingsHandler->setFieldCb.attach(&settingsFieldCallback);
203 URLHandler *HempyHandler = WebServer.createURLHandler("/Hempy.html.json");
204 HempyHandler->loadCb.attach(&ignoreCallback);
205 HempyHandler->refreshCb.attach(&ignoreCallback);
206
207 logToSerials(F("ESP-link ready"), true, 1);
208}
209
214{
215
216 MqttAPI.connectedCb.attach(mqttConnected);
217 MqttAPI.disconnectedCb.attach(mqttDisconnected);
218 MqttAPI.publishedCb.attach(mqttPublished);
219 MqttAPI.dataCb.attach(mqttReceived);
220
221 memset(&ShortMessage[0], 0, MaxShotTextLength); // reset variable to store the Publish to path
222 strcat(ShortMessage, MqttLwtTopic);
223 MqttAPI.lwt(ShortMessage, MqttLwtMessage, 0, 1); //(topic,message,qos,retain) declares what message should be sent on it's behalf by the broker after Gbox420 has gone offline.
224 MqttAPI.setup();
225}
226
227void mqttConnected(__attribute__((unused)) void *response)
228{
229 MqttAPI.subscribe(MqttSubTopic);
230 MqttConnected = true;
231 // if(Debug) logToSerials(F("MQTT connected"), true);
232}
233
234void mqttDisconnected(__attribute__((unused)) void *response)
235{
236 // if(Debug) logToSerials(F("MQTT disconnected"), true);
237 MqttConnected = false;
238}
239
240void mqttPublished(__attribute__((unused)) void *response)
241{
242 // if(Debug) logToSerials(F("MQTT published"), true);
243}
244
245void mqttReceived(void *response)
246{
247 static uint8_t MqttSubTopicLength = strlen(MqttSubTopic) - 1; // Get length of the command topic
248 static char command[MaxShotTextLength];
249 static char data[MaxShotTextLength];
250 ELClientResponse *res = (ELClientResponse *)response;
251 String mqttTopic = (*res).popString();
252 String mqttData = (*res).popString();
253 logToSerials(F("MQTT"), false, 0);
254 logToSerials(&mqttTopic, false, 1);
255 mqttTopic.remove(0, MqttSubTopicLength); // Cut the known command topic from the arrived topic
256 mqttTopic.toCharArray(command, MaxShotTextLength);
257 mqttData.toCharArray(data, MaxShotTextLength);
258 Main1->commandEventTrigger(command, data);
259 Main1->reportToMqttTrigger(true); // send out a fresh report
260}
261
262static bool SyncInProgress = false;
263
268{
269 time_t NTPResponse = 0;
270 if (!SyncInProgress)
271 { // block calling the sync again inside an interrupt while already waiting for a sync to finish
272 SyncInProgress = true;
273 uint32_t LastRefresh = millis();
274 logToSerials(F("Waiting for NTP time"), false, 0);
275 while (NTPResponse == 0 && millis() - LastRefresh < 15000)
276 {
277 NTPResponse = ESPCmd.GetTime();
278 if (NTPResponse == 0) {
279 delay(1000);
280 logToSerials(F("."), false, 0);
281 wdt_reset();
282 }
283 }
284 SyncInProgress = false;
285 if (NTPResponse == 0)
286 {
287 logToSerials(F("sync failed"), true, 3); // FORCE the library to try again in 60 seconds instead of 1 day
288 setSyncInterval(60);
289 }
290 else
291 {
292 logToSerials(F("synchronized"), true, 3); // SUCCESS: Set the interval back to 1 day
293 setSyncInterval(86400);
294 }
295 }
296 return NTPResponse;
297}
298
303void loadCallback(__attribute__((unused)) char *Url)
304{
305 Main1->websiteLoadEventTrigger(Url); // Runs through all components that are subscribed to this event
306}
307
312void refreshCallback(__attribute__((unused)) char *Url)
313{
314 Main1->websiteRefreshEventTrigger(Url);
315}
316
321void buttonCallback(char *Button)
322{
323 logToSerials(F("ESP button:"), false, 0);
324 if (strcmp_P(Button, (PGM_P)F("RestoreDef")) == 0)
325 {
327 }
328 else
329 {
330 Main1->commandEventTrigger(Button, NULL);
331 }
333}
334
339void fieldCallback(char *Field)
340{
341 logToSerials(F("ESP field:"), false, 0);
342 Main1->commandEventTrigger(Field, WebServer.getArgString());
344}
345
349void settingsLoadCallback(__attribute__((unused)) char *Url)
350{
351 Main1->settingsEvent_Load(Url); // Runs through all components that are subscribed to this event
352}
353
357void settingsRefreshCallback(__attribute__((unused)) char *Url)
358{
359 Main1->settingsEvent_Refresh(Url);
360}
361
366void settingsButtonCallback(char *Button)
367{
368 if (Debug)
369 {
370 logToSerials(F("Settings button:"), false, 0);
371 logToSerials(&Button, true, 1);
372 }
373 if (strcmp_P(Button, (PGM_P)F("RestoreDef")) == 0)
374 {
376 }
377 else
378 {
379 Main1->settingsEvent_Command(Button, NULL);
380 }
382}
383
388void settingsFieldCallback(char *Field)
389{
390 if (Debug)
391 {
392 logToSerials(F("Settings field:"), false, 0);
393 logToSerials(&Field, true, 1);
394 }
395 Main1->settingsEvent_Command(Field, WebServer.getArgString());
397}
398
403{
404 if (Debug)
405 {
406 logToSerials(F("Wireless report:"), true, 0);
407 Wireless.printPrettyDetails();
408 logToSerials(F(""), true, 0);
409 }
410}
411
415void ignoreCallback(__attribute__((unused)) char *Url)
416{
417}
Definitions for ELClientCmd.
Definitions for ELClientMqtt.
Definitions for ELClientRes.
Definitions for ELClientWebServer.
Definitions for ELClient.
void logToSerials(const __FlashStringHelper *ToPrint, bool BreakLine, uint8_t Indent)
< Logging
Definition SerialLog.cpp:5
void restoreDefaults()
Load sketch default settings into EEPROM.
Definition Settings.cpp:45
void saveSettings(Settings *ToSave)
Store settings in EEPROM - Only updates changed bits.
Definition Settings.cpp:10
Settings * loadSettings(bool ResetEEPROM)
Load settings from EEPROM.
Definition Settings.cpp:20
#define MqttSubTopic
Subscribe to messages of this topic and all sub-topic.
Definition Settings.h:43
#define MqttLwtMessage
Subscribers will get this message under the topic specified by MqttLwtTopic when the MQTT client goes...
Definition Settings.h:45
#define MqttLwtTopic
When the connection is lost the MQTT broker will publish a final message to this topic....
Definition Settings.h:44
void settingsButtonCallback(char *Button)
Called when a button is pressed on the /Settings.html web page.
void run5sec()
RF24 Wireless(WirelessCEPin, WirelessCSNPin)
Wireless communication with Modules over nRF24L01+.
void resetWebServer()
(re-)Initialize the ESP-link connection
void settingsRefreshCallback(__attribute__((unused)) char *Url)
Called when the /Settings.html website is refreshing on the ESP-link webserver.
void fieldCallback(char *Field)
Called when a field on the website is submitted.
void mqttDisconnected(__attribute__((unused)) void *response)
char CurrentTime[MaxWordLength]
Buffer for storing current time in text format.
char LongMessage[MaxLongTextLength]
Temp storage for assembling long messages (REST API, MQTT reporting)
Thread MinuteThread
Thread OneSecThread
void mqttConnected(__attribute__((unused)) void *response)
void processTimeCriticalStuff()
time_t getNtpTime()
Update the time over ESP-link using NTP (Network Time Protocol)
HardwareSerial & ESPSerial
Reference to the ESP Link Serial output : Mega 2560 Rev3 Serial:0(RX) 1(TX), Serial1:19(RX1) 18(TX1),...
void buttonCallback(char *Button)
Called when a button is pressed.
void setup()
Thread FiveSecThread
void mqttReceived(void *response)
void mqttPublished(__attribute__((unused)) void *response)
char ShortMessage[MaxShotTextLength]
Temp storage for assembling short text messages (Log entries, Error messages,etc)
bool MqttConnected
Track the connection state to the MQTT broker configured on the ESP-link's REST/MQTT tab.
void run1min()
void settingsLoadCallback(__attribute__((unused)) char *Url)
Called when the /Settings.html website is loading on the ESP-link webserver.
MainModule * Main1
Represents a Grow Box with all components (Lights, DHT sensors, Power sensor, Aero/Hempy/Reservoir wi...
Settings * ModuleSettings
This object will store the settings loaded from the EEPROM. Persistent between reboots.
HardwareSerial & ArduinoSerial
Reference to the Arduino Serial output : Mega 2560 Rev3 Serial:0(RX) 1(TX) also showed on USB,...
ELClientWebServer WebServer & ESPLink
ESP-link - WebServer API.
bool & Debug
True - Turns on extra debug messages on the Serial Output.
void settingsFieldCallback(char *Field)
Called when a field on the /Settings.html website is submitted.
void heartBeat()
Turns the integrated LED on the Arduino board ON/OFF.
void refreshCallback(__attribute__((unused)) char *Url)
Called when a website is refreshing on the ESP-link webserver.
void loadCallback(__attribute__((unused)) char *Url)
Called when a website is loading on the ESP-link webserver.
void setupMqtt()
Sets up the MQTT relay.
bool & Metric
True - Use metric units, False - Use imperial units.
void run1sec()
void ignoreCallback(__attribute__((unused)) char *Url)
Ignores the incoming load/refresh event. Used when embedding another module's web interface that alre...
void getWirelessStatus()
Prints the nRF24L01+ wireless transceiver's status to the Serial log.
void loop()
constexpr uint8_t WirelessPayloadSize
Size of the wireless packages exchanged with the Main module. Max 32 bytes are supported on nRF24L01+...
Definition Settings.h:31
constexpr uint8_t MaxShotTextLength
Default char * buffer length for storing mutiple words. Memory intense!
Definition Settings.h:18
constexpr uint8_t MaxWordLength
Default char * buffer length for storing a word + null terminator. Memory intense!
Definition Settings.h:17
constexpr uint16_t MaxLongTextLength
Default char * buffer length for storing a long text. Memory intense!
Definition Settings.h:19
String popString()
Extract a string from the response packet.
void attach(T *item, retT(T::*method)(argT))
Definition FP.h:147
ESP esp & Serial
Definition demo.ino:9
void mqttData(void *response)
Definition mqtt.ino:55
bool Metric
Switch between Imperial/Metric units. If changed update the default temp and pressure values below to...
Definition Settings.h:61
struct MainModuleSettings Main1
Definition Settings.h:134
bool Debug
Logs debug messages to serial and web outputs.
Definition Settings.h:60
FP< void, char * > setFieldCb
callback for setting a field from a HTML form
FP< void, char * > loadCb
Callback for HTML page loading.
FP< void, char * > buttonCb
callback for a HTML button press
FP< void, char * > refreshCb
Callback for HTML page refresh.