From e0e80dedad02e9a9738a25eff7d157dcd5a4e491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Ha=C3=9Fel?= Date: Tue, 9 Apr 2024 16:23:53 +0200 Subject: [PATCH] [UNTESTED] Patrix, log, console, wifi, mqtt --- .gitignore | 2 + lib/patrix/Patrix.cpp | 21 ++++++ lib/patrix/Patrix.h | 8 +++ lib/patrix/console.cpp | 74 +++++++++++++++++++ lib/patrix/console.h | 8 +++ lib/patrix/log.cpp | 41 +++++++++++ lib/patrix/log.h | 8 +++ lib/patrix/mqtt.cpp | 79 ++++++++++++++++++++ lib/patrix/mqtt.h | 16 +++++ lib/patrix/wifi.cpp | 159 +++++++++++++++++++++++++++++++++++++++++ lib/patrix/wifi.h | 20 ++++++ platformio.ini | 15 ++++ src/Fermenter.cpp | 9 +++ 13 files changed, 460 insertions(+) create mode 100644 .gitignore create mode 100644 lib/patrix/Patrix.cpp create mode 100644 lib/patrix/Patrix.h create mode 100644 lib/patrix/console.cpp create mode 100644 lib/patrix/console.h create mode 100644 lib/patrix/log.cpp create mode 100644 lib/patrix/log.h create mode 100644 lib/patrix/mqtt.cpp create mode 100644 lib/patrix/mqtt.h create mode 100644 lib/patrix/wifi.cpp create mode 100644 lib/patrix/wifi.h create mode 100644 platformio.ini create mode 100644 src/Fermenter.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b43bfca --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.pio +.idea diff --git a/lib/patrix/Patrix.cpp b/lib/patrix/Patrix.cpp new file mode 100644 index 0000000..6a8a15c --- /dev/null +++ b/lib/patrix/Patrix.cpp @@ -0,0 +1,21 @@ +#include +#include +#include +#include +#include "mqtt.h" +#include "log.h" + +void setup() { + delay(500); + Serial.begin(115200); + info("MAIN", "\n\n\nStartup..."); + mqttSetup(); + wifiSetup(); + patrixSetup(); +} + +void loop() { + consoleLoop(); + mqttLoop(); + patrixLoop(); +} \ No newline at end of file diff --git a/lib/patrix/Patrix.h b/lib/patrix/Patrix.h new file mode 100644 index 0000000..84e17db --- /dev/null +++ b/lib/patrix/Patrix.h @@ -0,0 +1,8 @@ +#ifndef SENSOR3_PATRIX_H +#define SENSOR3_PATRIX_H + +void patrixSetup(); + +void patrixLoop(); + +#endif diff --git a/lib/patrix/console.cpp b/lib/patrix/console.cpp new file mode 100644 index 0000000..02ce640 --- /dev/null +++ b/lib/patrix/console.cpp @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include +#include "mqtt.h" +#include "wifi.h" + +char consoleBuffer[64] = ""; + +char *consoleBufferW = consoleBuffer; + +void consoleLoop() { + uint8_t i = 0; + while (Serial.available() > 0 && i++ < 100) { + const char symbol = static_cast(Serial.read()); + if (symbol == '\n' || symbol == '\r') { + if (*consoleBuffer != 0) { + Serial.print('\n'); + info("CONSOLE", "> %s", consoleBuffer); + consoleHandle(consoleBuffer); + consoleBufferW = consoleBuffer; + *consoleBufferW = 0; + } + } else if (symbol == '\b') { + if (consoleBufferW > consoleBuffer) { + consoleBufferW--; + *consoleBufferW = 0; + Serial.print("\b \b"); + } + } else { + if (consoleBufferW < consoleBuffer + sizeof consoleBuffer - 1) { + *consoleBufferW = symbol; + consoleBufferW++; + *consoleBufferW = 0; + Serial.print("\r"); + Serial.print(consoleBuffer); + } else { + Serial.print("\nCONSOLE BUFFER OVERFLOW\n"); + Serial.print(consoleBuffer); + } + } + } +} + +void consoleHandle(const char *cmd) { + if (strcmp(cmd, "help") == 0) { + info("CONSOLE", "help : print this help"); + info("CONSOLE", "net : print network info"); + info("CONSOLE", "reboot : reboot system"); + info("CONSOLE", "wifi : reconnect wifi"); + info("CONSOLE", "mqtt : reconnect mqtt"); + } else if (strcmp(cmd, "net") == 0) { + info("CONSOLE", "MAC: %17s", WiFi.macAddress().c_str()); + info("CONSOLE", "IP: %17s", WiFi.localIP().toString().c_str()); + info("CONSOLE", "Gateway: %17s", WiFi.gatewayIP().toString().c_str()); + info("CONSOLE", "SSID: %17s", WiFi.SSID().c_str()); + info("CONSOLE", "BSSID: %17s", WiFi.BSSIDstr().c_str()); + info("CONSOLE", "RSSI: %17d", WiFi.RSSI()); + } else if (strcmp(cmd, "reboot") == 0) { + info("CONSOLE", "Rebooting..."); + delay(500); + yield(); + ESP.restart(); + } else if (strcmp(cmd, "wifi") == 0) { + info("CONSOLE", "Reconnecting WiFi..."); + wifiConnect(); + } else if (strcmp(cmd, "mqtt") == 0) { + info("CONSOLE", "Reconnecting MQTT..."); + mqttDisconnect(); + } else { + info("CONSOLE", "Unknown command: %s", cmd); + } +} diff --git a/lib/patrix/console.h b/lib/patrix/console.h new file mode 100644 index 0000000..59a8ed6 --- /dev/null +++ b/lib/patrix/console.h @@ -0,0 +1,8 @@ +#ifndef SENSOR3_CONSOLE_H +#define SENSOR3_CONSOLE_H + +void consoleLoop(); + +void consoleHandle(const char *cmd); + +#endif diff --git a/lib/patrix/log.cpp b/lib/patrix/log.cpp new file mode 100644 index 0000000..72e3d17 --- /dev/null +++ b/lib/patrix/log.cpp @@ -0,0 +1,41 @@ +#include +#include "log.h" +#include "mqtt.h" + +#define SEPARATOR " | " + +void getDateTime(char *buffer, size_t size) { + time_t now; + time(&now); + tm time{}; + localtime_r(&now, &time); + strftime(buffer, size, "%Y-%m-%d %H:%M:%S %z", &time); +} + +void log(const char *level, const char *module, const char *format, ...) { + char datetime[26]; + getDateTime(datetime, sizeof datetime); + + va_list vl; + va_start(vl, format); + char message[500]; + vsnprintf(message, sizeof message, format, vl); + va_end(vl); + + const size_t len = Serial.print(datetime) + Serial.print(SEPARATOR) + Serial.print(module) + Serial.print(SEPARATOR) + Serial.print(level) + Serial.print(SEPARATOR) + Serial.print(message) + Serial.println(); + mqttLog(len, SEPARATOR, datetime, module, level, message); +} + +void info(const char *module, const char *format, ...) { + va_list vl; + va_start(vl, format); + log("INFO", module, format, vl); + va_end(vl); +} + +void error(const char *module, const char *format, ...) { + va_list vl; + va_start(vl, format); + log("ERROR", module, format, vl); + va_end(vl); +} diff --git a/lib/patrix/log.h b/lib/patrix/log.h new file mode 100644 index 0000000..0866f69 --- /dev/null +++ b/lib/patrix/log.h @@ -0,0 +1,8 @@ +#ifndef SENSOR3_LOG_H +#define SENSOR3_LOG_H + +void info(const char *module, const char *format, ...); + +void error(const char *module, const char *format, ...); + +#endif diff --git a/lib/patrix/mqtt.cpp b/lib/patrix/mqtt.cpp new file mode 100644 index 0000000..a870a67 --- /dev/null +++ b/lib/patrix/mqtt.cpp @@ -0,0 +1,79 @@ +#include "mqtt.h" + +#include +#include +#include "log.h" +#include "PubSubClient.h" +#include "console.h" + +#define CONNECT_TIMEOUT_MILLISECONDS 5000 + +unsigned long mqttLastConnectTry = 0; + +WiFiClient espClient; + +PubSubClient mqtt(espClient); + +char logTopic[32] = "log/UNSET"; + +char cmdTopic[64] = "cmd/UNSET"; + +void mqttSetup() { + snprintf(logTopic, sizeof logTopic, "%s/%s", "log", HOSTNAME); + snprintf(cmdTopic, sizeof cmdTopic, "%s/%s", "cmd", HOSTNAME); +} + +void mqttLog(const size_t len, const char *separator, const char *datetime, const char *module, const char *level, const char *message) { + if (mqtt.beginPublish(logTopic, len, false)) { + mqtt.print(datetime); + mqtt.print(separator); + mqtt.print(module); + mqtt.print(separator); + mqtt.print(level); + mqtt.print(separator); + mqtt.print(message); + mqtt.endPublish(); + } +} + +void mqttDisconnect() { + mqtt.disconnect(); +} + +void mqttLoop() { + if (WiFi.localIP() != 0 && !mqtt.connected() && (mqttLastConnectTry == 0 || millis() - mqttLastConnectTry > CONNECT_TIMEOUT_MILLISECONDS)) { + mqttLastConnectTry = millis(); + mqtt.setServer("10.0.0.50", 1883); + if (!mqtt.connect(HOSTNAME, logTopic, 0, false, "disconnected")) { + Serial.printf("failed to connect\n"); + return; + } + info("MQTT", "connected"); + mqtt.setBufferSize(512); + mqtt.subscribe(cmdTopic); + mqtt.setCallback([](const char *topic, const uint8_t *bytes, const unsigned int length) { + char content[64]; + if (length > sizeof content - 1) { + error("MQTT", "RECEIVE BUFFER OVERFLOW"); + return; + } + memcpy(content, bytes, length); + content[length] = 0; + + if (strcmp(topic, cmdTopic) == 0) { + info("MQTT", "CMD > %s", content); + consoleHandle(content); + } + }); + } + mqtt.loop(); +} + +bool mqttPublish(const char *topic, const JsonDocument &doc) { + if (!mqtt.connected()) { + return false; + } + char buffer[512]; + const size_t size = serializeJson(doc, buffer); + return mqtt.publish(topic, buffer, size); +} diff --git a/lib/patrix/mqtt.h b/lib/patrix/mqtt.h new file mode 100644 index 0000000..cc4445b --- /dev/null +++ b/lib/patrix/mqtt.h @@ -0,0 +1,16 @@ +#ifndef SENSOR3_MQTT_H +#define SENSOR3_MQTT_H + +#include + +void mqttSetup(); + +void mqttLoop(); + +void mqttDisconnect(); + +void mqttLog(size_t len, const char *separator, const char *datetime, const char *module, const char *level, const char *message); + +bool mqttPublish(const char *topic, const JsonDocument &doc); + +#endif diff --git a/lib/patrix/wifi.cpp b/lib/patrix/wifi.cpp new file mode 100644 index 0000000..fd99c97 --- /dev/null +++ b/lib/patrix/wifi.cpp @@ -0,0 +1,159 @@ +#include "wifi.h" +#include "log.h" + +#include +#include + +#define NTP_SERVER "pool.ntp.org" + +#define TIMEZONE_OFFSET 3600 + +#define DST_OFFSET 3600 + +#define MIN_EPOCH_SECONDS 1712675973 + +uint8_t otaLastLogStep = 0; + +bool wifiConnected = false; + +bool timeSet = false; + +time_t preTimeOffset = 0; + +unsigned long wifiLastConnectTry = 0; + +void otaSetup(); + +void bootDelay(); + +void wifiSetup() { + WiFi.softAPdisconnect(); + WiFi.setAutoReconnect(false); + wifiConnect(); + otaSetup(); + configTime(TIMEZONE_OFFSET, DST_OFFSET, NTP_SERVER, WiFi.gatewayIP().toString().c_str()); + bootDelay(); +} + +void wifiConnect() { + WiFi.disconnect(); + yield(); + wifiLastConnectTry = millis(); + info("WIFI", "Connecting: %s", WIFI_SSID); + WiFi.begin(WIFI_SSID, WIFI_PKEY); + yield(); +} + +void otaSetup() { + ArduinoOTA.onStart([] { + info("OTA", "Start!"); + otaLastLogStep = 0; + }); + ArduinoOTA.onProgress([](unsigned int received, unsigned int total) { + uint8_t currentStep = 20 * received / total; + if (otaLastLogStep != currentStep) { + info("OTA", "Progress: %3d%%", currentStep * 5); + otaLastLogStep = currentStep; + } + }); + ArduinoOTA.onEnd([] { + info("OTA", "Complete!"); + }); + ArduinoOTA.onError([](ota_error_t e) { + const char *name; + switch (e) { + case OTA_AUTH_ERROR: + name = "AUTH"; + break; + case OTA_BEGIN_ERROR: + name = "BEGIN"; + break; + case OTA_CONNECT_ERROR: + name = "CONNECT"; + break; + case OTA_RECEIVE_ERROR: + name = "RECEIVE"; + break; + case OTA_END_ERROR: + name = "END"; + break; + default: + name = "[???]"; + break; + } + error("OTA", name); + }); + ArduinoOTA.begin(); +} + +void bootDelay() { +#ifdef BOOT_DELAY + info("BOOT DELAY", "Waiting for WiFi..."); + while ((uint32_t) WiFi.localIP() == 0) { + delay(100); + yield(); + wifiLoop(); + } + info("BOOT DELAY", "WiFi connected!"); + unsigned long begin = millis(); + while (millis() - begin < 10000) { + delay(100); + yield(); + wifiLoop(); + } + info("BOOT DELAY", "Completed."); +#endif +} + +void wifiLoop() { + const time_t epochSeconds = getTime(); + if (!timeSet) { + timeSet = epochSeconds >= MIN_EPOCH_SECONDS; + if (!timeSet) { + preTimeOffset = epochSeconds; + } else { + info("NTP", "Time set!"); + } + } + + const bool hasIp = static_cast(WiFi.localIP()) != 0; + if (wifiConnected) { + if (!hasIp) { + wifiConnected = false; + info("WIFI", "Disconnected!"); + wifiConnect(); + } + } else { + if (hasIp) { + wifiConnected = true; + info("WIFI", "Connected as: %s", WiFi.localIP().toString().c_str()); + } else if (millis() - wifiLastConnectTry > 10000) { + info("WIFI", "Timeout!"); + wifiConnect(); + } + } +} + +bool isTimeSet() { + return timeSet; +} + +time_t getTime() { + time_t epochSeconds; + time(&epochSeconds); + return epochSeconds; +} + +time_t correctTime(const time_t value) { + if (!timeSet) { + return value; + } + if (value < MIN_EPOCH_SECONDS) { + return getTime() - preTimeOffset + value; + } + return value; +} + +bool isOlderThan(time_t time, time_t seconds) { + return getTime() > correctTime(time) + seconds; +} diff --git a/lib/patrix/wifi.h b/lib/patrix/wifi.h new file mode 100644 index 0000000..afce175 --- /dev/null +++ b/lib/patrix/wifi.h @@ -0,0 +1,20 @@ +#ifndef GAERBOX_WIFI_H +#define GAERBOX_WIFI_H + +#include + +void wifiSetup(); + +void wifiLoop(); + +void wifiConnect(); + +bool isTimeSet(); + +time_t getTime(); + +time_t correctTime(time_t value); + +bool isOlderThan(time_t time, time_t seconds); + +#endif diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..a9b0546 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,15 @@ +[env:Fermenter] +platform = espressif32 +board = esp32dev +framework = arduino +upload_port = /dev/ttyUSB0 +upload_speed = 921600 +;upload_protocol = espota +;upload_port = +;upload_flags = --auth=OtaAuthPatrixFermenter +monitor_port = /dev/ttyUSB0 +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder +build_flags = -D HOSTNAME=\"Fermenter\" -D WIFI_SSID=\"HappyNet\" -D WIFI_PKEY=\"1Grausame!Sackratte7\" -D OTA_PASSWORD=\"OtaAuthPatrixFermenter\" -D BOOT_DELAY=true +lib_deps = knolleary/PubSubClient + bblanchon/ArduinoJson \ No newline at end of file diff --git a/src/Fermenter.cpp b/src/Fermenter.cpp new file mode 100644 index 0000000..7508389 --- /dev/null +++ b/src/Fermenter.cpp @@ -0,0 +1,9 @@ +#include + +void patrixSetup() { + +} + +void patrixLoop() { + +} \ No newline at end of file