From 0046a02733a6bd5619ef07897d392e9ad0864218 Mon Sep 17 00:00:00 2001 From: RobThree Date: Wed, 9 Apr 2025 03:42:43 +0200 Subject: [PATCH 01/11] Move stuff to separate files --- lib/Display/display.cpp | 40 +++++++++++ lib/Display/display.h | 20 ++++++ lib/Logger/logger.cpp | 40 +++++++++++ lib/Logger/logger.h | 33 +++++++++ lib/OTA/ota.cpp | 41 +++++++++++ lib/OTA/ota.h | 16 +++++ lib/SimpleWiFi/simplewifi.cpp | 42 +++++++++++ lib/SimpleWiFi/simplewifi.h | 18 +++++ platformio.ini | 1 - src/main.cpp | 131 +++++++--------------------------- 10 files changed, 277 insertions(+), 105 deletions(-) create mode 100644 lib/Display/display.cpp create mode 100644 lib/Display/display.h create mode 100644 lib/Logger/logger.cpp create mode 100644 lib/Logger/logger.h create mode 100644 lib/OTA/ota.cpp create mode 100644 lib/OTA/ota.h create mode 100644 lib/SimpleWiFi/simplewifi.cpp create mode 100644 lib/SimpleWiFi/simplewifi.h diff --git a/lib/Display/display.cpp b/lib/Display/display.cpp new file mode 100644 index 0000000..e3db8bd --- /dev/null +++ b/lib/Display/display.cpp @@ -0,0 +1,40 @@ +#include "display.h" +#include + +Display::Display(Logger &log, uint8_t w, uint8_t h) + : logger(log), width(w), height(h), display(w, h, &Wire, -1) {} + +void Display::begin() { + Wire.begin(); + + // Initialize SSD1306 display + if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // 0x3C is the I2C address of SSD1306 + logger.error("SSD1306 allocation failed"); + ESP.restart(); + } + + display.clearDisplay(); + display.setRotation(2); + display.setTextSize(2); + display.setTextColor(SSD1306_WHITE); +} + +void Display::showMeasurements(float temperature, bool isFahrenheit, float humidity) { + display.clearDisplay(); + display.setCursor(0, 0); + display.print("Temp: "); + display.print(temperature); + display.print(isFahrenheit ? " F" : " C"); + display.setCursor(0, 16); + display.print("Humidity: "); + display.print(humidity); + display.print(" %"); + display.display(); +} + +void Display::setStatus(const String &status) { + display.clearDisplay(); + display.setCursor(0, 0); + display.print(status); + display.display(); +} \ No newline at end of file diff --git a/lib/Display/display.h b/lib/Display/display.h new file mode 100644 index 0000000..8e88bec --- /dev/null +++ b/lib/Display/display.h @@ -0,0 +1,20 @@ +#ifndef DISPLAY_H +#define DISPLAY_H + +#include +#include "../Logger/logger.h" + +class Display { +public: + Display(Logger &log, uint8_t width, uint8_t height); + void begin(); + void setStatus(const String &status); + void showMeasurements(float temperature, bool isFahrenheit, float humidity); +private: + Logger &logger; // Reference to the logger + uint8_t width; + uint8_t height; + Adafruit_SSD1306 display; +}; + +#endif // DISPLAY_H diff --git a/lib/Logger/logger.cpp b/lib/Logger/logger.cpp new file mode 100644 index 0000000..bfd0bc9 --- /dev/null +++ b/lib/Logger/logger.cpp @@ -0,0 +1,40 @@ +#include "logger.h" + +Logger::Logger(Stream &output) : _output(output), _logLevel(INFO) {} + +void Logger::setLogLevel(Level level) { + _logLevel = level; +} + +void Logger::log(Level level, const char* message) { + if (level >= _logLevel) { + _output.print("["); + _output.print(levelToString(level)); + _output.print("] "); + _output.println(message); + } +} + +void Logger::log(Level level, const String &message) { + log(level, message.c_str()); // Convert String to const char* +} + +void Logger::debug(const char* message) { log(DEBUG, message); } +void Logger::info(const char* message) { log(INFO, message); } +void Logger::warn(const char* message) { log(WARN, message); } +void Logger::error(const char* message) { log(ERROR, message); } + +void Logger::debug(const String &message) { log(DEBUG, message); } +void Logger::info(const String &message) { log(INFO, message); } +void Logger::warn(const String &message) { log(WARN, message); } +void Logger::error(const String &message) { log(ERROR, message); } + +const char* Logger::levelToString(Level level) { + switch (level) { + case DEBUG: return "DEBUG"; + case INFO: return "INFO"; + case WARN: return "WARN"; + case ERROR: return "ERROR"; + default: return "UNKNOWN"; + } +} diff --git a/lib/Logger/logger.h b/lib/Logger/logger.h new file mode 100644 index 0000000..72cf811 --- /dev/null +++ b/lib/Logger/logger.h @@ -0,0 +1,33 @@ +#ifndef LOGGER_H +#define LOGGER_H + +#include // Required for Serial and String handling + +class Logger { +public: + enum Level { DEBUG, INFO, WARN, ERROR }; + + Logger(Stream &output); // Constructor + + void setLogLevel(Level level); + void log(Level level, const char* message); + void log(Level level, const String &message); + + void debug(const char* message); + void info(const char* message); + void warn(const char* message); + void error(const char* message); + + void debug(const String &message); + void info(const String &message); + void warn(const String &message); + void error(const String &message); + + +private: + Stream &_output; + Level _logLevel; + const char* levelToString(Level level); +}; + +#endif // LOGGER_H diff --git a/lib/OTA/ota.cpp b/lib/OTA/ota.cpp new file mode 100644 index 0000000..d3ecd08 --- /dev/null +++ b/lib/OTA/ota.cpp @@ -0,0 +1,41 @@ +#include +#include "ota.h" + +OTA::OTA(Logger &log) + : logger(log) {} + +void OTA::begin(const char *const hostname, const char *const password) { + ArduinoOTA.setPassword(hostname); + ArduinoOTA.setHostname(password); + ArduinoOTA.onStart([this]() { + String type = (ArduinoOTA.getCommand() == U_FLASH) ? "sketch" : "filesystem"; + logger.info(("Start updating " + type).c_str()); + }); + ArduinoOTA.onEnd([this]() { + logger.info("Update Complete"); + }); + ArduinoOTA.onProgress([this](unsigned int progress, unsigned int total) { + logger.info(("Progress: " + String((progress * 100) / total) + "%").c_str()); + }); + ArduinoOTA.onError([this](ota_error_t error) { + logger.error(("Error: " + String(error)).c_str()); + if (error == OTA_AUTH_ERROR) { + logger.error("Auth Failed"); + } else if (error == OTA_BEGIN_ERROR) { + logger.error("Begin Failed"); + } else if (error == OTA_CONNECT_ERROR) { + logger.error("Connect Failed"); + } else if (error == OTA_RECEIVE_ERROR) { + logger.error("Receive Failed"); + } else if (error == OTA_END_ERROR) { + logger.error("End Failed"); + } + }); + + logger.info("Starting OTA server"); + ArduinoOTA.begin(); +} + +void OTA::handle() { + ArduinoOTA.handle(); +} \ No newline at end of file diff --git a/lib/OTA/ota.h b/lib/OTA/ota.h new file mode 100644 index 0000000..87b77b9 --- /dev/null +++ b/lib/OTA/ota.h @@ -0,0 +1,16 @@ +#ifndef OTA_H +#define OTA_H + +#include +#include "../Logger/logger.h" + +class OTA { +public: + OTA(Logger &log); + void begin(const char *const hostname, const char *const password); + void handle(); +private: + Logger &logger; // Reference to the logger +}; + +#endif // OTA_H diff --git a/lib/SimpleWiFi/simplewifi.cpp b/lib/SimpleWiFi/simplewifi.cpp new file mode 100644 index 0000000..f29e80d --- /dev/null +++ b/lib/SimpleWiFi/simplewifi.cpp @@ -0,0 +1,42 @@ +#include "simplewifi.h" + +SimpleWiFi::SimpleWiFi(Logger &log) + : logger(log) {} + +void SimpleWiFi::begin(unsigned long portalTimeout, unsigned long wifiConnectTimeout, + unsigned long wifiConnectRetries, const char *apName, + const char *apPassword) { + logger.info("Starting WiFi"); + wifiManager.setConfigPortalTimeout(portalTimeout); + wifiManager.setConnectTimeout(wifiConnectTimeout); + wifiManager.setConnectRetries(wifiConnectRetries); + wifiManager.setWiFiAutoReconnect(true); + if (!wifiManager.autoConnect(apName, apPassword)) { + logger.error("Autoconnect failed. Restarting..."); + delay(3000); + ESP.restart(); + } +} + +void SimpleWiFi::ensureConnected() { + if ((WiFi.status() != WL_CONNECTED)) { + logger.warn("WiFi not connected, reconnecting..."); + WiFi.reconnect(); + unsigned long start = millis(); + + while (WiFi.status() != WL_CONNECTED && millis() - start < 10000) { + delay(100); + } + + if (WiFi.status() == WL_CONNECTED) { + logger.error("WiFi reconnected"); + } else { + logger.warn("Failed to reconnect to WiFi. Retrying..."); + delay(1000); + } + } +} + +String SimpleWiFi::localIP() { + return WiFi.localIP().toString(); +} \ No newline at end of file diff --git a/lib/SimpleWiFi/simplewifi.h b/lib/SimpleWiFi/simplewifi.h new file mode 100644 index 0000000..18afd5a --- /dev/null +++ b/lib/SimpleWiFi/simplewifi.h @@ -0,0 +1,18 @@ +#ifndef SIMPLEWIFI_H +#define SIMPLEWIFI_H + +#include +#include "../Logger/logger.h" + +class SimpleWiFi { +public: + SimpleWiFi(Logger &log); + void begin(unsigned long portalTimeout, unsigned long wifiConnectTimeout, unsigned long wifiConnectRetries, const char *apName, const char *apPassword = (const char *)__null); + void ensureConnected(); + String localIP(); +private: + Logger &logger; // Reference to the logger + WiFiManager wifiManager; +}; + +#endif // SIMPLEWIFI_H \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 1d32109..e8824e4 100644 --- a/platformio.ini +++ b/platformio.ini @@ -28,7 +28,6 @@ board_build.filesystem = littlefs board_build.ldscript = eagle.flash.4m1m.ld lib_deps = adafruit/Adafruit SHT31 Library@^2.2.2 - adafruit/Adafruit GFX Library@^1.12.0 adafruit/Adafruit SSD1306@^2.5.13 bblanchon/ArduinoJson@^7.2.0 tzapu/WiFiManager@^2.0.17 diff --git a/src/main.cpp b/src/main.cpp index 903d9b9..fef511c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,20 +1,21 @@ -#include #include -#include #include -#include +#include "display.h" +#include "ota.h" #include #include #include -#include -#include +#include "simplewifi.h" #include // See include/example_config.h for configuration. Make sure you copy it // to include/config.h and enter your configuration data there. +Logger logger(Serial); +OTA ota(logger); +SimpleWiFi wifi(logger); +Display display(logger, SCREEN_WIDTH, SCREEN_HEIGHT); Adafruit_SHT31 sht31 = Adafruit_SHT31(); -Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); // -1 means no reset pin ESP8266WebServer server(HTTP_PORT); ESP8266HTTPUpdateServer httpUpdateServer; @@ -33,17 +34,13 @@ struct Settings { Settings settings; -void setStatus(const String &status) { - Serial.println(status); - - display.clearDisplay(); - display.setCursor(0, 0); - display.print(status); - display.display(); +void setStatus(Logger::Level level, const String &status) { + logger.log(level, status); + display.setStatus(status); } void restart(const String &status) { - setStatus(status); + setStatus(Logger::WARN, status); delay(3000); ESP.restart(); } @@ -93,41 +90,22 @@ float offsetToFahrenheit(float celsius) { return celsius * factorCtoF; } void setup() { Serial.begin(SERIAL_BAUDRATE); - Wire.begin(); - - // Initialize SSD1306 display - if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // 0x3C is the I2C address of SSD1306 - setStatus("SSD1306 allocation failed"); - ESP.restart(); - } - - display.clearDisplay(); - display.setRotation(2); - display.setTextSize(2); - display.setTextColor(SSD1306_WHITE); if (!LittleFS.begin()) { - setStatus("Failed to mount filesystem"); + setStatus(Logger::ERROR, "Failed to mount filesystem"); } if (loadSettings()) { - setStatus("Settings loaded"); + setStatus(Logger::INFO, "Settings loaded"); } else { - setStatus("Using default settings"); + setStatus(Logger::INFO, "Using default settings"); } - WiFiManager wifiManager; - wifiManager.setConfigPortalTimeout(PORTALTIMEOUT); - wifiManager.setConnectTimeout(WIFICONNECTTIMEOUT); - wifiManager.setConnectRetries(WIFICONNECTRETRIES); - wifiManager.setWiFiAutoReconnect(true); - if (!wifiManager.autoConnect(settings.deviceName)) { - restart("Autconnect failed..."); - } + wifi.begin(PORTALTIMEOUT, WIFICONNECTTIMEOUT, WIFICONNECTRETRIES, settings.deviceName); // Initialize GXHT30 if (!sht31.begin(0x44)) { // 0x44 is the I2C address of GXHT30 - setStatus("Couldn't find GXHT30 sensor!"); + setStatus(Logger::ERROR, "Couldn't find GXHT30 sensor!"); ESP.restart(); } @@ -198,7 +176,7 @@ void setup() { if (errors.size() == 0 && saveSettings(newsettings)) { root["status"] = "success"; - setStatus("Settings saved"); + setStatus(Logger::INFO, "Settings saved"); } else { root["status"] = "error"; } @@ -213,56 +191,16 @@ void setup() { Serial.println("Starting HTTP server"); server.begin(); - ArduinoOTA.setPassword(OTAPASSWORD); - ArduinoOTA.setHostname(settings.deviceName); - ArduinoOTA.onStart([]() { - String type = (ArduinoOTA.getCommand() == U_FLASH) ? "sketch" : "filesystem"; - setStatus("Start updating " + type); - }); - ArduinoOTA.onEnd([]() { setStatus("Update Complete"); }); - ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { - setStatus("Progress: " + String((progress * 100) / total) + "%"); - }); - ArduinoOTA.onError([](ota_error_t error) { - setStatus("Error: " + String(error)); - if (error == OTA_AUTH_ERROR) - setStatus("Auth Failed"); - else if (error == OTA_BEGIN_ERROR) - setStatus("Begin Failed"); - else if (error == OTA_CONNECT_ERROR) - setStatus("Connect Failed"); - else if (error == OTA_RECEIVE_ERROR) - setStatus("Receive Failed"); - else if (error == OTA_END_ERROR) - setStatus("End Failed"); - }); + ota.begin(settings.deviceName, OTAPASSWORD); - setStatus("Starting OTA server"); - ArduinoOTA.begin(); - - setStatus(WiFi.localIP().toString()); + setStatus(Logger::INFO, wifi.localIP()); delay(2000); } void loop() { server.handleClient(); - ArduinoOTA.handle(); - if ((WiFi.status() != WL_CONNECTED)) { - setStatus("WiFi not connected, reconnecting..."); - WiFi.reconnect(); - unsigned long start = millis(); - - while (WiFi.status() != WL_CONNECTED && millis() - start < 10000) { - delay(100); - } - - if (WiFi.status() == WL_CONNECTED) { - setStatus("WiFi reconnected"); - } else { - setStatus("Failed to reconnect to WiFi. Retrying..."); - delay(1000); - } - } + ota.handle(); + wifi.ensureConnected(); unsigned long currentMillis = millis(); // Get the current time @@ -275,25 +213,10 @@ void loop() { Serial.printf("Temperature: %.2f C, %.2f F, humidity: %.2f %%RH\n", temperature, toFahrenheit(temperature), humidity); - display.clearDisplay(); - - display.setCursor(0, 0); - display.print("Temp: "); - display.setCursor(15, 15); - if (settings.showFahrenheit) { - display.print(toFahrenheit(temperature)); - display.print(" F"); - } else { - display.print(temperature); - display.print(" C"); - } - - display.setCursor(0, 30); - display.print("Humidity: "); - display.setCursor(15, 45); - display.print(humidity); - display.print(" %"); - - display.display(); + display.showMeasurements( + settings.showFahrenheit ? toFahrenheit(temperature) : temperature, + settings.showFahrenheit, + humidity + ); } -} +} \ No newline at end of file From 60478ee569ce92b2f10dd47a652bcfe2f68b4de2 Mon Sep 17 00:00:00 2001 From: RobThree Date: Wed, 9 Apr 2025 05:16:40 +0200 Subject: [PATCH 02/11] More splitting to separate files --- .vscode/settings.json | 7 ++ lib/Display/display.cpp | 12 +-- lib/Display/display.h | 3 +- lib/Sensor/sensor.cpp | 19 +++++ lib/Sensor/sensor.h | 18 ++++ lib/Sensor/sensordata.h | 52 ++++++++++++ lib/SimpleSettings/appsettings.h | 12 +++ lib/SimpleSettings/settings.cpp | 38 +++++++++ lib/SimpleSettings/settings.h | 24 ++++++ src/main.cpp | 139 +++++++++++-------------------- 10 files changed, 229 insertions(+), 95 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 lib/Sensor/sensor.cpp create mode 100644 lib/Sensor/sensor.h create mode 100644 lib/Sensor/sensordata.h create mode 100644 lib/SimpleSettings/appsettings.h create mode 100644 lib/SimpleSettings/settings.cpp create mode 100644 lib/SimpleSettings/settings.h diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..dcd99cc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "files.associations": { + "*.module": "php", + "*.php": "php", + "cmath": "cpp" + } +} \ No newline at end of file diff --git a/lib/Display/display.cpp b/lib/Display/display.cpp index e3db8bd..2f11f7a 100644 --- a/lib/Display/display.cpp +++ b/lib/Display/display.cpp @@ -1,4 +1,5 @@ #include "display.h" +#include "sensordata.h" #include Display::Display(Logger &log, uint8_t w, uint8_t h) @@ -19,16 +20,17 @@ void Display::begin() { display.setTextColor(SSD1306_WHITE); } -void Display::showMeasurements(float temperature, bool isFahrenheit, float humidity) { +void Display::showMeasurements(SensorData &data, bool showFahrenheit) { display.clearDisplay(); + display.setCursor(0, 0); display.print("Temp: "); - display.print(temperature); - display.print(isFahrenheit ? " F" : " C"); + display.print(data.getTemperatureDisplay(showFahrenheit)); + display.setCursor(0, 16); display.print("Humidity: "); - display.print(humidity); - display.print(" %"); + display.print(data.getHumidityDisplay()); + display.display(); } diff --git a/lib/Display/display.h b/lib/Display/display.h index 8e88bec..ab1746b 100644 --- a/lib/Display/display.h +++ b/lib/Display/display.h @@ -2,6 +2,7 @@ #define DISPLAY_H #include +#include "sensordata.h" #include "../Logger/logger.h" class Display { @@ -9,7 +10,7 @@ class Display { Display(Logger &log, uint8_t width, uint8_t height); void begin(); void setStatus(const String &status); - void showMeasurements(float temperature, bool isFahrenheit, float humidity); + void showMeasurements(SensorData &data, bool showFahrenheit); private: Logger &logger; // Reference to the logger uint8_t width; diff --git a/lib/Sensor/sensor.cpp b/lib/Sensor/sensor.cpp new file mode 100644 index 0000000..5fb0bc2 --- /dev/null +++ b/lib/Sensor/sensor.cpp @@ -0,0 +1,19 @@ +#include "sensor.h" + +Sensor::Sensor(Logger &log) + : logger(log) {} + +void Sensor::begin(uint8_t i2cAddress) { + if (!sht31.begin(i2cAddress)) { + logger.error("Couldn't find GXHT30 sensor!"); + ESP.restart(); + } +} + +SensorData Sensor::readData(float tempOffset, float humidityOffset) { + SensorData data(sht31.readTemperature(), tempOffset, sht31.readHumidity(), humidityOffset); + if (isnan(data.temperature_c) || isnan(data.humidity)) { + logger.error("Failed to read sensor data"); + } + return data; +} \ No newline at end of file diff --git a/lib/Sensor/sensor.h b/lib/Sensor/sensor.h new file mode 100644 index 0000000..d35b141 --- /dev/null +++ b/lib/Sensor/sensor.h @@ -0,0 +1,18 @@ +#ifndef SENSOR_H +#define SENSOR_H + +#include +#include "sensordata.h" +#include "../Logger/logger.h" + +class Sensor { +public: + Sensor(Logger &log); + void begin(uint8_t i2cAddress = 0x44); // Default I2C address for SHT31 + SensorData readData(float tempOffset, float humidityOffset); +private: + Logger &logger; // Reference to the logger + Adafruit_SHT31 sht31 = Adafruit_SHT31(); +}; + +#endif // SENSOR_H \ No newline at end of file diff --git a/lib/Sensor/sensordata.h b/lib/Sensor/sensordata.h new file mode 100644 index 0000000..86400cc --- /dev/null +++ b/lib/Sensor/sensordata.h @@ -0,0 +1,52 @@ +#ifndef SENSORDATA_H +#define SENSORDATA_H + +#include + +struct SensorData { + float temperature_c; + float temperature_f; + float offset_c; + float offset_f; + float humidity; + float humidity_offset; + + static constexpr float factorCtoF = 9.0 / 5.0; + static float toFahrenheit(float celsius) { + return std::isnan(celsius) ? NAN : (celsius * factorCtoF) + 32.0; + } + static float offsetToFahrenheit(float celsius) { return celsius * factorCtoF; } + + SensorData(float temp_c = NAN, float offset_c = 0, float hum = NAN, float hum_offset = 0) + : temperature_c(temp_c), temperature_f(toFahrenheit(temp_c)), offset_c(offset_c), + offset_f(offsetToFahrenheit(temp_c)), humidity(hum), humidity_offset(hum_offset) {} + + float getTemperatureDisplayValue(bool showFahrenheit = false) const { + return showFahrenheit ? temperature_f + offset_f : temperature_c + offset_f; + } + float getHumidityDisplayValue() const { return humidity + humidity_offset; } + + String getTemperatureDisplay(bool showFahrenheit = false) const { + float t = getTemperatureDisplayValue(showFahrenheit); + if (std::isnan(t)) { + return "N/A"; + } + + char buffer[10]; + snprintf(buffer, sizeof(buffer), "%.2f %s", t, showFahrenheit ? "F" : "C"); + return String(buffer); + } + + String getHumidityDisplay() const { + float h = getHumidityDisplayValue(); + if (std::isnan(h)) { + return "N/A"; + } + + char buffer[10]; + snprintf(buffer, sizeof(buffer), "%.2f %%RH", h); + return String(buffer); + } +}; + +#endif // SENSORDATA_H \ No newline at end of file diff --git a/lib/SimpleSettings/appsettings.h b/lib/SimpleSettings/appsettings.h new file mode 100644 index 0000000..1386e37 --- /dev/null +++ b/lib/SimpleSettings/appsettings.h @@ -0,0 +1,12 @@ +#ifndef APPSETTINGS_H +#define APPSETTINGS_H + +struct AppSettings { + unsigned long updateInterval; + bool showFahrenheit; + float tempOffset; + float humidityOffset; + char deviceName[32]; +}; + +#endif // APPSETTINGS_H \ No newline at end of file diff --git a/lib/SimpleSettings/settings.cpp b/lib/SimpleSettings/settings.cpp new file mode 100644 index 0000000..6f103f6 --- /dev/null +++ b/lib/SimpleSettings/settings.cpp @@ -0,0 +1,38 @@ +#include "appsettings.h" +#include "settings.h" + +const char *Settings::SETTINGS_FILENAME = "/settings.bin"; + +void Settings::begin() { + LittleFS.begin(); +} + +bool Settings::loadSettings(AppSettings &settings, std::function getDefaultSettings) { + File file = LittleFS.open(filename, "r"); + if (!file || file.size() < sizeof(AppSettings)) { + getDefaultSettings(settings); + logger.warn("Using default settings"); + return true; // No settings file, used default settings + } + size_t bytesRead = file.read((uint8_t *)&settings, sizeof(AppSettings)); + file.close(); + if (bytesRead == sizeof(AppSettings)) { + logger.info("Settings loaded successfully"); + return true; + } else { + logger.error("Failed to read settings"); + } + return false; // Failed to read settings +} + +bool Settings::saveSettings(const AppSettings &newsettings, AppSettings &settings) { + File file = LittleFS.open(filename, "w"); + if (!file) { + logger.error("Failed to open file for writing"); + return false; + } + size_t bytesWritten = file.write((uint8_t *)&newsettings, sizeof(AppSettings)); + file.close(); + settings = newsettings; + return bytesWritten == sizeof(AppSettings); +} \ No newline at end of file diff --git a/lib/SimpleSettings/settings.h b/lib/SimpleSettings/settings.h new file mode 100644 index 0000000..9919cdb --- /dev/null +++ b/lib/SimpleSettings/settings.h @@ -0,0 +1,24 @@ +#ifndef SETTINGS_H +#define SETTINGS_H + +#include "../Logger/logger.h" +#include "appsettings.h" +#include + +class Settings { + public: + Settings(Logger &log, String filename = SETTINGS_FILENAME) + : logger(log), filename(filename) {} + // SimpleSettings(Logger &log, const char *filename = SETTINGS_FILENAME) + // : SimpleSettings(log, String(filename)) {} + void begin(); + bool loadSettings(AppSettings &settings, std::function getDefaultSettings); + bool saveSettings(const AppSettings &newsettings, AppSettings &settings); + static const char *SETTINGS_FILENAME; + + private: + Logger &logger; // Reference to the logger + String filename; +}; + +#endif // SETTINGS_H diff --git a/src/main.cpp b/src/main.cpp index fef511c..b94095a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,11 +1,12 @@ -#include -#include +#include "appsettings.h" #include "display.h" #include "ota.h" +#include "sensor.h" +#include "settings.h" +#include "simplewifi.h" +#include #include #include -#include -#include "simplewifi.h" #include // See include/example_config.h for configuration. Make sure you copy it @@ -14,25 +15,16 @@ Logger logger(Serial); OTA ota(logger); SimpleWiFi wifi(logger); +Settings settings(logger); +Sensor sht31(logger); Display display(logger, SCREEN_WIDTH, SCREEN_HEIGHT); -Adafruit_SHT31 sht31 = Adafruit_SHT31(); ESP8266WebServer server(HTTP_PORT); ESP8266HTTPUpdateServer httpUpdateServer; -float temperature; -float humidity; -unsigned long previousMillis = 0; // Store the last time a measurement was taken -const char *const SETTINGS_FILENAME = "/settings.bin"; +SensorData lastreading; +AppSettings appsettings; -struct Settings { - unsigned long updateInterval; - bool showFahrenheit; - float tempOffset; - float humidityOffset; - char deviceName[32]; -}; - -Settings settings; +unsigned long previousMillis = 0; // Store the last time a measurement was taken void setStatus(Logger::Level level, const String &status) { logger.log(level, status); @@ -45,34 +37,6 @@ void restart(const String &status) { ESP.restart(); } -bool loadSettings() { - File file = LittleFS.open(SETTINGS_FILENAME, "r"); - if (!file || file.size() < sizeof(Settings)) { - settings.updateInterval = UPDATEINTERVAL; - settings.tempOffset = TEMPERATUREOFFSET; - settings.humidityOffset = HUMIDITYOFFSET; - settings.showFahrenheit = SHOWFAHRENHEIT ? true : false; - strncpy(settings.deviceName, DEVICENAME, sizeof(settings.deviceName) - 1); - settings.deviceName[sizeof(settings.deviceName) - 1] = '\0'; // Ensure null termination - return false; // No settings file, use default settings - } - size_t bytesRead = file.read((uint8_t *)&settings, sizeof(Settings)); - file.close(); - return bytesRead == sizeof(Settings); -} - -bool saveSettings(const Settings &newsettings) { - File file = LittleFS.open(SETTINGS_FILENAME, "w"); - if (!file) { - Serial.println("Failed to open file for writing"); - return false; - } - size_t bytesWritten = file.write((uint8_t *)&newsettings, sizeof(Settings)); - file.close(); - settings = newsettings; - return bytesWritten == sizeof(Settings); -} - void serveFile(const char *path, const char *contentType = "text/html", int cacheTTL = 300) { File file = LittleFS.open(path, "r"); if (!file) { @@ -84,43 +48,37 @@ void serveFile(const char *path, const char *contentType = "text/html", int cach file.close(); } -float factorCtoF = 9.0 / 5.0; -float toFahrenheit(float celsius) { return (celsius * factorCtoF) + 32.0; } -float offsetToFahrenheit(float celsius) { return celsius * factorCtoF; } - void setup() { Serial.begin(SERIAL_BAUDRATE); - if (!LittleFS.begin()) { - setStatus(Logger::ERROR, "Failed to mount filesystem"); - } + settings.begin(); - if (loadSettings()) { - setStatus(Logger::INFO, "Settings loaded"); - } else { - setStatus(Logger::INFO, "Using default settings"); - } + settings.loadSettings( + appsettings, [](AppSettings &settings) { + settings.updateInterval = UPDATEINTERVAL; + settings.tempOffset = TEMPERATUREOFFSET; + settings.humidityOffset = HUMIDITYOFFSET; + settings.showFahrenheit = SHOWFAHRENHEIT ? true : false; + strncpy(settings.deviceName, DEVICENAME, sizeof(settings.deviceName) - 1); + settings.deviceName[sizeof(settings.deviceName) - 1] = '\0'; // Ensure null termination + }); - wifi.begin(PORTALTIMEOUT, WIFICONNECTTIMEOUT, WIFICONNECTRETRIES, settings.deviceName); + wifi.begin(PORTALTIMEOUT, WIFICONNECTTIMEOUT, WIFICONNECTRETRIES, appsettings.deviceName); - // Initialize GXHT30 - if (!sht31.begin(0x44)) { // 0x44 is the I2C address of GXHT30 - setStatus(Logger::ERROR, "Couldn't find GXHT30 sensor!"); - ESP.restart(); - } + sht31.begin(); // Initialize SHT31 sensor httpUpdateServer.setup(&server); server.on("/read", HTTP_GET, []() { JsonDocument root; JsonObject celsiusnode = root["celsius"].to(); - celsiusnode["temperature"] = temperature; - celsiusnode["offset"] = settings.tempOffset; + celsiusnode["temperature"] = lastreading.getTemperatureDisplayValue(false); + celsiusnode["offset"] = lastreading.offset_c; JsonObject fahrenheitnode = root["fahrenheit"].to(); - fahrenheitnode["temperature"] = toFahrenheit(temperature); - fahrenheitnode["offset"] = offsetToFahrenheit(settings.tempOffset); + fahrenheitnode["temperature"] = lastreading.getTemperatureDisplayValue(true); + fahrenheitnode["offset"] = lastreading.offset_f; JsonObject humiditynode = root["humidity"].to(); - humiditynode["relative_perc"] = humidity; - humiditynode["relative_perc_offset"] = settings.humidityOffset; + humiditynode["relative_perc"] = lastreading.getHumidityDisplayValue(); + humiditynode["relative_perc_offset"] = lastreading.humidity_offset; JsonObject wifinode = root["wifi"].to(); wifinode["rssi"] = WiFi.RSSI(); @@ -128,9 +86,9 @@ void setup() { snprintf(lastUpdateStr, sizeof(lastUpdateStr), "%.2f seconds ago", (millis() - previousMillis) / 1000.0); root["lastupdate"] = lastUpdateStr; - root["display"] = settings.showFahrenheit ? "f" : "c"; - root["devicename"] = settings.deviceName; - root["updateinterval"] = settings.updateInterval; + root["display"] = appsettings.showFahrenheit ? "f" : "c"; + root["devicename"] = appsettings.deviceName; + root["updateinterval"] = appsettings.updateInterval; String response; serializeJson(root, response); @@ -146,10 +104,11 @@ void setup() { server.on("/", HTTP_GET, []() { serveFile("/index.html"); }); server.on("/css", HTTP_GET, []() { serveFile("/main.css", "text/css"); }); server.on("/js", HTTP_GET, []() { serveFile("/app.js", "text/javascript"); }); - server.on("/favicon.ico", HTTP_GET, []() { serveFile("/favicon.svg", "image/svg+xml", 60 * 60 * 24); }); + server.on("/favicon.ico", HTTP_GET, + []() { serveFile("/favicon.svg", "image/svg+xml", 60 * 60 * 24); }); server.on("/settings", HTTP_GET, []() { serveFile("/settings.html"); }); server.on("/settings", HTTP_POST, []() { - Settings newsettings; + AppSettings newsettings; JsonDocument root; root["status"] = ""; JsonArray errors = root["errors"].to(); @@ -169,12 +128,13 @@ void setup() { } strncpy(newsettings.deviceName, server.arg("devicename").c_str(), sizeof(newsettings.deviceName) - 1); - newsettings.deviceName[sizeof(newsettings.deviceName) - 1] = '\0'; // Ensure null termination + newsettings.deviceName[sizeof(newsettings.deviceName) - 1] = + '\0'; // Ensure null termination if (strlen(newsettings.deviceName) == 0 || strlen(newsettings.deviceName) > 31) { errors.add("Device name must be between 1 and 31 characters"); } - if (errors.size() == 0 && saveSettings(newsettings)) { + if (errors.size() == 0 && settings.saveSettings(newsettings, appsettings)) { root["status"] = "success"; setStatus(Logger::INFO, "Settings saved"); } else { @@ -188,10 +148,10 @@ void setup() { server.onNotFound([]() { server.send(404, "text/plain", "File not found"); }); - Serial.println("Starting HTTP server"); + logger.info("Starting HTTP server"); server.begin(); - ota.begin(settings.deviceName, OTAPASSWORD); + ota.begin(appsettings.deviceName, OTAPASSWORD); setStatus(Logger::INFO, wifi.localIP()); delay(2000); @@ -205,18 +165,19 @@ void loop() { unsigned long currentMillis = millis(); // Get the current time // Check if it's time to take another measurement - if (currentMillis - previousMillis >= settings.updateInterval) { + if (currentMillis - previousMillis >= appsettings.updateInterval) { previousMillis = currentMillis; - temperature = sht31.readTemperature() + settings.tempOffset; - humidity = constrain(sht31.readHumidity() + settings.humidityOffset, 0, 100); - Serial.printf("Temperature: %.2f C, %.2f F, humidity: %.2f %%RH\n", temperature, - toFahrenheit(temperature), humidity); + SensorData reading = sht31.readData(appsettings.tempOffset, appsettings.humidityOffset); + + char buffer[64]; + snprintf(buffer, sizeof(buffer), "Temperature: %s, humidity: %s", + reading.getTemperatureDisplay(appsettings.showFahrenheit).c_str(), + reading.getHumidityDisplay().c_str()); + logger.info(String(buffer)); + + display.showMeasurements(reading, appsettings.showFahrenheit); - display.showMeasurements( - settings.showFahrenheit ? toFahrenheit(temperature) : temperature, - settings.showFahrenheit, - humidity - ); + lastreading = reading; // Store the last reading } } \ No newline at end of file From f2ee3fa33d233a50ae7e7b2b0f6b09538e972a74 Mon Sep 17 00:00:00 2001 From: RobThree Date: Wed, 9 Apr 2025 05:19:12 +0200 Subject: [PATCH 03/11] Format documents --- .clang-format | 2 +- lib/Display/display.cpp | 5 ++--- lib/Display/display.h | 11 ++++++----- lib/Logger/logger.cpp | 33 ++++++++++++++++++--------------- lib/Logger/logger.h | 23 +++++++++++------------ lib/OTA/ota.cpp | 13 ++++--------- lib/OTA/ota.h | 9 +++++---- lib/Sensor/sensor.cpp | 5 ++--- lib/Sensor/sensor.h | 11 ++++++----- lib/Sensor/sensordata.h | 4 +--- lib/SimpleSettings/settings.cpp | 8 +++----- lib/SimpleSettings/settings.h | 5 ++--- lib/SimpleWiFi/simplewifi.cpp | 12 ++++-------- lib/SimpleWiFi/simplewifi.h | 12 +++++++----- src/main.cpp | 15 +++++---------- 15 files changed, 77 insertions(+), 91 deletions(-) diff --git a/.clang-format b/.clang-format index 681f132..f1d6945 100644 --- a/.clang-format +++ b/.clang-format @@ -1,4 +1,4 @@ BasedOnStyle: LLVM IndentWidth: 4 -ColumnLimit: 100 +ColumnLimit: 120 BreakBeforeBraces: Attach diff --git a/lib/Display/display.cpp b/lib/Display/display.cpp index 2f11f7a..ab431d7 100644 --- a/lib/Display/display.cpp +++ b/lib/Display/display.cpp @@ -2,8 +2,7 @@ #include "sensordata.h" #include -Display::Display(Logger &log, uint8_t w, uint8_t h) - : logger(log), width(w), height(h), display(w, h, &Wire, -1) {} +Display::Display(Logger &log, uint8_t w, uint8_t h) : logger(log), width(w), height(h), display(w, h, &Wire, -1) {} void Display::begin() { Wire.begin(); @@ -22,7 +21,7 @@ void Display::begin() { void Display::showMeasurements(SensorData &data, bool showFahrenheit) { display.clearDisplay(); - + display.setCursor(0, 0); display.print("Temp: "); display.print(data.getTemperatureDisplay(showFahrenheit)); diff --git a/lib/Display/display.h b/lib/Display/display.h index ab1746b..74f88e9 100644 --- a/lib/Display/display.h +++ b/lib/Display/display.h @@ -1,18 +1,19 @@ #ifndef DISPLAY_H #define DISPLAY_H -#include -#include "sensordata.h" #include "../Logger/logger.h" +#include "sensordata.h" +#include class Display { -public: + public: Display(Logger &log, uint8_t width, uint8_t height); void begin(); void setStatus(const String &status); void showMeasurements(SensorData &data, bool showFahrenheit); -private: - Logger &logger; // Reference to the logger + + private: + Logger &logger; // Reference to the logger uint8_t width; uint8_t height; Adafruit_SSD1306 display; diff --git a/lib/Logger/logger.cpp b/lib/Logger/logger.cpp index bfd0bc9..f4ff150 100644 --- a/lib/Logger/logger.cpp +++ b/lib/Logger/logger.cpp @@ -2,11 +2,9 @@ Logger::Logger(Stream &output) : _output(output), _logLevel(INFO) {} -void Logger::setLogLevel(Level level) { - _logLevel = level; -} +void Logger::setLogLevel(Level level) { _logLevel = level; } -void Logger::log(Level level, const char* message) { +void Logger::log(Level level, const char *message) { if (level >= _logLevel) { _output.print("["); _output.print(levelToString(level)); @@ -16,25 +14,30 @@ void Logger::log(Level level, const char* message) { } void Logger::log(Level level, const String &message) { - log(level, message.c_str()); // Convert String to const char* + log(level, message.c_str()); // Convert String to const char* } -void Logger::debug(const char* message) { log(DEBUG, message); } -void Logger::info(const char* message) { log(INFO, message); } -void Logger::warn(const char* message) { log(WARN, message); } -void Logger::error(const char* message) { log(ERROR, message); } +void Logger::debug(const char *message) { log(DEBUG, message); } +void Logger::info(const char *message) { log(INFO, message); } +void Logger::warn(const char *message) { log(WARN, message); } +void Logger::error(const char *message) { log(ERROR, message); } void Logger::debug(const String &message) { log(DEBUG, message); } void Logger::info(const String &message) { log(INFO, message); } void Logger::warn(const String &message) { log(WARN, message); } void Logger::error(const String &message) { log(ERROR, message); } -const char* Logger::levelToString(Level level) { +const char *Logger::levelToString(Level level) { switch (level) { - case DEBUG: return "DEBUG"; - case INFO: return "INFO"; - case WARN: return "WARN"; - case ERROR: return "ERROR"; - default: return "UNKNOWN"; + case DEBUG: + return "DEBUG"; + case INFO: + return "INFO"; + case WARN: + return "WARN"; + case ERROR: + return "ERROR"; + default: + return "UNKNOWN"; } } diff --git a/lib/Logger/logger.h b/lib/Logger/logger.h index 72cf811..e9dba64 100644 --- a/lib/Logger/logger.h +++ b/lib/Logger/logger.h @@ -1,33 +1,32 @@ #ifndef LOGGER_H #define LOGGER_H -#include // Required for Serial and String handling +#include // Required for Serial and String handling class Logger { -public: + public: enum Level { DEBUG, INFO, WARN, ERROR }; - Logger(Stream &output); // Constructor + Logger(Stream &output); // Constructor void setLogLevel(Level level); - void log(Level level, const char* message); + void log(Level level, const char *message); void log(Level level, const String &message); - - void debug(const char* message); - void info(const char* message); - void warn(const char* message); - void error(const char* message); + + void debug(const char *message); + void info(const char *message); + void warn(const char *message); + void error(const char *message); void debug(const String &message); void info(const String &message); void warn(const String &message); void error(const String &message); - -private: + private: Stream &_output; Level _logLevel; - const char* levelToString(Level level); + const char *levelToString(Level level); }; #endif // LOGGER_H diff --git a/lib/OTA/ota.cpp b/lib/OTA/ota.cpp index d3ecd08..db3f34b 100644 --- a/lib/OTA/ota.cpp +++ b/lib/OTA/ota.cpp @@ -1,8 +1,7 @@ -#include #include "ota.h" +#include -OTA::OTA(Logger &log) - : logger(log) {} +OTA::OTA(Logger &log) : logger(log) {} void OTA::begin(const char *const hostname, const char *const password) { ArduinoOTA.setPassword(hostname); @@ -11,9 +10,7 @@ void OTA::begin(const char *const hostname, const char *const password) { String type = (ArduinoOTA.getCommand() == U_FLASH) ? "sketch" : "filesystem"; logger.info(("Start updating " + type).c_str()); }); - ArduinoOTA.onEnd([this]() { - logger.info("Update Complete"); - }); + ArduinoOTA.onEnd([this]() { logger.info("Update Complete"); }); ArduinoOTA.onProgress([this](unsigned int progress, unsigned int total) { logger.info(("Progress: " + String((progress * 100) / total) + "%").c_str()); }); @@ -36,6 +33,4 @@ void OTA::begin(const char *const hostname, const char *const password) { ArduinoOTA.begin(); } -void OTA::handle() { - ArduinoOTA.handle(); -} \ No newline at end of file +void OTA::handle() { ArduinoOTA.handle(); } \ No newline at end of file diff --git a/lib/OTA/ota.h b/lib/OTA/ota.h index 87b77b9..ef2efc4 100644 --- a/lib/OTA/ota.h +++ b/lib/OTA/ota.h @@ -1,16 +1,17 @@ #ifndef OTA_H #define OTA_H -#include #include "../Logger/logger.h" +#include class OTA { -public: + public: OTA(Logger &log); void begin(const char *const hostname, const char *const password); void handle(); -private: - Logger &logger; // Reference to the logger + + private: + Logger &logger; // Reference to the logger }; #endif // OTA_H diff --git a/lib/Sensor/sensor.cpp b/lib/Sensor/sensor.cpp index 5fb0bc2..4c79490 100644 --- a/lib/Sensor/sensor.cpp +++ b/lib/Sensor/sensor.cpp @@ -1,13 +1,12 @@ #include "sensor.h" -Sensor::Sensor(Logger &log) - : logger(log) {} +Sensor::Sensor(Logger &log) : logger(log) {} void Sensor::begin(uint8_t i2cAddress) { if (!sht31.begin(i2cAddress)) { logger.error("Couldn't find GXHT30 sensor!"); ESP.restart(); - } + } } SensorData Sensor::readData(float tempOffset, float humidityOffset) { diff --git a/lib/Sensor/sensor.h b/lib/Sensor/sensor.h index d35b141..a709026 100644 --- a/lib/Sensor/sensor.h +++ b/lib/Sensor/sensor.h @@ -1,17 +1,18 @@ #ifndef SENSOR_H #define SENSOR_H -#include -#include "sensordata.h" #include "../Logger/logger.h" +#include "sensordata.h" +#include class Sensor { -public: + public: Sensor(Logger &log); void begin(uint8_t i2cAddress = 0x44); // Default I2C address for SHT31 SensorData readData(float tempOffset, float humidityOffset); -private: - Logger &logger; // Reference to the logger + + private: + Logger &logger; // Reference to the logger Adafruit_SHT31 sht31 = Adafruit_SHT31(); }; diff --git a/lib/Sensor/sensordata.h b/lib/Sensor/sensordata.h index 86400cc..22c9a90 100644 --- a/lib/Sensor/sensordata.h +++ b/lib/Sensor/sensordata.h @@ -12,9 +12,7 @@ struct SensorData { float humidity_offset; static constexpr float factorCtoF = 9.0 / 5.0; - static float toFahrenheit(float celsius) { - return std::isnan(celsius) ? NAN : (celsius * factorCtoF) + 32.0; - } + static float toFahrenheit(float celsius) { return std::isnan(celsius) ? NAN : (celsius * factorCtoF) + 32.0; } static float offsetToFahrenheit(float celsius) { return celsius * factorCtoF; } SensorData(float temp_c = NAN, float offset_c = 0, float hum = NAN, float hum_offset = 0) diff --git a/lib/SimpleSettings/settings.cpp b/lib/SimpleSettings/settings.cpp index 6f103f6..23de1e4 100644 --- a/lib/SimpleSettings/settings.cpp +++ b/lib/SimpleSettings/settings.cpp @@ -1,13 +1,11 @@ -#include "appsettings.h" #include "settings.h" +#include "appsettings.h" const char *Settings::SETTINGS_FILENAME = "/settings.bin"; -void Settings::begin() { - LittleFS.begin(); -} +void Settings::begin() { LittleFS.begin(); } -bool Settings::loadSettings(AppSettings &settings, std::function getDefaultSettings) { +bool Settings::loadSettings(AppSettings &settings, std::function getDefaultSettings) { File file = LittleFS.open(filename, "r"); if (!file || file.size() < sizeof(AppSettings)) { getDefaultSettings(settings); diff --git a/lib/SimpleSettings/settings.h b/lib/SimpleSettings/settings.h index 9919cdb..48062a9 100644 --- a/lib/SimpleSettings/settings.h +++ b/lib/SimpleSettings/settings.h @@ -7,12 +7,11 @@ class Settings { public: - Settings(Logger &log, String filename = SETTINGS_FILENAME) - : logger(log), filename(filename) {} + Settings(Logger &log, String filename = SETTINGS_FILENAME) : logger(log), filename(filename) {} // SimpleSettings(Logger &log, const char *filename = SETTINGS_FILENAME) // : SimpleSettings(log, String(filename)) {} void begin(); - bool loadSettings(AppSettings &settings, std::function getDefaultSettings); + bool loadSettings(AppSettings &settings, std::function getDefaultSettings); bool saveSettings(const AppSettings &newsettings, AppSettings &settings); static const char *SETTINGS_FILENAME; diff --git a/lib/SimpleWiFi/simplewifi.cpp b/lib/SimpleWiFi/simplewifi.cpp index f29e80d..d921584 100644 --- a/lib/SimpleWiFi/simplewifi.cpp +++ b/lib/SimpleWiFi/simplewifi.cpp @@ -1,11 +1,9 @@ #include "simplewifi.h" -SimpleWiFi::SimpleWiFi(Logger &log) - : logger(log) {} +SimpleWiFi::SimpleWiFi(Logger &log) : logger(log) {} -void SimpleWiFi::begin(unsigned long portalTimeout, unsigned long wifiConnectTimeout, - unsigned long wifiConnectRetries, const char *apName, - const char *apPassword) { +void SimpleWiFi::begin(unsigned long portalTimeout, unsigned long wifiConnectTimeout, unsigned long wifiConnectRetries, + const char *apName, const char *apPassword) { logger.info("Starting WiFi"); wifiManager.setConfigPortalTimeout(portalTimeout); wifiManager.setConnectTimeout(wifiConnectTimeout); @@ -37,6 +35,4 @@ void SimpleWiFi::ensureConnected() { } } -String SimpleWiFi::localIP() { - return WiFi.localIP().toString(); -} \ No newline at end of file +String SimpleWiFi::localIP() { return WiFi.localIP().toString(); } \ No newline at end of file diff --git a/lib/SimpleWiFi/simplewifi.h b/lib/SimpleWiFi/simplewifi.h index 18afd5a..099de6e 100644 --- a/lib/SimpleWiFi/simplewifi.h +++ b/lib/SimpleWiFi/simplewifi.h @@ -1,17 +1,19 @@ #ifndef SIMPLEWIFI_H #define SIMPLEWIFI_H -#include #include "../Logger/logger.h" +#include class SimpleWiFi { -public: + public: SimpleWiFi(Logger &log); - void begin(unsigned long portalTimeout, unsigned long wifiConnectTimeout, unsigned long wifiConnectRetries, const char *apName, const char *apPassword = (const char *)__null); + void begin(unsigned long portalTimeout, unsigned long wifiConnectTimeout, unsigned long wifiConnectRetries, + const char *apName, const char *apPassword = (const char *)__null); void ensureConnected(); String localIP(); -private: - Logger &logger; // Reference to the logger + + private: + Logger &logger; // Reference to the logger WiFiManager wifiManager; }; diff --git a/src/main.cpp b/src/main.cpp index b94095a..018fa93 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -53,8 +53,7 @@ void setup() { settings.begin(); - settings.loadSettings( - appsettings, [](AppSettings &settings) { + settings.loadSettings(appsettings, [](AppSettings &settings) { settings.updateInterval = UPDATEINTERVAL; settings.tempOffset = TEMPERATUREOFFSET; settings.humidityOffset = HUMIDITYOFFSET; @@ -83,8 +82,7 @@ void setup() { wifinode["rssi"] = WiFi.RSSI(); char lastUpdateStr[32]; - snprintf(lastUpdateStr, sizeof(lastUpdateStr), "%.2f seconds ago", - (millis() - previousMillis) / 1000.0); + snprintf(lastUpdateStr, sizeof(lastUpdateStr), "%.2f seconds ago", (millis() - previousMillis) / 1000.0); root["lastupdate"] = lastUpdateStr; root["display"] = appsettings.showFahrenheit ? "f" : "c"; root["devicename"] = appsettings.deviceName; @@ -104,8 +102,7 @@ void setup() { server.on("/", HTTP_GET, []() { serveFile("/index.html"); }); server.on("/css", HTTP_GET, []() { serveFile("/main.css", "text/css"); }); server.on("/js", HTTP_GET, []() { serveFile("/app.js", "text/javascript"); }); - server.on("/favicon.ico", HTTP_GET, - []() { serveFile("/favicon.svg", "image/svg+xml", 60 * 60 * 24); }); + server.on("/favicon.ico", HTTP_GET, []() { serveFile("/favicon.svg", "image/svg+xml", 60 * 60 * 24); }); server.on("/settings", HTTP_GET, []() { serveFile("/settings.html"); }); server.on("/settings", HTTP_POST, []() { AppSettings newsettings; @@ -126,10 +123,8 @@ void setup() { if (newsettings.humidityOffset < -50 || newsettings.humidityOffset > 50) { errors.add("Humidity offset must be between -50 and 50"); } - strncpy(newsettings.deviceName, server.arg("devicename").c_str(), - sizeof(newsettings.deviceName) - 1); - newsettings.deviceName[sizeof(newsettings.deviceName) - 1] = - '\0'; // Ensure null termination + strncpy(newsettings.deviceName, server.arg("devicename").c_str(), sizeof(newsettings.deviceName) - 1); + newsettings.deviceName[sizeof(newsettings.deviceName) - 1] = '\0'; // Ensure null termination if (strlen(newsettings.deviceName) == 0 || strlen(newsettings.deviceName) > 31) { errors.add("Device name must be between 1 and 31 characters"); } From f5c8d202d17e236c3abc6e31c441499e537d0d66 Mon Sep 17 00:00:00 2001 From: RobThree Date: Wed, 9 Apr 2025 06:48:43 +0200 Subject: [PATCH 04/11] More cleanup --- .../appsettings.h | 0 lib/{SimpleSettings => Settings}/settings.cpp | 0 lib/{SimpleSettings => Settings}/settings.h | 0 lib/Webserver/webserver.cpp | 29 +++ lib/Webserver/webserver.h | 36 ++++ src/main.cpp | 189 ++++++++---------- 6 files changed, 144 insertions(+), 110 deletions(-) rename lib/{SimpleSettings => Settings}/appsettings.h (100%) rename lib/{SimpleSettings => Settings}/settings.cpp (100%) rename lib/{SimpleSettings => Settings}/settings.h (100%) create mode 100644 lib/Webserver/webserver.cpp create mode 100644 lib/Webserver/webserver.h diff --git a/lib/SimpleSettings/appsettings.h b/lib/Settings/appsettings.h similarity index 100% rename from lib/SimpleSettings/appsettings.h rename to lib/Settings/appsettings.h diff --git a/lib/SimpleSettings/settings.cpp b/lib/Settings/settings.cpp similarity index 100% rename from lib/SimpleSettings/settings.cpp rename to lib/Settings/settings.cpp diff --git a/lib/SimpleSettings/settings.h b/lib/Settings/settings.h similarity index 100% rename from lib/SimpleSettings/settings.h rename to lib/Settings/settings.h diff --git a/lib/Webserver/webserver.cpp b/lib/Webserver/webserver.cpp new file mode 100644 index 0000000..0d9ffa9 --- /dev/null +++ b/lib/Webserver/webserver.cpp @@ -0,0 +1,29 @@ +#include "webserver.h" + +void Webserver::useDefaultEndpoints() { + server.on("/reset", HTTP_PUT, [this]() { + server.send(200, "text/html", "reset"); + logger.warn("Restarting"); + ESP.restart(); + }); +} + +void Webserver::serveStatic(const char *uri, const char *path, const char *cacheheader) { + server.serveStatic(uri, fs, path, cacheheader); +} + +void Webserver::begin() { + fs.begin(); + server.begin(); + logger.info("HTTP server started"); +} + +void Webserver::handleClient() { server.handleClient(); } + +void Webserver::sendJson(const JsonDocument &json, int httpCode) { + String response; + serializeJson(json, response); + server.send(httpCode, "application/json", response); +} + +const String &Webserver::arg(const String &name) const { return server.arg(name); } \ No newline at end of file diff --git a/lib/Webserver/webserver.h b/lib/Webserver/webserver.h new file mode 100644 index 0000000..aaf5090 --- /dev/null +++ b/lib/Webserver/webserver.h @@ -0,0 +1,36 @@ +#ifndef WEBSERVER_H +#define WEBSERVER_H + +#include "../Logger/logger.h" +#include +#include +#include +#include + +class Webserver { + public: + Webserver(Logger &log, FS fs, int port = 80) : logger(log), fs(fs), server(port) { + httpUpdateServer.setup(&server); + server.onNotFound([this]() { server.send(404, "text/plain", "File not found"); }); + }; + void useDefaultEndpoints(); + void begin(); + void handleClient(); + void sendJson(const JsonDocument &json, int httpCode = 200); + const String &arg(const String &name) const; + void serveStatic(const char *, const char *path, const char *cacheheader = "max-age=300"); + void on(const String &uri, ESP8266WebServer::THandlerFunction fn) { server.on(uri, fn); } + void on(const String &uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn) { server.on(uri, method, fn); } + void on(const String &uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn, + ESP8266WebServer::THandlerFunction ufn) { + server.on(uri, method, fn, ufn); + } + + private: + Logger &logger; // Reference to the logger + FS fs; + ESP8266WebServer server; + ESP8266HTTPUpdateServer httpUpdateServer; +}; + +#endif // WEBSERVER_H diff --git a/src/main.cpp b/src/main.cpp index 018fa93..8b5ac70 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,9 +4,7 @@ #include "sensor.h" #include "settings.h" #include "simplewifi.h" -#include -#include -#include +#include "webserver.h" #include // See include/example_config.h for configuration. Make sure you copy it @@ -16,37 +14,17 @@ Logger logger(Serial); OTA ota(logger); SimpleWiFi wifi(logger); Settings settings(logger); -Sensor sht31(logger); +Sensor sensor(logger); Display display(logger, SCREEN_WIDTH, SCREEN_HEIGHT); -ESP8266WebServer server(HTTP_PORT); -ESP8266HTTPUpdateServer httpUpdateServer; - +Webserver server(logger, LittleFS, HTTP_PORT); SensorData lastreading; AppSettings appsettings; unsigned long previousMillis = 0; // Store the last time a measurement was taken -void setStatus(Logger::Level level, const String &status) { - logger.log(level, status); - display.setStatus(status); -} - -void restart(const String &status) { - setStatus(Logger::WARN, status); - delay(3000); - ESP.restart(); -} - -void serveFile(const char *path, const char *contentType = "text/html", int cacheTTL = 300) { - File file = LittleFS.open(path, "r"); - if (!file) { - server.send(404, "text/plain", "File not found"); - return; - } - server.sendHeader("Cache-Control", "max-age=" + String(cacheTTL)); - server.streamFile(file, contentType); - file.close(); -} +void doReading(); +void saveSettings(); +void setStatus(Logger::Level level, const String &status); void setup() { Serial.begin(SERIAL_BAUDRATE); @@ -63,87 +41,16 @@ void setup() { }); wifi.begin(PORTALTIMEOUT, WIFICONNECTTIMEOUT, WIFICONNECTRETRIES, appsettings.deviceName); - - sht31.begin(); // Initialize SHT31 sensor - - httpUpdateServer.setup(&server); - server.on("/read", HTTP_GET, []() { - JsonDocument root; - JsonObject celsiusnode = root["celsius"].to(); - celsiusnode["temperature"] = lastreading.getTemperatureDisplayValue(false); - celsiusnode["offset"] = lastreading.offset_c; - JsonObject fahrenheitnode = root["fahrenheit"].to(); - fahrenheitnode["temperature"] = lastreading.getTemperatureDisplayValue(true); - fahrenheitnode["offset"] = lastreading.offset_f; - JsonObject humiditynode = root["humidity"].to(); - humiditynode["relative_perc"] = lastreading.getHumidityDisplayValue(); - humiditynode["relative_perc_offset"] = lastreading.humidity_offset; - JsonObject wifinode = root["wifi"].to(); - wifinode["rssi"] = WiFi.RSSI(); - - char lastUpdateStr[32]; - snprintf(lastUpdateStr, sizeof(lastUpdateStr), "%.2f seconds ago", (millis() - previousMillis) / 1000.0); - root["lastupdate"] = lastUpdateStr; - root["display"] = appsettings.showFahrenheit ? "f" : "c"; - root["devicename"] = appsettings.deviceName; - root["updateinterval"] = appsettings.updateInterval; - - String response; - serializeJson(root, response); - - server.send(200, "application/json", response); - }); - - server.on("/reset", HTTP_PUT, []() { - server.send(200, "text/html", "reset"); - restart("Restarting"); - }); - - server.on("/", HTTP_GET, []() { serveFile("/index.html"); }); - server.on("/css", HTTP_GET, []() { serveFile("/main.css", "text/css"); }); - server.on("/js", HTTP_GET, []() { serveFile("/app.js", "text/javascript"); }); - server.on("/favicon.ico", HTTP_GET, []() { serveFile("/favicon.svg", "image/svg+xml", 60 * 60 * 24); }); - server.on("/settings", HTTP_GET, []() { serveFile("/settings.html"); }); - server.on("/settings", HTTP_POST, []() { - AppSettings newsettings; - JsonDocument root; - root["status"] = ""; - JsonArray errors = root["errors"].to(); - newsettings.updateInterval = server.arg("updateinterval").toInt(); - if (newsettings.updateInterval < 1000 || newsettings.updateInterval > 60000) { - errors.add("Update interval must be between 1000 and 60000 ms"); - } - newsettings.showFahrenheit = server.arg("showfahrenheit") == "f"; - - newsettings.tempOffset = server.arg("tempoffset").toFloat(); - if (newsettings.tempOffset < -50 || newsettings.tempOffset > 50) { - errors.add("Temperature offset must be between -50 and 50"); - } - newsettings.humidityOffset = server.arg("humidityoffset").toFloat(); - if (newsettings.humidityOffset < -50 || newsettings.humidityOffset > 50) { - errors.add("Humidity offset must be between -50 and 50"); - } - strncpy(newsettings.deviceName, server.arg("devicename").c_str(), sizeof(newsettings.deviceName) - 1); - newsettings.deviceName[sizeof(newsettings.deviceName) - 1] = '\0'; // Ensure null termination - if (strlen(newsettings.deviceName) == 0 || strlen(newsettings.deviceName) > 31) { - errors.add("Device name must be between 1 and 31 characters"); - } - - if (errors.size() == 0 && settings.saveSettings(newsettings, appsettings)) { - root["status"] = "success"; - setStatus(Logger::INFO, "Settings saved"); - } else { - root["status"] = "error"; - } - String response; - serializeJson(root, response); - - server.send(200, "application/json", response); - }); - - server.onNotFound([]() { server.send(404, "text/plain", "File not found"); }); - - logger.info("Starting HTTP server"); + sensor.begin(); + + server.serveStatic("/", "/index.html"); + server.serveStatic("/css", "/main.css"); + server.serveStatic("/js", "/app.js"); + server.serveStatic("/favicon.ico", "/favicon.svg"); + server.serveStatic("/settings", "/settings.html"); + server.on("/read", HTTP_GET, doReading); + server.on("/settings", HTTP_POST, saveSettings); + server.useDefaultEndpoints(); server.begin(); ota.begin(appsettings.deviceName, OTAPASSWORD); @@ -163,7 +70,7 @@ void loop() { if (currentMillis - previousMillis >= appsettings.updateInterval) { previousMillis = currentMillis; - SensorData reading = sht31.readData(appsettings.tempOffset, appsettings.humidityOffset); + SensorData reading = sensor.readData(appsettings.tempOffset, appsettings.humidityOffset); char buffer[64]; snprintf(buffer, sizeof(buffer), "Temperature: %s, humidity: %s", @@ -175,4 +82,66 @@ void loop() { lastreading = reading; // Store the last reading } +} + +void doReading() { + JsonDocument response; + JsonObject celsiusnode = response["celsius"].to(); + celsiusnode["temperature"] = lastreading.getTemperatureDisplayValue(false); + celsiusnode["offset"] = lastreading.offset_c; + JsonObject fahrenheitnode = response["fahrenheit"].to(); + fahrenheitnode["temperature"] = lastreading.getTemperatureDisplayValue(true); + fahrenheitnode["offset"] = lastreading.offset_f; + JsonObject humiditynode = response["humidity"].to(); + humiditynode["relative_perc"] = lastreading.getHumidityDisplayValue(); + humiditynode["relative_perc_offset"] = lastreading.humidity_offset; + JsonObject wifinode = response["wifi"].to(); + wifinode["rssi"] = WiFi.RSSI(); + + char lastUpdateStr[32]; + snprintf(lastUpdateStr, sizeof(lastUpdateStr), "%.2f seconds ago", (millis() - previousMillis) / 1000.0); + response["lastupdate"] = lastUpdateStr; + response["display"] = appsettings.showFahrenheit ? "f" : "c"; + response["devicename"] = appsettings.deviceName; + response["updateinterval"] = appsettings.updateInterval; + server.sendJson(response); +} + +void saveSettings() { + AppSettings newsettings; + JsonDocument response; + response["status"] = ""; + JsonArray errors = response["errors"].to(); + newsettings.updateInterval = server.arg("updateinterval").toInt(); + if (newsettings.updateInterval < 1000 || newsettings.updateInterval > 60000) { + errors.add("Update interval must be between 1000 and 60000 ms"); + } + newsettings.showFahrenheit = server.arg("showfahrenheit") == "f"; + + newsettings.tempOffset = server.arg("tempoffset").toFloat(); + if (newsettings.tempOffset < -50 || newsettings.tempOffset > 50) { + errors.add("Temperature offset must be between -50 and 50"); + } + newsettings.humidityOffset = server.arg("humidityoffset").toFloat(); + if (newsettings.humidityOffset < -50 || newsettings.humidityOffset > 50) { + errors.add("Humidity offset must be between -50 and 50"); + } + strncpy(newsettings.deviceName, server.arg("devicename").c_str(), sizeof(newsettings.deviceName) - 1); + newsettings.deviceName[sizeof(newsettings.deviceName) - 1] = '\0'; // Ensure null termination + if (strlen(newsettings.deviceName) == 0 || strlen(newsettings.deviceName) > 31) { + errors.add("Device name must be between 1 and 31 characters"); + } + + if (errors.size() == 0 && settings.saveSettings(newsettings, appsettings)) { + response["status"] = "success"; + setStatus(Logger::INFO, "Settings saved"); + } else { + response["status"] = "error"; + } + server.sendJson(response); +} + +void setStatus(Logger::Level level, const String &status) { + logger.log(level, status); + display.setStatus(status); } \ No newline at end of file From f4f5c12b1022e072ffbddfce653ed298f1831378 Mon Sep 17 00:00:00 2001 From: RobThree Date: Wed, 9 Apr 2025 07:14:30 +0200 Subject: [PATCH 05/11] Proper naming for private variables --- lib/Display/display.cpp | 40 ++++++++++++++++++----------------- lib/Display/display.h | 8 +++---- lib/OTA/ota.cpp | 22 +++++++++---------- lib/OTA/ota.h | 2 +- lib/Sensor/sensor.cpp | 10 ++++----- lib/Sensor/sensor.h | 4 ++-- lib/Settings/settings.cpp | 12 +++++------ lib/Settings/settings.h | 6 +++--- lib/SimpleWiFi/simplewifi.cpp | 22 +++++++++---------- lib/SimpleWiFi/simplewifi.h | 4 ++-- lib/Webserver/webserver.cpp | 20 +++++++++--------- lib/Webserver/webserver.h | 22 ++++++++++--------- src/main.cpp | 1 + 13 files changed, 89 insertions(+), 84 deletions(-) diff --git a/lib/Display/display.cpp b/lib/Display/display.cpp index ab431d7..221dc69 100644 --- a/lib/Display/display.cpp +++ b/lib/Display/display.cpp @@ -2,40 +2,42 @@ #include "sensordata.h" #include -Display::Display(Logger &log, uint8_t w, uint8_t h) : logger(log), width(w), height(h), display(w, h, &Wire, -1) {} +Display::Display(Logger &log, uint8_t w, uint8_t h) : _logger(log), _width(w), _height(h), _display(w, h, &Wire, -1) {} void Display::begin() { Wire.begin(); // Initialize SSD1306 display - if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // 0x3C is the I2C address of SSD1306 - logger.error("SSD1306 allocation failed"); + if (!_display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // 0x3C is the I2C address of SSD1306 + _logger.error("SSD1306 allocation failed"); ESP.restart(); } - display.clearDisplay(); - display.setRotation(2); - display.setTextSize(2); - display.setTextColor(SSD1306_WHITE); + _display.clearDisplay(); + _display.setRotation(2); + _display.setTextSize(2); + _display.setTextColor(SSD1306_WHITE); } void Display::showMeasurements(SensorData &data, bool showFahrenheit) { - display.clearDisplay(); + _display.clearDisplay(); - display.setCursor(0, 0); - display.print("Temp: "); - display.print(data.getTemperatureDisplay(showFahrenheit)); + _display.setCursor(0, 0); + _display.print("Temp: "); + _display.setCursor(15, 15); + _display.print(data.getTemperatureDisplay(showFahrenheit)); - display.setCursor(0, 16); - display.print("Humidity: "); - display.print(data.getHumidityDisplay()); + _display.setCursor(0, 30); + _display.print("Humidity: "); + _display.setCursor(15, 45); + _display.print(data.getHumidityDisplay()); - display.display(); + _display.display(); } void Display::setStatus(const String &status) { - display.clearDisplay(); - display.setCursor(0, 0); - display.print(status); - display.display(); + _display.clearDisplay(); + _display.setCursor(0, 0); + _display.print(status); + _display.display(); } \ No newline at end of file diff --git a/lib/Display/display.h b/lib/Display/display.h index 74f88e9..dee8a9e 100644 --- a/lib/Display/display.h +++ b/lib/Display/display.h @@ -13,10 +13,10 @@ class Display { void showMeasurements(SensorData &data, bool showFahrenheit); private: - Logger &logger; // Reference to the logger - uint8_t width; - uint8_t height; - Adafruit_SSD1306 display; + Logger &_logger; // Reference to the logger + uint8_t _width; + uint8_t _height; + Adafruit_SSD1306 _display; }; #endif // DISPLAY_H diff --git a/lib/OTA/ota.cpp b/lib/OTA/ota.cpp index db3f34b..55e5395 100644 --- a/lib/OTA/ota.cpp +++ b/lib/OTA/ota.cpp @@ -1,35 +1,35 @@ #include "ota.h" #include -OTA::OTA(Logger &log) : logger(log) {} +OTA::OTA(Logger &log) : _logger(log) {} void OTA::begin(const char *const hostname, const char *const password) { ArduinoOTA.setPassword(hostname); ArduinoOTA.setHostname(password); ArduinoOTA.onStart([this]() { String type = (ArduinoOTA.getCommand() == U_FLASH) ? "sketch" : "filesystem"; - logger.info(("Start updating " + type).c_str()); + _logger.info(("Start updating " + type).c_str()); }); - ArduinoOTA.onEnd([this]() { logger.info("Update Complete"); }); + ArduinoOTA.onEnd([this]() { _logger.info("Update Complete"); }); ArduinoOTA.onProgress([this](unsigned int progress, unsigned int total) { - logger.info(("Progress: " + String((progress * 100) / total) + "%").c_str()); + _logger.info(("Progress: " + String((progress * 100) / total) + "%").c_str()); }); ArduinoOTA.onError([this](ota_error_t error) { - logger.error(("Error: " + String(error)).c_str()); + _logger.error(("Error: " + String(error)).c_str()); if (error == OTA_AUTH_ERROR) { - logger.error("Auth Failed"); + _logger.error("Auth Failed"); } else if (error == OTA_BEGIN_ERROR) { - logger.error("Begin Failed"); + _logger.error("Begin Failed"); } else if (error == OTA_CONNECT_ERROR) { - logger.error("Connect Failed"); + _logger.error("Connect Failed"); } else if (error == OTA_RECEIVE_ERROR) { - logger.error("Receive Failed"); + _logger.error("Receive Failed"); } else if (error == OTA_END_ERROR) { - logger.error("End Failed"); + _logger.error("End Failed"); } }); - logger.info("Starting OTA server"); + _logger.info("Starting OTA server"); ArduinoOTA.begin(); } diff --git a/lib/OTA/ota.h b/lib/OTA/ota.h index ef2efc4..14ca704 100644 --- a/lib/OTA/ota.h +++ b/lib/OTA/ota.h @@ -11,7 +11,7 @@ class OTA { void handle(); private: - Logger &logger; // Reference to the logger + Logger &_logger; // Reference to the logger }; #endif // OTA_H diff --git a/lib/Sensor/sensor.cpp b/lib/Sensor/sensor.cpp index 4c79490..5aa56f4 100644 --- a/lib/Sensor/sensor.cpp +++ b/lib/Sensor/sensor.cpp @@ -1,18 +1,18 @@ #include "sensor.h" -Sensor::Sensor(Logger &log) : logger(log) {} +Sensor::Sensor(Logger &log) : _logger(log) {} void Sensor::begin(uint8_t i2cAddress) { - if (!sht31.begin(i2cAddress)) { - logger.error("Couldn't find GXHT30 sensor!"); + if (!_sht31.begin(i2cAddress)) { + _logger.error("Couldn't find GXHT30 sensor!"); ESP.restart(); } } SensorData Sensor::readData(float tempOffset, float humidityOffset) { - SensorData data(sht31.readTemperature(), tempOffset, sht31.readHumidity(), humidityOffset); + SensorData data(_sht31.readTemperature(), tempOffset, _sht31.readHumidity(), humidityOffset); if (isnan(data.temperature_c) || isnan(data.humidity)) { - logger.error("Failed to read sensor data"); + _logger.error("Failed to read sensor data"); } return data; } \ No newline at end of file diff --git a/lib/Sensor/sensor.h b/lib/Sensor/sensor.h index a709026..11ca69b 100644 --- a/lib/Sensor/sensor.h +++ b/lib/Sensor/sensor.h @@ -12,8 +12,8 @@ class Sensor { SensorData readData(float tempOffset, float humidityOffset); private: - Logger &logger; // Reference to the logger - Adafruit_SHT31 sht31 = Adafruit_SHT31(); + Logger &_logger; // Reference to the logger + Adafruit_SHT31 _sht31 = Adafruit_SHT31(); }; #endif // SENSOR_H \ No newline at end of file diff --git a/lib/Settings/settings.cpp b/lib/Settings/settings.cpp index 23de1e4..a45d78d 100644 --- a/lib/Settings/settings.cpp +++ b/lib/Settings/settings.cpp @@ -6,27 +6,27 @@ const char *Settings::SETTINGS_FILENAME = "/settings.bin"; void Settings::begin() { LittleFS.begin(); } bool Settings::loadSettings(AppSettings &settings, std::function getDefaultSettings) { - File file = LittleFS.open(filename, "r"); + File file = LittleFS.open(_filename, "r"); if (!file || file.size() < sizeof(AppSettings)) { getDefaultSettings(settings); - logger.warn("Using default settings"); + _logger.warn("Using default settings"); return true; // No settings file, used default settings } size_t bytesRead = file.read((uint8_t *)&settings, sizeof(AppSettings)); file.close(); if (bytesRead == sizeof(AppSettings)) { - logger.info("Settings loaded successfully"); + _logger.info("Settings loaded successfully"); return true; } else { - logger.error("Failed to read settings"); + _logger.error("Failed to read settings"); } return false; // Failed to read settings } bool Settings::saveSettings(const AppSettings &newsettings, AppSettings &settings) { - File file = LittleFS.open(filename, "w"); + File file = LittleFS.open(_filename, "w"); if (!file) { - logger.error("Failed to open file for writing"); + _logger.error("Failed to open file for writing"); return false; } size_t bytesWritten = file.write((uint8_t *)&newsettings, sizeof(AppSettings)); diff --git a/lib/Settings/settings.h b/lib/Settings/settings.h index 48062a9..ec1ac30 100644 --- a/lib/Settings/settings.h +++ b/lib/Settings/settings.h @@ -7,7 +7,7 @@ class Settings { public: - Settings(Logger &log, String filename = SETTINGS_FILENAME) : logger(log), filename(filename) {} + Settings(Logger &log, String filename = SETTINGS_FILENAME) : _logger(log), _filename(filename) {} // SimpleSettings(Logger &log, const char *filename = SETTINGS_FILENAME) // : SimpleSettings(log, String(filename)) {} void begin(); @@ -16,8 +16,8 @@ class Settings { static const char *SETTINGS_FILENAME; private: - Logger &logger; // Reference to the logger - String filename; + Logger &_logger; // Reference to the logger + String _filename; }; #endif // SETTINGS_H diff --git a/lib/SimpleWiFi/simplewifi.cpp b/lib/SimpleWiFi/simplewifi.cpp index d921584..78f14b7 100644 --- a/lib/SimpleWiFi/simplewifi.cpp +++ b/lib/SimpleWiFi/simplewifi.cpp @@ -1,16 +1,16 @@ #include "simplewifi.h" -SimpleWiFi::SimpleWiFi(Logger &log) : logger(log) {} +SimpleWiFi::SimpleWiFi(Logger &log) : _logger(log) {} void SimpleWiFi::begin(unsigned long portalTimeout, unsigned long wifiConnectTimeout, unsigned long wifiConnectRetries, const char *apName, const char *apPassword) { - logger.info("Starting WiFi"); - wifiManager.setConfigPortalTimeout(portalTimeout); - wifiManager.setConnectTimeout(wifiConnectTimeout); - wifiManager.setConnectRetries(wifiConnectRetries); - wifiManager.setWiFiAutoReconnect(true); - if (!wifiManager.autoConnect(apName, apPassword)) { - logger.error("Autoconnect failed. Restarting..."); + _logger.info("Starting WiFi"); + _wifiManager.setConfigPortalTimeout(portalTimeout); + _wifiManager.setConnectTimeout(wifiConnectTimeout); + _wifiManager.setConnectRetries(wifiConnectRetries); + _wifiManager.setWiFiAutoReconnect(true); + if (!_wifiManager.autoConnect(apName, apPassword)) { + _logger.error("Autoconnect failed. Restarting..."); delay(3000); ESP.restart(); } @@ -18,7 +18,7 @@ void SimpleWiFi::begin(unsigned long portalTimeout, unsigned long wifiConnectTim void SimpleWiFi::ensureConnected() { if ((WiFi.status() != WL_CONNECTED)) { - logger.warn("WiFi not connected, reconnecting..."); + _logger.warn("WiFi not connected, reconnecting..."); WiFi.reconnect(); unsigned long start = millis(); @@ -27,9 +27,9 @@ void SimpleWiFi::ensureConnected() { } if (WiFi.status() == WL_CONNECTED) { - logger.error("WiFi reconnected"); + _logger.error("WiFi reconnected"); } else { - logger.warn("Failed to reconnect to WiFi. Retrying..."); + _logger.warn("Failed to reconnect to WiFi. Retrying..."); delay(1000); } } diff --git a/lib/SimpleWiFi/simplewifi.h b/lib/SimpleWiFi/simplewifi.h index 099de6e..a19f02e 100644 --- a/lib/SimpleWiFi/simplewifi.h +++ b/lib/SimpleWiFi/simplewifi.h @@ -13,8 +13,8 @@ class SimpleWiFi { String localIP(); private: - Logger &logger; // Reference to the logger - WiFiManager wifiManager; + Logger &_logger; // Reference to the logger + WiFiManager _wifiManager; }; #endif // SIMPLEWIFI_H \ No newline at end of file diff --git a/lib/Webserver/webserver.cpp b/lib/Webserver/webserver.cpp index 0d9ffa9..a175c95 100644 --- a/lib/Webserver/webserver.cpp +++ b/lib/Webserver/webserver.cpp @@ -1,29 +1,29 @@ #include "webserver.h" void Webserver::useDefaultEndpoints() { - server.on("/reset", HTTP_PUT, [this]() { - server.send(200, "text/html", "reset"); - logger.warn("Restarting"); + _server.on("/reset", HTTP_PUT, [this]() { + _server.send(200, "text/html", "reset"); + _logger.warn("Restarting"); ESP.restart(); }); } void Webserver::serveStatic(const char *uri, const char *path, const char *cacheheader) { - server.serveStatic(uri, fs, path, cacheheader); + _server.serveStatic(uri, _fs, path, cacheheader); } void Webserver::begin() { - fs.begin(); - server.begin(); - logger.info("HTTP server started"); + _fs.begin(); + _server.begin(); + _logger.info("HTTP server started"); } -void Webserver::handleClient() { server.handleClient(); } +void Webserver::handleClient() { _server.handleClient(); } void Webserver::sendJson(const JsonDocument &json, int httpCode) { String response; serializeJson(json, response); - server.send(httpCode, "application/json", response); + _server.send(httpCode, "application/json", response); } -const String &Webserver::arg(const String &name) const { return server.arg(name); } \ No newline at end of file +const String &Webserver::arg(const String &name) const { return _server.arg(name); } \ No newline at end of file diff --git a/lib/Webserver/webserver.h b/lib/Webserver/webserver.h index aaf5090..a41eff4 100644 --- a/lib/Webserver/webserver.h +++ b/lib/Webserver/webserver.h @@ -9,9 +9,9 @@ class Webserver { public: - Webserver(Logger &log, FS fs, int port = 80) : logger(log), fs(fs), server(port) { - httpUpdateServer.setup(&server); - server.onNotFound([this]() { server.send(404, "text/plain", "File not found"); }); + Webserver(Logger &log, FS fs, int port = 80) : _logger(log), _fs(fs), _server(port) { + _httpUpdateServer.setup(&_server); + _server.onNotFound([this]() { _server.send(404, "text/plain", "File not found"); }); }; void useDefaultEndpoints(); void begin(); @@ -19,18 +19,20 @@ class Webserver { void sendJson(const JsonDocument &json, int httpCode = 200); const String &arg(const String &name) const; void serveStatic(const char *, const char *path, const char *cacheheader = "max-age=300"); - void on(const String &uri, ESP8266WebServer::THandlerFunction fn) { server.on(uri, fn); } - void on(const String &uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn) { server.on(uri, method, fn); } + void on(const String &uri, ESP8266WebServer::THandlerFunction fn) { _server.on(uri, fn); } + void on(const String &uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn) { + _server.on(uri, method, fn); + } void on(const String &uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn, ESP8266WebServer::THandlerFunction ufn) { - server.on(uri, method, fn, ufn); + _server.on(uri, method, fn, ufn); } private: - Logger &logger; // Reference to the logger - FS fs; - ESP8266WebServer server; - ESP8266HTTPUpdateServer httpUpdateServer; + Logger &_logger; // Reference to the logger + FS _fs; + ESP8266WebServer _server; + ESP8266HTTPUpdateServer _httpUpdateServer; }; #endif // WEBSERVER_H diff --git a/src/main.cpp b/src/main.cpp index 8b5ac70..0c2c5b9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,6 +29,7 @@ void setStatus(Logger::Level level, const String &status); void setup() { Serial.begin(SERIAL_BAUDRATE); + display.begin(); settings.begin(); settings.loadSettings(appsettings, [](AppSettings &settings) { From 772f8902659143dd2aa9a5c887c958b0ae21a259 Mon Sep 17 00:00:00 2001 From: RobThree Date: Wed, 9 Apr 2025 07:24:20 +0200 Subject: [PATCH 06/11] Better naming --- src/main.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 0c2c5b9..12cb409 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,8 +22,8 @@ AppSettings appsettings; unsigned long previousMillis = 0; // Store the last time a measurement was taken -void doReading(); -void saveSettings(); +void handleReading(); +void handleSettings(); void setStatus(Logger::Level level, const String &status); void setup() { @@ -49,8 +49,8 @@ void setup() { server.serveStatic("/js", "/app.js"); server.serveStatic("/favicon.ico", "/favicon.svg"); server.serveStatic("/settings", "/settings.html"); - server.on("/read", HTTP_GET, doReading); - server.on("/settings", HTTP_POST, saveSettings); + server.on("/read", HTTP_GET, handleReading); + server.on("/settings", HTTP_POST, handleSettings); server.useDefaultEndpoints(); server.begin(); @@ -85,7 +85,7 @@ void loop() { } } -void doReading() { +void handleReading() { JsonDocument response; JsonObject celsiusnode = response["celsius"].to(); celsiusnode["temperature"] = lastreading.getTemperatureDisplayValue(false); @@ -108,7 +108,7 @@ void doReading() { server.sendJson(response); } -void saveSettings() { +void handleSettings() { AppSettings newsettings; JsonDocument response; response["status"] = ""; From a7f56afc7b124f261d2145447aa6b43bd6f618a5 Mon Sep 17 00:00:00 2001 From: RobThree Date: Wed, 9 Apr 2025 07:27:59 +0200 Subject: [PATCH 07/11] Fix some C/F confusion introduced by CoPilot --- lib/Sensor/sensordata.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Sensor/sensordata.h b/lib/Sensor/sensordata.h index 22c9a90..7b60247 100644 --- a/lib/Sensor/sensordata.h +++ b/lib/Sensor/sensordata.h @@ -17,10 +17,10 @@ struct SensorData { SensorData(float temp_c = NAN, float offset_c = 0, float hum = NAN, float hum_offset = 0) : temperature_c(temp_c), temperature_f(toFahrenheit(temp_c)), offset_c(offset_c), - offset_f(offsetToFahrenheit(temp_c)), humidity(hum), humidity_offset(hum_offset) {} + offset_f(offsetToFahrenheit(offset_c)), humidity(hum), humidity_offset(hum_offset) {} float getTemperatureDisplayValue(bool showFahrenheit = false) const { - return showFahrenheit ? temperature_f + offset_f : temperature_c + offset_f; + return showFahrenheit ? temperature_f + offset_f : temperature_c + offset_c; } float getHumidityDisplayValue() const { return humidity + humidity_offset; } From a4d4d08c0179fa9dae6d0dd0da3a56b739aee37b Mon Sep 17 00:00:00 2001 From: RobThree Date: Wed, 9 Apr 2025 07:33:09 +0200 Subject: [PATCH 08/11] Minor cleanup & refactoring --- lib/Settings/settings.cpp | 8 +++++++- src/main.cpp | 9 +-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/Settings/settings.cpp b/lib/Settings/settings.cpp index a45d78d..34635ee 100644 --- a/lib/Settings/settings.cpp +++ b/lib/Settings/settings.cpp @@ -32,5 +32,11 @@ bool Settings::saveSettings(const AppSettings &newsettings, AppSettings &setting size_t bytesWritten = file.write((uint8_t *)&newsettings, sizeof(AppSettings)); file.close(); settings = newsettings; - return bytesWritten == sizeof(AppSettings); + if (bytesWritten == sizeof(AppSettings)) { + _logger.info("Settings saved successfully"); + return true; + } else { + _logger.error("Failed to write settings"); + } + return false; // Failed to write settings } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 12cb409..146f9f5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,7 +24,6 @@ unsigned long previousMillis = 0; // Store the last time a measurement was taken void handleReading(); void handleSettings(); -void setStatus(Logger::Level level, const String &status); void setup() { Serial.begin(SERIAL_BAUDRATE); @@ -56,7 +55,7 @@ void setup() { ota.begin(appsettings.deviceName, OTAPASSWORD); - setStatus(Logger::INFO, wifi.localIP()); + display.setStatus(wifi.localIP()); delay(2000); } @@ -135,14 +134,8 @@ void handleSettings() { if (errors.size() == 0 && settings.saveSettings(newsettings, appsettings)) { response["status"] = "success"; - setStatus(Logger::INFO, "Settings saved"); } else { response["status"] = "error"; } server.sendJson(response); -} - -void setStatus(Logger::Level level, const String &status) { - logger.log(level, status); - display.setStatus(status); } \ No newline at end of file From 987fe99639cf5d825aa7d7818808d374f6d057fc Mon Sep 17 00:00:00 2001 From: RobThree Date: Wed, 9 Apr 2025 07:48:12 +0200 Subject: [PATCH 09/11] Cleanup HTML, add spinner on settingspage while loading settings --- data/app.js | 118 ++++++++++++++------------ data/index.html | 52 +++++++----- data/main.css | 206 ++++++++++++++++++++++++++------------------- data/settings.html | 169 ++++++++++++++++++++++++------------- 4 files changed, 328 insertions(+), 217 deletions(-) diff --git a/data/app.js b/data/app.js index bd199a1..498ab7c 100644 --- a/data/app.js +++ b/data/app.js @@ -1,71 +1,83 @@ async function fetchData() { - const response = await fetch('/read'); - return await response.json() + const response = await fetch("/read"); + return await response.json(); } async function updateUI(data) { - let temp, hum, rssi, devicename; - try { - const data = await fetchData(); + let temp, hum, rssi, devicename; + try { + const data = await fetchData(); - let tempValue, tempUnit; - if (data.display === 'c') { - tempValue = data.celsius.temperature; - tempUnit = 'C'; - } else { - tempValue = data.fahrenheit.temperature; - tempUnit = 'F'; - } - temp = `${tempValue.toFixed(1)}°${tempUnit}`; - hum = `${data.humidity.relative_perc.toFixed(1)}% RH`; - rssi = `RSSI: ${data.wifi.rssi} dBm`; - devicename = data.devicename; - } catch (error) { - console.error('Error fetching sensor data:', error); - temp = hum = rssi = devicename = 'Error'; + let tempValue, tempUnit; + if (data.display === "c") { + tempValue = data.celsius.temperature; + tempUnit = "C"; + } else { + tempValue = data.fahrenheit.temperature; + tempUnit = "F"; } - document.getElementById('devicename').textContent = devicename; - document.getElementById('temperature').textContent = temp; - document.getElementById('humidity').textContent = hum; - document.getElementById('rssi').textContent = rssi; - setTimeout(updateUI, 5000); + temp = `${tempValue.toFixed(1)}°${tempUnit}`; + hum = `${data.humidity.relative_perc.toFixed(1)}% RH`; + rssi = `RSSI: ${data.wifi.rssi} dBm`; + devicename = data.devicename; + } catch (error) { + console.error("Error fetching sensor data:", error); + temp = hum = rssi = devicename = "Error"; + } + document.getElementById("devicename").textContent = devicename; + document.getElementById("temperature").textContent = temp; + document.getElementById("humidity").textContent = hum; + document.getElementById("rssi").textContent = rssi; + setTimeout(updateUI, 5000); } async function showNotification(message, type) { - const notification = document.getElementById('notification'); - notification.innerHTML = message; - notification.className = `notification ${type}`; - notification.style.display = 'block'; + const notification = document.getElementById("notification"); + notification.innerHTML = message; + notification.className = `notification ${type}`; + notification.style.display = "block"; - setTimeout(() => { notification.style.display = 'none'; }, 10000); + setTimeout(() => { + notification.style.display = "none"; + }, 10000); } async function loadSettings() { - try { - const settings = await fetchData(); + try { + const settings = await fetchData(); + const overlay = document.getElementById("loadingOverlay"); - document.getElementById('devicename').value = settings.devicename; - document.getElementById('updateinterval').value = settings.updateinterval; - document.getElementById('tempoffset').value = settings.celsius.offset; - document.getElementById('humidityoffset').value = settings.humidity.relative_perc_offset; - document.getElementById('showfahrenheit').checked = settings.display === 'f'; - } catch (error) { - console.error('Error loading settings:', error); - } + document.getElementById("devicename").value = settings.devicename; + document.getElementById("updateinterval").value = settings.updateinterval; + document.getElementById("tempoffset").value = settings.celsius.offset; + document.getElementById("humidityoffset").value = + settings.humidity.relative_perc_offset; + document.getElementById("showfahrenheit").checked = + settings.display === "f"; + overlay.classList.add("hidden"); + } catch (error) { + console.error("Error loading settings:", error); + } } async function saveSettings(formData) { - try { - const response = await fetch('/settings', { method: 'POST', body: formData }); - const data = await response.json(); + try { + const response = await fetch("/settings", { + method: "POST", + body: formData, + }); + const data = await response.json(); - if (data.status === "success") { - showNotification("Settings saved successfully!", "success"); - } else if (data.status === "error" && data.errors.length > 0) { - showNotification("Errors:
  • " + data.errors.join("
  • ") + "
", "error"); - } else { - showNotification("Unexpected response from server", "error"); - } - } catch (error) { - showNotification("Failed to save settings: " + error.message, "error"); + if (data.status === "success") { + showNotification("Settings saved successfully!", "success"); + } else if (data.status === "error" && data.errors.length > 0) { + showNotification( + "Errors:
  • " + data.errors.join("
  • ") + "
", + "error" + ); + } else { + showNotification("Unexpected response from server", "error"); } -} \ No newline at end of file + } catch (error) { + showNotification("Failed to save settings: " + error.message, "error"); + } +} diff --git a/data/index.html b/data/index.html index 518ae2f..1ff09bc 100644 --- a/data/index.html +++ b/data/index.html @@ -1,32 +1,42 @@ - - - + + + Temperature Display - - - - + + + + - - - - + + + +
-

Please wait…

-
--°
-
--% RH
-
RSSI: --
+

Please wait…

+
--°
+
--% RH
+
RSSI: --
- - - \ No newline at end of file + + diff --git a/data/main.css b/data/main.css index 4fc31fb..46804c8 100644 --- a/data/main.css +++ b/data/main.css @@ -1,156 +1,192 @@ -html, body { - margin: 0; - padding: 10px; - height: 100%; - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - line-height: 1.6; - color: #333; - max-width: 600px; - margin: 0 auto; - background-color: #f5f5f5; - box-sizing: border-box; +html, +body { + margin: 0; + padding: 10px; + height: 100%; + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + line-height: 1.6; + color: #333; + max-width: 600px; + margin: 0 auto; + background-color: #f5f5f5; + box-sizing: border-box; } .main { - display: flex; - justify-content: center; - align-items: center; + display: flex; + justify-content: center; + align-items: center; } .container { - background-color: white; - border-radius: 8px; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); - padding: 25px; + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + padding: 25px; } .main .container { - text-align: center; - width: 300px; - margin: 0; + text-align: center; + width: 300px; + margin: 0; } h1 { - color: #2c3e50; - margin-top: 0; - margin-bottom: 25px; - font-size: 24px; + color: #2c3e50; + margin-top: 0; + margin-bottom: 25px; + font-size: 24px; } .form-group { - margin-bottom: 20px; + margin-bottom: 20px; } label { - display: block; - font-weight: 600; + display: block; + font-weight: 600; } input[type="text"], input[type="number"] { - width: 100%; - padding: 10px; - border: 1px solid #ddd; - border-radius: 4px; - font-size: 16px; - box-sizing: border-box; + width: 100%; + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 16px; + box-sizing: border-box; } input[type="checkbox"] { - margin-right: 8px; - transform: scale(1.2); + margin-right: 8px; + transform: scale(1.2); } input[type="submit"] { - margin-left: 10px; + margin-left: 10px; } .checkbox-label { - display: flex; - align-items: center; - cursor: pointer; + display: flex; + align-items: center; + cursor: pointer; } button { - background-color: #007bff; - color: white; - border: none; - padding: 12px 20px; - border-radius: 4px; - cursor: pointer; - font-size: 16px; - font-weight: 600; - transition: background-color 0.2s; + background-color: #007bff; + color: white; + border: none; + padding: 12px 20px; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + font-weight: 600; + transition: background-color 0.2s; } button.secondary { - background-color: #6c757d; + background-color: #6c757d; } button:hover { - background-color: #0069d9; + background-color: #0069d9; } button.secondary:hover { - background-color: #5a6268; + background-color: #5a6268; } input:invalid + .error { - display: block; + display: block; } .footer { - text-align: right; - margin-top: 20px; + text-align: right; + margin-top: 20px; } .notification { - display: none; - padding: 10px; - margin-bottom: 15px; - border-radius: 5px; - font-weight: bold; - text-align: center; + display: none; + padding: 10px; + margin-bottom: 15px; + border-radius: 5px; + font-weight: bold; + text-align: center; } .notification.success { - background-color: #d4edda; - color: #155724; - border: 1px solid #c3e6cb; + background-color: #d4edda; + color: #155724; + border: 1px solid #c3e6cb; } .notification.error { - background-color: #f8d7da; - color: #721c24; - border: 1px solid #f5c6cb; + background-color: #f8d7da; + color: #721c24; + border: 1px solid #f5c6cb; } .help-text { - font-size: 12px; - color: #666; - margin-top: 5px; + font-size: 12px; + color: #666; + margin-top: 5px; } #temperature { - font-size: 48px; - font-weight: bold; - margin: 20px 0; + font-size: 48px; + font-weight: bold; + margin: 20px 0; } #humidity { - font-size: 24px; - margin: 15px 0; + font-size: 24px; + margin: 15px 0; } #rssi { - font-size: 10px; - color: #888; - margin-top: 20px; + font-size: 10px; + color: #888; + margin-top: 20px; } .settings-icon, .home-icon { - position: absolute; - top: 10px; - right: 10px; - width: 24px; - height: 24px; - cursor: pointer; -} \ No newline at end of file + position: absolute; + top: 10px; + right: 10px; + width: 24px; + height: 24px; + cursor: pointer; +} + +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; +} + +.spinner { + width: 50px; + height: 50px; + border: 5px solid #f3f3f3; + border-top: 5px solid #3498db; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.hidden { + display: none; +} diff --git a/data/settings.html b/data/settings.html index 858f709..83a9f7a 100644 --- a/data/settings.html +++ b/data/settings.html @@ -1,70 +1,123 @@ - - - + + + Device Configuration - - - - + + + + +
+
+
+ - - - + + +
-

Device Configuration

+

Device Configuration

+ +
+
+
+ + +
+ Note that this is not the Web UI update interval! +
+
+ +
+
+ + +
+
+ When unchecked, temperature will be shown in Celsius +
+
+ +
+ + +
In Celsius, regardless of above settings
+
+ +
+ + +
+ +
+ + +
-
- -
- - -
Note that this is not the Web UI update interval!
-
- -
-
- - -
-
When unchecked, temperature will be shown in Celsius
-
- -
- - -
In Celsius, regardless of above settings
-
- -
- - -
- -
- - -
- - -
+ +
- - \ No newline at end of file + + From 3ebcc4c894b7ef761bf27c0b499770051b28aef5 Mon Sep 17 00:00:00 2001 From: RobThree Date: Wed, 9 Apr 2025 08:29:38 +0200 Subject: [PATCH 10/11] Properly initialize display --- lib/Display/display.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Display/display.cpp b/lib/Display/display.cpp index 221dc69..ae1ad85 100644 --- a/lib/Display/display.cpp +++ b/lib/Display/display.cpp @@ -17,6 +17,8 @@ void Display::begin() { _display.setRotation(2); _display.setTextSize(2); _display.setTextColor(SSD1306_WHITE); + + _display.display(); } void Display::showMeasurements(SensorData &data, bool showFahrenheit) { From 911fcb19c3c9697cbccf93944b578c382343c901 Mon Sep 17 00:00:00 2001 From: RobIII Date: Wed, 9 Apr 2025 14:50:12 +0200 Subject: [PATCH 11/11] More status on display --- lib/OTA/ota.cpp | 11 ++++++----- lib/OTA/ota.h | 5 ++++- lib/Settings/settings.cpp | 8 +++++++- lib/Settings/settings.h | 5 ++++- lib/SimpleWiFi/simplewifi.cpp | 13 ++++++++----- lib/SimpleWiFi/simplewifi.h | 6 +++++- lib/Webserver/webserver.cpp | 1 + lib/Webserver/webserver.h | 5 ++++- lib/statuscallback.h | 8 ++++++++ src/main.cpp | 31 ++++++++++++++++++++++--------- 10 files changed, 69 insertions(+), 24 deletions(-) create mode 100644 lib/statuscallback.h diff --git a/lib/OTA/ota.cpp b/lib/OTA/ota.cpp index 55e5395..f8cf0c7 100644 --- a/lib/OTA/ota.cpp +++ b/lib/OTA/ota.cpp @@ -1,20 +1,21 @@ #include "ota.h" #include -OTA::OTA(Logger &log) : _logger(log) {} - void OTA::begin(const char *const hostname, const char *const password) { - ArduinoOTA.setPassword(hostname); - ArduinoOTA.setHostname(password); + ArduinoOTA.setHostname(hostname); + ArduinoOTA.setPassword(password); ArduinoOTA.onStart([this]() { String type = (ArduinoOTA.getCommand() == U_FLASH) ? "sketch" : "filesystem"; _logger.info(("Start updating " + type).c_str()); }); ArduinoOTA.onEnd([this]() { _logger.info("Update Complete"); }); ArduinoOTA.onProgress([this](unsigned int progress, unsigned int total) { - _logger.info(("Progress: " + String((progress * 100) / total) + "%").c_str()); + String message = "Updating: " + String((progress * 100) / total) + "%"; + _statuscallback(message.c_str()); + _logger.info(message.c_str()); }); ArduinoOTA.onError([this](ota_error_t error) { + _statuscallback("Error updating"); _logger.error(("Error: " + String(error)).c_str()); if (error == OTA_AUTH_ERROR) { _logger.error("Auth Failed"); diff --git a/lib/OTA/ota.h b/lib/OTA/ota.h index 14ca704..f84b26a 100644 --- a/lib/OTA/ota.h +++ b/lib/OTA/ota.h @@ -2,16 +2,19 @@ #define OTA_H #include "../Logger/logger.h" +#include "../statuscallback.h" #include class OTA { public: - OTA(Logger &log); + OTA(Logger &log) : _logger(log), _statuscallback(emptyStatus) {}; void begin(const char *const hostname, const char *const password); void handle(); + void onStatus(StatusCallback callback) { _statuscallback = callback; } private: Logger &_logger; // Reference to the logger + StatusCallback _statuscallback; }; #endif // OTA_H diff --git a/lib/Settings/settings.cpp b/lib/Settings/settings.cpp index 34635ee..d86e70f 100644 --- a/lib/Settings/settings.cpp +++ b/lib/Settings/settings.cpp @@ -9,16 +9,19 @@ bool Settings::loadSettings(AppSettings &settings, std::function class Settings { public: - Settings(Logger &log, String filename = SETTINGS_FILENAME) : _logger(log), _filename(filename) {} + Settings(Logger &log, String filename = SETTINGS_FILENAME) : _logger(log), _filename(filename), _statuscallback(emptyStatus) {} // SimpleSettings(Logger &log, const char *filename = SETTINGS_FILENAME) // : SimpleSettings(log, String(filename)) {} void begin(); bool loadSettings(AppSettings &settings, std::function getDefaultSettings); bool saveSettings(const AppSettings &newsettings, AppSettings &settings); + void onStatus(StatusCallback callback) { _statuscallback = callback; } static const char *SETTINGS_FILENAME; private: Logger &_logger; // Reference to the logger String _filename; + StatusCallback _statuscallback; }; #endif // SETTINGS_H diff --git a/lib/SimpleWiFi/simplewifi.cpp b/lib/SimpleWiFi/simplewifi.cpp index 78f14b7..95718f1 100644 --- a/lib/SimpleWiFi/simplewifi.cpp +++ b/lib/SimpleWiFi/simplewifi.cpp @@ -1,16 +1,16 @@ #include "simplewifi.h" -SimpleWiFi::SimpleWiFi(Logger &log) : _logger(log) {} - void SimpleWiFi::begin(unsigned long portalTimeout, unsigned long wifiConnectTimeout, unsigned long wifiConnectRetries, const char *apName, const char *apPassword) { + _statuscallback("Starting WiFi"); _logger.info("Starting WiFi"); _wifiManager.setConfigPortalTimeout(portalTimeout); _wifiManager.setConnectTimeout(wifiConnectTimeout); _wifiManager.setConnectRetries(wifiConnectRetries); _wifiManager.setWiFiAutoReconnect(true); if (!_wifiManager.autoConnect(apName, apPassword)) { - _logger.error("Autoconnect failed. Restarting..."); + _statuscallback("Autoconnect failed. Restarting"); + _logger.error("Autoconnect failed. Restarting"); delay(3000); ESP.restart(); } @@ -18,7 +18,8 @@ void SimpleWiFi::begin(unsigned long portalTimeout, unsigned long wifiConnectTim void SimpleWiFi::ensureConnected() { if ((WiFi.status() != WL_CONNECTED)) { - _logger.warn("WiFi not connected, reconnecting..."); + _statuscallback("Reconnecting WiFi"); + _logger.warn("WiFi not connected, reconnecting"); WiFi.reconnect(); unsigned long start = millis(); @@ -27,8 +28,10 @@ void SimpleWiFi::ensureConnected() { } if (WiFi.status() == WL_CONNECTED) { - _logger.error("WiFi reconnected"); + _statuscallback("WiFi reconnected"); + _logger.info("WiFi reconnected"); } else { + _statuscallback("Reconnect WiFi failed. Retrying"); _logger.warn("Failed to reconnect to WiFi. Retrying..."); delay(1000); } diff --git a/lib/SimpleWiFi/simplewifi.h b/lib/SimpleWiFi/simplewifi.h index a19f02e..0a12f1b 100644 --- a/lib/SimpleWiFi/simplewifi.h +++ b/lib/SimpleWiFi/simplewifi.h @@ -2,19 +2,23 @@ #define SIMPLEWIFI_H #include "../Logger/logger.h" +#include "../statuscallback.h" #include class SimpleWiFi { public: - SimpleWiFi(Logger &log); + SimpleWiFi(Logger &log) : _logger(log), _statuscallback(emptyStatus) {} + void begin(unsigned long portalTimeout, unsigned long wifiConnectTimeout, unsigned long wifiConnectRetries, const char *apName, const char *apPassword = (const char *)__null); void ensureConnected(); String localIP(); + void onStatus(StatusCallback callback) { _statuscallback = callback; } private: Logger &_logger; // Reference to the logger WiFiManager _wifiManager; + StatusCallback _statuscallback; }; #endif // SIMPLEWIFI_H \ No newline at end of file diff --git a/lib/Webserver/webserver.cpp b/lib/Webserver/webserver.cpp index a175c95..a80f65a 100644 --- a/lib/Webserver/webserver.cpp +++ b/lib/Webserver/webserver.cpp @@ -3,6 +3,7 @@ void Webserver::useDefaultEndpoints() { _server.on("/reset", HTTP_PUT, [this]() { _server.send(200, "text/html", "reset"); + _statuscallback("Restarting"); _logger.warn("Restarting"); ESP.restart(); }); diff --git a/lib/Webserver/webserver.h b/lib/Webserver/webserver.h index a41eff4..c44267e 100644 --- a/lib/Webserver/webserver.h +++ b/lib/Webserver/webserver.h @@ -2,6 +2,7 @@ #define WEBSERVER_H #include "../Logger/logger.h" +#include "../statuscallback.h" #include #include #include @@ -9,7 +10,7 @@ class Webserver { public: - Webserver(Logger &log, FS fs, int port = 80) : _logger(log), _fs(fs), _server(port) { + Webserver(Logger &log, FS fs, int port = 80) : _logger(log), _fs(fs), _server(port), _statuscallback(emptyStatus) { _httpUpdateServer.setup(&_server); _server.onNotFound([this]() { _server.send(404, "text/plain", "File not found"); }); }; @@ -27,12 +28,14 @@ class Webserver { ESP8266WebServer::THandlerFunction ufn) { _server.on(uri, method, fn, ufn); } + void onStatus(StatusCallback callback) { _statuscallback = callback; } private: Logger &_logger; // Reference to the logger FS _fs; ESP8266WebServer _server; ESP8266HTTPUpdateServer _httpUpdateServer; + StatusCallback _statuscallback; }; #endif // WEBSERVER_H diff --git a/lib/statuscallback.h b/lib/statuscallback.h new file mode 100644 index 0000000..9cf9e4e --- /dev/null +++ b/lib/statuscallback.h @@ -0,0 +1,8 @@ +#ifndef STATUS_CALLBACK_H +#define STATUS_CALLBACK_H + +using StatusCallback = void (*)(const char*); + +inline void emptyStatus(const char*) {} + +#endif // STATUS_CALLBACK_H \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 146f9f5..c6b0360 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,6 +22,7 @@ AppSettings appsettings; unsigned long previousMillis = 0; // Store the last time a measurement was taken +void setStatus(const char *status); void handleReading(); void handleSettings(); @@ -29,18 +30,22 @@ void setup() { Serial.begin(SERIAL_BAUDRATE); display.begin(); + + settings.onStatus(setStatus); settings.begin(); - settings.loadSettings(appsettings, [](AppSettings &settings) { - settings.updateInterval = UPDATEINTERVAL; - settings.tempOffset = TEMPERATUREOFFSET; - settings.humidityOffset = HUMIDITYOFFSET; - settings.showFahrenheit = SHOWFAHRENHEIT ? true : false; - strncpy(settings.deviceName, DEVICENAME, sizeof(settings.deviceName) - 1); - settings.deviceName[sizeof(settings.deviceName) - 1] = '\0'; // Ensure null termination + settings.loadSettings(appsettings, [](AppSettings &defaults) { + defaults.updateInterval = UPDATEINTERVAL; + defaults.tempOffset = TEMPERATUREOFFSET; + defaults.humidityOffset = HUMIDITYOFFSET; + defaults.showFahrenheit = SHOWFAHRENHEIT ? true : false; + strncpy(appsettings.deviceName, DEVICENAME, sizeof(appsettings.deviceName) - 1); + defaults.deviceName[sizeof(appsettings.deviceName) - 1] = '\0'; // Ensure null termination }); + wifi.onStatus(setStatus); wifi.begin(PORTALTIMEOUT, WIFICONNECTTIMEOUT, WIFICONNECTRETRIES, appsettings.deviceName); + sensor.begin(); server.serveStatic("/", "/index.html"); @@ -51,11 +56,13 @@ void setup() { server.on("/read", HTTP_GET, handleReading); server.on("/settings", HTTP_POST, handleSettings); server.useDefaultEndpoints(); + server.onStatus(setStatus); server.begin(); + ota.onStatus(setStatus); ota.begin(appsettings.deviceName, OTAPASSWORD); - display.setStatus(wifi.localIP()); + setStatus(wifi.localIP().c_str()); delay(2000); } @@ -99,8 +106,10 @@ void handleReading() { wifinode["rssi"] = WiFi.RSSI(); char lastUpdateStr[32]; - snprintf(lastUpdateStr, sizeof(lastUpdateStr), "%.2f seconds ago", (millis() - previousMillis) / 1000.0); + unsigned long lastupdate = millis() - previousMillis; + snprintf(lastUpdateStr, sizeof(lastUpdateStr), "%.2f seconds ago", lastupdate / 1000.0); response["lastupdate"] = lastUpdateStr; + response["lastupdate_ms"] = lastupdate; response["display"] = appsettings.showFahrenheit ? "f" : "c"; response["devicename"] = appsettings.deviceName; response["updateinterval"] = appsettings.updateInterval; @@ -138,4 +147,8 @@ void handleSettings() { response["status"] = "error"; } server.sendJson(response); +} + +void setStatus(const char *status) { + display.setStatus(status); } \ No newline at end of file