diff --git a/bin/firmware.bin b/bin/firmware.bin index d31f2be..712cca5 100644 Binary files a/bin/firmware.bin and b/bin/firmware.bin differ diff --git a/project.json b/project.json index 0a474c3..b73ced3 100644 --- a/project.json +++ b/project.json @@ -1,7 +1,7 @@ { "name": "AirQualityMonitor", "about": "Умный монитор качества воздуха", - "version": "1.3.6", + "version": "1.3.7", "notes": "", "builds": [ { diff --git a/resources/.DS_Store b/resources/.DS_Store index ad99c2b..5b70bf3 100644 Binary files a/resources/.DS_Store and b/resources/.DS_Store differ diff --git a/src/configs/config.h b/src/configs/config.h index 7daafe2..6d90073 100644 --- a/src/configs/config.h +++ b/src/configs/config.h @@ -1,6 +1,5 @@ #pragma once #include -// #include "configs/secrets.h" // HINT: for development only #include "configs/secrets.example.h" #define STRINGIZER(arg) #arg @@ -9,13 +8,19 @@ // app #define APP_NAME "AirQualityMonitor" #define APP_VERSION STR_VALUE(BUILD_VERSION) // Change version via project.json! +#define APP_DEFAULT_LOG_LEVEL "ERROR" #define APP_LOG_LEVEL "DEBUG" // DEBUG, ERROR, WARN, INFO // #define ENABLE_TEST // Enable mock sensor reading #define APP_DARK_THEME false // Select color theme +#define APP_CO2_DEFAULT_ALERT_TRHLD 1200 // CO2 threshold for red blinking +#define APP_CO2_DEFAULT_SCALE_TYPE "4 color" // app // maint // #define DB_RESET // Factory reset database +#ifdef DB_RESET +#include "configs/secrets.h" // HINT: for development only +#endif #define DB_NAME "/settings.db" #define PROJECT_PATH "WildEgor/AirQualityMonitor/master/project.json" #define USER_MANUAL_URL "https://github.com/WildEgor/AirQualityMonitor/blob/master/docs/en/UserManual.md" @@ -35,6 +40,7 @@ #define SEC_5 5000 #define SEC_10 10000 #define SEC_30 30000 +#define EMPTY_SECRET "*****" // system // MQTT service for interaction with Yandex (see wqtt.ru) @@ -59,7 +65,6 @@ #define RGB_ENABLED false #define RGB_PIN 19 #define RGB_NUMPIXELS 4 // Number of LEDs in the strip. Min: 1, Max: 255 -#define RGB_DEFAULT_ALERT_TRHLD 1200 // CO2 threshold for red blinking // rgb settings // hmi diff --git a/src/db/settings_db.cpp b/src/db/settings_db.cpp index bf4b187..0866a96 100644 --- a/src/db/settings_db.cpp +++ b/src/db/settings_db.cpp @@ -32,20 +32,22 @@ SettingsDB::SettingsDB() : LoopTickerBase(), _db(&LittleFS, DB_NAME) _db.begin(); /** - * @note Сброс базы данных к заводским настройкам, если определён RESET_DB + * @note Reset database */ -#ifdef RESET_DB +#ifdef DB_RESET _db.reset(); + _db.update(); #endif /** - * @note Инициализация разделов настроек: APP, WIFI, MQTT, CO2 + * @note Init settings: APP, WIFI, MQTT, CO2 */ // ============================== APP ============================== _db.init(kk::rgb_enabled, RGB_ENABLED); _db.init(kk::use_dark_theme, APP_DARK_THEME); _db.init(kk::log_lvl, APP_LOG_LEVEL); _db.init(kk::rotation_display, TFT_ROTATION_0); + _db.init(kk::cfm_fr, false); // ============================== WIFI ============================== _db.init(kk::wifi_ssid, WIFI_SSID); @@ -60,11 +62,11 @@ SettingsDB::SettingsDB() : LoopTickerBase(), _db(&LittleFS, DB_NAME) _db.init(kk::mqtt_device_id, MQTT_DEFAULT_DEVICE_ID); // ============================== CO2 ============================== - _db.init(kk::co2_scale_type, "4 color"); - _db.init(kk::co2_alarm_lvl, RGB_DEFAULT_ALERT_TRHLD); + _db.init(kk::co2_scale_type, APP_CO2_DEFAULT_SCALE_TYPE); + _db.init(kk::co2_alarm_lvl, APP_CO2_DEFAULT_ALERT_TRHLD); /** - * @note Вывод содержимого базы данных в сериал лог + * @note Show db dump */ _db.dump(Serial); @@ -73,6 +75,30 @@ SettingsDB::SettingsDB() : LoopTickerBase(), _db(&LittleFS, DB_NAME) this->addLoop(); } +void SettingsDB::factory_reset() +{ + _db.reset(); + + _db[kk::cfm_fr] = false; + _db[kk::rgb_enabled] = false; + _db[kk::use_dark_theme] = false; + _db[kk::log_lvl] = APP_DEFAULT_LOG_LEVEL; + _db[kk::rotation_display] = TFT_ROTATION_0; + _db[kk::wifi_ssid] = EMPTY_SECRET; + _db[kk::wifi_pass] = EMPTY_SECRET; + _db[kk::mqtt_enabled] = false; + _db[kk::mqtt_server] = EMPTY_SECRET; + _db[kk::mqtt_username] = EMPTY_SECRET; + _db[kk::mqtt_pass] = EMPTY_SECRET; + _db[kk::mqtt_device_id] = MQTT_DEFAULT_DEVICE_ID; + _db[kk::co2_scale_type] = APP_CO2_DEFAULT_SCALE_TYPE; + _db[kk::co2_alarm_lvl] = APP_CO2_DEFAULT_ALERT_TRHLD; + + _db.update(); + + ESP.restart(); +} + void SettingsDB::exec() { _db.tick(); diff --git a/src/db/settings_db.h b/src/db/settings_db.h index 29fe52d..3fd45ce 100644 --- a/src/db/settings_db.h +++ b/src/db/settings_db.h @@ -27,7 +27,8 @@ enum kk : size_t rgb_enabled, ///< RGB enabled flag use_dark_theme, ///< Use dark theme flag rotation_display, ///< Rotation display - log_lvl ///< Log level + log_lvl, ///< Log level + cfm_fr ///< Configrm factory reset }; /** @@ -58,6 +59,11 @@ class SettingsDB : public LoopTickerBase */ void setup(); + /** + * @brief Reset db values to factory values and reboot device + */ + void factory_reset(); + /** * @brief Handle database updates */ diff --git a/src/hmi/display.h b/src/hmi/display.h index d15204f..04897e6 100644 --- a/src/hmi/display.h +++ b/src/hmi/display.h @@ -15,6 +15,9 @@ #define LOG_COMPONENT "Display" #include "services/logger.h" +#define TFT_BASE_X_OFFSET 0 +#define TFT_BASE_Y_OFFSET 0 + /** * @name Display * @details Class for managing the TFT display, rendering widgets and sensor data @@ -66,6 +69,7 @@ class Display : public LoopTimerBase _tft.init(); _tft.setRotation(_tft_rotate); _init_theme(true); + _init_rotation_offsets(); LOG_INFO("init tft ok!"); LOG_INFO("init widgets..."); @@ -76,7 +80,7 @@ class Display : public LoopTimerBase _co2_scale->getScale(rs, re, os, oe, ys, ye, gs, ge); _co2_meter.setZones(rs, re, os, oe, ys, ye, gs, ge); _co2_meter.setTheme(_state.dark_theme); - _co2_meter.analogMeter(0, 0, _co2_scale->getHumanMax(), "CO2", "", "", "", "", ""); + _co2_meter.analogMeter(_x_center_offset, _y_center_offset, _co2_scale->getHumanMax(), "CO2", "", "", "", "", ""); LOG_INFO("init widgets ok!"); _render(); @@ -109,7 +113,7 @@ class Display : public LoopTimerBase uint16_t rs, re, os, oe, ys, ye, gs, ge; _co2_scale->getScale(rs, re, os, oe, ys, ye, gs, ge); _co2_meter.setZones(rs, re, os, oe, ys, ye, gs, ge); - _co2_meter.analogMeter(0, 0, _co2_scale->getHumanMax(), "CO2", "", "", "", "", ""); + _co2_meter.analogMeter(_x_center_offset, _y_center_offset, _co2_scale->getHumanMax(), "CO2", "", "", "", "", ""); _force_redraw = true; _render(); @@ -127,6 +131,10 @@ class Display : public LoopTimerBase (*_db)[kk::rotation_display] = _tft_rotate; + LOG_DEBUG("rotation: " + String(_tft_rotate)); + + _init_rotation_offsets(); + _tft.setRotation(_tft_rotate); if (_state.dark_theme) @@ -142,7 +150,7 @@ class Display : public LoopTimerBase uint16_t rs, re, os, oe, ys, ye, gs, ge; _co2_scale->getScale(rs, re, os, oe, ys, ye, gs, ge); _co2_meter.setZones(rs, re, os, oe, ys, ye, gs, ge); - _co2_meter.analogMeter(0, 0, _co2_scale->getHumanMax(), "CO2", "", "", "", "", ""); + _co2_meter.analogMeter(_x_center_offset, _y_center_offset, _co2_scale->getHumanMax(), "CO2", "", "", "", "", ""); _force_redraw = true; _render(); @@ -158,7 +166,7 @@ class Display : public LoopTimerBase uint16_t rs, re, os, oe, ys, ye, gs, ge; _co2_scale->getScale(rs, re, os, oe, ys, ye, gs, ge); _co2_meter.setZones(rs, re, os, oe, ys, ye, gs, ge); - _co2_meter.analogMeter(0, 0, _co2_scale->getHumanMax(), "CO2", "", "", "", "", ""); + _co2_meter.analogMeter(_x_center_offset, _y_center_offset, _co2_scale->getHumanMax(), "CO2", "", "", "", "", ""); _force_redraw = true; _render(); @@ -226,6 +234,17 @@ class Display : public LoopTimerBase */ int _tft_rotate = TFT_ROTATION_0; + /** + * @name _x_center_offset + * @details Adjust center + */ + uint16_t _x_center_offset = 0; + /** + * @name _y_center_offset + * @details Adjust center + */ + uint16_t _y_center_offset = 0; + /** * @name _render * @details Render all display widgets and info @@ -341,41 +360,46 @@ class Display : public LoopTimerBase void _print_fw_version() { // show current fw version - _tft.setCursor(100, 185); + uint16_t x_fw = 100 + _x_center_offset; + uint16_t y_fw = 185 + _y_center_offset; + + _tft.setCursor(x_fw, y_fw); if (_state.dark_theme) { - _tft.fillRect(100, 185, 60, 10, TFT_BLACK); + _tft.fillRect(x_fw, y_fw, 60, 10, TFT_BLACK); } else { - _tft.fillRect(100, 185, 60, 10, TFT_WHITE); + _tft.fillRect(x_fw, y_fw, 60, 10, TFT_WHITE); } _tft.setTextColor(TFT_LIGHTGREY); _tft.print(F("v ")); _tft.println(_state.last_fw_ver); // show little green round dot as updates notification - _tft.setCursor(145, 185); + uint16_t x_fw_n = 145 + _x_center_offset; + uint16_t y_fw_n = 185 + _y_center_offset; + _tft.setCursor(x_fw_n, y_fw_n); if (_state.dark_theme) { if (_state.has_updates) { - _tft.drawSmoothCircle(145, 185, 2, TFT_GREENYELLOW, TFT_BLACK); + _tft.drawSmoothCircle(x_fw_n, y_fw_n, 2, TFT_GREENYELLOW, TFT_BLACK); } else { - _tft.drawSmoothCircle(145, 185, 2, TFT_BLACK, TFT_BLACK); + _tft.drawSmoothCircle(x_fw_n, y_fw_n, 2, TFT_BLACK, TFT_BLACK); } } else { if (_state.has_updates) { - _tft.drawSmoothCircle(145, 185, 2, TFT_GREEN, TFT_WHITE); + _tft.drawSmoothCircle(x_fw_n, y_fw_n, 2, TFT_GREEN, TFT_WHITE); } else { - _tft.drawSmoothCircle(145, 185, 2, TFT_WHITE, TFT_WHITE); + _tft.drawSmoothCircle(x_fw_n, y_fw_n, 2, TFT_WHITE, TFT_WHITE); } } } @@ -386,14 +410,17 @@ class Display : public LoopTimerBase */ void _print_mqtt_info() { - _tft.setCursor(130, 145); + int16_t x_info = 130 + _x_center_offset; + int16_t y_info = 145 + _y_center_offset; + + _tft.setCursor(x_info, y_info); if (_state.dark_theme) { - _tft.fillRect(130, 145, 60, 10, TFT_BLACK); + _tft.fillRect(x_info, y_info, 60, 10, TFT_BLACK); } else { - _tft.fillRect(130, 145, 60, 10, TFT_WHITE); + _tft.fillRect(x_info, y_info, 60, 10, TFT_WHITE); } if (!_state.last_mqtt_state) @@ -415,16 +442,17 @@ class Display : public LoopTimerBase */ void _print_wifi_info() { - _init_theme(false); + int16_t x_link = 20 + _x_center_offset; + int16_t y_link = 130 + _y_center_offset; - _tft.setCursor(20, 130); + _tft.setCursor(x_link, y_link); if (_state.dark_theme) { - _tft.fillRect(20, 130, 200, 10, TFT_BLACK); + _tft.fillRect(x_link, y_link, 200, 10, TFT_BLACK); } else { - _tft.fillRect(20, 130, 200, 10, TFT_WHITE); + _tft.fillRect(x_link, y_link, 200, 10, TFT_WHITE); } LOG_DEBUG("admin panel: http://" + _wifi->ip()); @@ -433,14 +461,17 @@ class Display : public LoopTimerBase _tft.print(F("admin panel: http://")); _tft.println(_wifi->ip()); - _tft.setCursor(90, 145); + int16_t x_wifi = 90 + _x_center_offset; + int16_t y_wifi = 145 + _y_center_offset; + + _tft.setCursor(x_wifi, y_wifi); if (_state.dark_theme) { - _tft.fillRect(90, 145, 60, 10, TFT_BLACK); + _tft.fillRect(x_wifi, y_wifi, 60, 10, TFT_BLACK); } else { - _tft.fillRect(90, 145, 60, 10, TFT_WHITE); + _tft.fillRect(x_wifi, y_wifi, 60, 10, TFT_WHITE); } if (!_state.last_wifi_state) @@ -471,10 +502,8 @@ class Display : public LoopTimerBase value = _co2_scale->getHumanMax(); } - _init_theme(false); - LOG_DEBUG("update gauge value: " + String(value)); - _tft.setCursor(0, 0); + _tft.setCursor(_x_center_offset, _y_center_offset); _co2_meter.updateNeedle(value, 10); } @@ -484,16 +513,17 @@ class Display : public LoopTimerBase */ void _print_sensor_state() { - _init_theme(false); + int16_t x_state = 90 + _x_center_offset; + int16_t y_state = 165 + _y_center_offset; - _tft.setCursor(90, 165); + _tft.setCursor(x_state, y_state); if (_state.dark_theme) { - _tft.fillRect(90, 165, 80, 10, TFT_BLACK); + _tft.fillRect(x_state, y_state, 80, 10, TFT_BLACK); } else { - _tft.fillRect(90, 165, 80, 10, TFT_WHITE); + _tft.fillRect(x_state, y_state, 80, 10, TFT_WHITE); } _tft.setTextColor(TFT_CYAN); @@ -505,11 +535,11 @@ class Display : public LoopTimerBase { if (_state.dark_theme) { - _tft.fillRect(90, 165, 80, 10, TFT_BLACK); + _tft.fillRect(x_state, y_state, 80, 10, TFT_BLACK); } else { - _tft.fillRect(90, 165, 80, 10, TFT_WHITE); + _tft.fillRect(x_state, y_state, 80, 10, TFT_WHITE); } } } @@ -535,4 +565,33 @@ class Display : public LoopTimerBase _tft.fillScreen(TFT_WHITE); _tft.setTextColor(TFT_BLACK); } + + /** + * @name _init_rotation_offsets + * @details Init display rotation offsets + * @note TODO + */ + void _init_rotation_offsets() + { + if (_tft_rotate == 0) + { + _x_center_offset = 0; + _y_center_offset = 0; + } + if (_tft_rotate == 1) + { + _x_center_offset = 0; + _y_center_offset = 0; + } + if (_tft_rotate == 2) + { + _x_center_offset = 0; + _y_center_offset = 0; + } + if (_tft_rotate == 3) + { + _x_center_offset = 0; + _y_center_offset = 0; + } + } }; diff --git a/src/hmi/web.cpp b/src/hmi/web.cpp index bc419a4..b5230a6 100644 --- a/src/hmi/web.cpp +++ b/src/hmi/web.cpp @@ -1,7 +1,6 @@ #include "web.h" sets::Logger webLogger(1024); -bool cfm_fw = false; WebPanel::WebPanel( SettingsDB &settingsDb, @@ -10,7 +9,8 @@ WebPanel::WebPanel( _sett(String(APP_NAME) + " v" + String(APP_VERSION), &settingsDb.db()), _db(&settingsDb.db()), _wifi_conn(&wifiConn), - _is_initialized(false) + _is_initialized(false), + _cfm_fr(false) { _init(); } @@ -26,6 +26,7 @@ WebPanel::WebPanel( TPHSensor &tphSeonsor) : LoopTickerBase(), _sett(String(APP_NAME) + " v" + ota.version(), &settingsDb.db()), + _settingsDb(&settingsDb), _db(&settingsDb.db()), _wifi_conn(&wifiConn), _ota(&ota), @@ -34,7 +35,8 @@ WebPanel::WebPanel( _display(&display), _co2_sensor(&co2sensor), _tph_sensor(&tphSeonsor), - _is_initialized(false) + _is_initialized(false), + _cfm_fr(false) { _init(); } @@ -81,6 +83,13 @@ void WebPanel::_update(sets::Updater &u) u.update("humidity_gauge"_h, _tph_sensor->getHumidity()); #endif + if (_cfm_fr) + { + LOG_DEBUG("confirm factory reset?"); + u.update(kk::cfm_fr, "Confirm factory reset?"); + _cfm_fr = false; + } + u.update(H(log), webLogger); if (_ota && _ota->hasUpdate()) @@ -173,6 +182,24 @@ void WebPanel::_build(sets::Builder &b) _ota->update(true); } } + + bool res; + if (b.Confirm(kk::cfm_fr, "Confirm", &res)) { + LOG_DEBUG("confirm factory reset? " + b.build.value.toString()); + + if (_settingsDb && b.build.value.toBool()) + { + _settingsDb->factory_reset(); + } + } + + if (b.Button("Factory reset")) + { + _cfm_fr = true; + + // TODO: fix confirm + _settingsDb->factory_reset(); + } SUB_BUILD_END SUB_BUILD_BEGIN @@ -181,7 +208,7 @@ void WebPanel::_build(sets::Builder &b) SUB_BUILD_BEGIN if (b.build.isAction()) - { + { switch (b.build.id) { case SH("wifi_save"): diff --git a/src/hmi/web.h b/src/hmi/web.h index 82cee83..ac0d7fa 100644 --- a/src/hmi/web.h +++ b/src/hmi/web.h @@ -78,7 +78,8 @@ class WebPanel : public LoopTickerBase { void _build(sets::Builder& b); SettingsGyver _sett; ///< Settings manager instance - GyverDBFile* _db; ///< Pointer to database file + GyverDBFile* _db; + SettingsDB* _settingsDb; ///< Pointer to database file WiFiConn* _wifi_conn; ///< Pointer to WiFi connection OTA* _ota; ///< Pointer to OTA update service MQTTConn* _mqtt_conn; ///< Pointer to MQTT connection @@ -88,4 +89,5 @@ class WebPanel : public LoopTickerBase { TPHSensor* _tph_sensor; ///< Pointer to TPH sensor bool _is_initialized; ///< Initialization flag + bool _cfm_fr; }; diff --git a/src/sensors/tph.cpp b/src/sensors/tph.cpp index b8fa35d..fc6a079 100644 --- a/src/sensors/tph.cpp +++ b/src/sensors/tph.cpp @@ -6,6 +6,7 @@ TPHSensor::TPHSensor(uint32_t ms) _data.pressure = 0.0; _data.temp = 0.0; + _data.humidity = 0.0; if (!_enable_test && !_init()) { LOG_ERROR("init failed! please check your wiring.");