From 4986a4d741f588278c6cf76230fec6dd6df81850 Mon Sep 17 00:00:00 2001 From: Ray Jones Date: Mon, 23 Mar 2020 16:54:15 +1100 Subject: [PATCH] Assortment of tweaks and fixes New Features: GPIO "Run" status output (not standby) RSSI of STA connection STA gatewayIP Only run as "active" controller when changes to fuel mixture etc for a short while. Timed moderation added for frequently changing JSON vars Altitude & Humidity via JSON JSON reboot (mainly for MQTT clients) Bug Fixes: Cyclic not enabled when frost start LVC holdoff added for "starting car" situation Better handling of string and float NV Storage defaults FrostRise limits 1-30 now 0-30 Handle spaces in SPIFFS file uploads --- Bootload/Checklist.txt | 12 ++-- src/Afterburner.cpp | 50 ++++++-------- src/OLED/GPIOInfoScreen.cpp | 7 ++ src/OLED/GPIOSetupScreen.cpp | 10 ++- src/OLED/ScreenHeader.cpp | 9 +++ src/OLED/ScreenManager.cpp | 2 + src/OLED/WiFiSTAScreen.cpp | 123 ++++++++++++++++++++++++++++++++++ src/OLED/WiFiSTAScreen.h | 38 +++++++++++ src/OLED/fonts/Icons.cpp | 16 +++++ src/OLED/fonts/Icons.h | 1 + src/Protocol/Protocol.cpp | 7 ++ src/Protocol/Protocol.h | 2 + src/Protocol/SmartError.cpp | 37 +++++----- src/Protocol/TxManage.cpp | 15 ++++- src/Protocol/TxManage.h | 2 + src/Utility/ABpreferences.cpp | 42 ++++++++++++ src/Utility/ABpreferences.h | 9 +++ src/Utility/BTC_GPIO.cpp | 29 ++++++-- src/Utility/BTC_GPIO.h | 11 +-- src/Utility/BTC_JSON.cpp | 78 ++++++++++++++++----- src/Utility/BTC_JSON.h | 1 + src/Utility/Moderator.h | 89 +++++++++++++++++++++--- src/Utility/NVCore.cpp | 51 +++++++++++--- src/Utility/NVCore.h | 8 ++- src/Utility/NVStorage.cpp | 96 +++++++++++++++++++++----- src/Utility/NVStorage.h | 10 +++ src/Utility/TempSense.cpp | 22 ++++-- src/Utility/UtilClasses.cpp | 14 +++- src/Utility/helpers.h | 2 + src/WiFi/ABMQTT.cpp | 34 +++++++--- src/WiFi/BTCWebServer.cpp | 11 ++- src/WiFi/BTCWifi.cpp | 32 +++++++++ src/WiFi/BTCWifi.h | 2 + src/WiFi/BrowserUpload.cpp | 2 + 34 files changed, 737 insertions(+), 137 deletions(-) create mode 100644 src/OLED/WiFiSTAScreen.cpp create mode 100644 src/OLED/WiFiSTAScreen.h create mode 100644 src/Utility/ABpreferences.cpp create mode 100644 src/Utility/ABpreferences.h diff --git a/Bootload/Checklist.txt b/Bootload/Checklist.txt index 2cfbf36..2894f69 100644 --- a/Bootload/Checklist.txt +++ b/Bootload/Checklist.txt @@ -1,18 +1,20 @@ Afterburner operation checklist ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Modifications performed: - Pullup on Debug Rx line - Swapped GPIO In#1 with Analogue input - 5V via blocking diode to GPIO port OLED intact Latest firmware uploaded SPIFFS uploaded +Check temperature sensor Keypad functions OK Indicator LEDs function Web server functions and serves heater control page Bluetooth pairs and streams SPP data +Set time +Install battery + +GPIO units only +~~~~~~~~~~~~~~~ +Check pressure sensor GPIO functions: Input x2 Output x2 diff --git a/src/Afterburner.cpp b/src/Afterburner.cpp index a28f995..157b718 100644 --- a/src/Afterburner.cpp +++ b/src/Afterburner.cpp @@ -97,7 +97,7 @@ #include "Protocol/Protocol.h" #include "Protocol/TxManage.h" #include "Protocol/SmartError.h" -#include "Utility/helpers.h" +#include "Utility/helpers.h" #include "Utility/NVStorage.h" #include "Utility/DebugPort.h" #include "Utility/macros.h" @@ -124,10 +124,10 @@ #define RX_DATA_TIMOUT 50 -const int FirmwareRevision = 31; -const int FirmwareSubRevision = 9; -const int FirmwareMinorRevision = 1; -const char* FirmwareDate = "15 Jan 2020"; +const int FirmwareRevision = 32; +const int FirmwareSubRevision = 0; +const int FirmwareMinorRevision = 0; +const char* FirmwareDate = "21 Mar 2020"; #ifdef ESP32 @@ -367,9 +367,6 @@ void setup() { digitalWrite(GPIOout2_pin, LOW); #endif - nvs_stats_t nvs_stats; - nvs_get_stats(NULL, &nvs_stats); - // initialise TelnetSpy (port 23) as well as Serial to 115200 // Serial is the usual USB connection to a PC // DO THIS BEFORE WE TRY AND SEND DEBUG INFO! @@ -383,6 +380,11 @@ void setup() { DebugPort.begin(115200); DebugPort.println("_______________________________________________________________"); + DebugPort.printf("Getting NVS stats\r\n"); + + nvs_stats_t nvs_stats; + while( nvs_get_stats(NULL, &nvs_stats) == ESP_ERR_NVS_NOT_INITIALIZED); + DebugPort.printf("Reset reason: core0:%d, core1:%d\r\n", rtc_get_reset_reason(0), rtc_get_reset_reason(0)); // DebugPort.printf("Previous user ON = %d\r\n", bUserON); // state flag required for cyclic mode to persist properly after a WD reboot :-) @@ -482,6 +484,9 @@ void setup() { setupGPIO(); +// pinMode(0, OUTPUT); +// digitalWrite(0, LOW); + #if USE_SW_WATCHDOG == 1 && USE_JTAG == 0 // create a high priority FreeRTOS task as a watchdog monitor TaskHandle_t wdTask; @@ -984,15 +989,18 @@ void manageFrostMode() if(heaterState == 0) { RTC_Store.setFrostOn(true); DebugPort.printf("FROST MODE: Starting heater, < %d`C\r\n", engage); + if(NVstore.getUserSettings().FrostRise == 0) + RTC_Store.setCyclicEngaged(true); // enable cyclic mode if user stop heaterOn(); } } uint8_t rise = NVstore.getUserSettings().FrostRise; - if(rise && (deltaT > rise)) { // if rsie is set to 0, user must shut off heater + if(rise && (deltaT > rise)) { // if rise is set to 0, user must shut off heater if(RTC_Store.getFrostOn()) { DebugPort.printf("FROST MODE: Stopping heater, > %d`C\r\n", engage+rise); heaterOff(); RTC_Store.setFrostOn(false); // cancel active frost mode + RTC_Store.setCyclicEngaged(false); // for cyclic mode } } } @@ -1271,29 +1279,10 @@ float getTemperatureDesired() float getTemperatureSensor(int source) { - static long lasttime = millis(); - static float tempsave = 0; - long tDelta; - // NVstore always holds primary sensor as index 0 float retval; TempSensor.getTemperature(source, retval); return retval; -// switch (source) { -// case 0: -// return FilteredSamples.AmbientTemp.getValue() + NVstore.getHeaterTuning().DS18B20probe[0].offset; -// case 1: -// BMESensor.getTemperature(tempsave); -// /* tDelta = millis() - lasttime; -// if(tDelta >= 0) { -// bme.takeForcedMeasurement(); -// tempsave = bme.readTemperature(); -// lasttime = millis() + 10000; -// }*/ -// return tempsave; -// default: -// return -100; -// } } void setPumpMin(float val) @@ -2031,4 +2020,7 @@ CTempSense& getTempSensor() return TempSensor; } - +void reqHeaterCalUpdate() +{ + TxManage.queueSysUpdate(); +} diff --git a/src/OLED/GPIOInfoScreen.cpp b/src/OLED/GPIOInfoScreen.cpp index 9cac49b..7d265ca 100644 --- a/src/OLED/GPIOInfoScreen.cpp +++ b/src/OLED/GPIOInfoScreen.cpp @@ -135,6 +135,10 @@ CGPIOInfoScreen::animate() _drawBitmap(99, 15, threshIconInfo); _drawBitmap(110, 13, bulbmode ? BulbOnIconInfo : BulbOffIconInfo); break; + case CGPIOout1::HtrActive: + _drawBitmap(99, 15, onOffIconInfo); + _drawBitmap(110, 13, bulbmode ? BulbOnIconInfo : BulbOffIconInfo); + break; } #if USE_JTAG == 0 @@ -151,6 +155,9 @@ CGPIOInfoScreen::animate() _drawBitmap(99, 27, threshIconInfo); _drawBitmap(110, 26, bulbmode ? BulbOnIconInfo : BulbOffIconInfo); break; + case CGPIOout2::HtrActive: + _drawBitmap(99, 27, onOffIconInfo); + _drawBitmap(110, 26, bulbmode ? BulbOnIconInfo : BulbOffIconInfo); } diff --git a/src/OLED/GPIOSetupScreen.cpp b/src/OLED/GPIOSetupScreen.cpp index 54be858..f40145a 100644 --- a/src/OLED/GPIOSetupScreen.cpp +++ b/src/OLED/GPIOSetupScreen.cpp @@ -138,7 +138,7 @@ CGPIOSetupScreen::show() const char* msgText = NULL; switch(_GPIOparams.out1Mode) { case CGPIOout1::Disabled: msgText = "---"; break; - case CGPIOout1::Status: msgText = "Status"; break; + case CGPIOout1::Status: msgText = "stsLED"; break; case CGPIOout1::User: msgText = "User"; break; case CGPIOout1::Thresh: if(_rowSel == 6) { @@ -151,6 +151,7 @@ CGPIOSetupScreen::show() _printMenuText(Column2, Line3, msg, _rowSel == 8); } break; + case CGPIOout1::HtrActive: msgText ="OnSts"; break; } if(msgText) _printMenuText(Column2, Line3, msgText, _rowSel == 6); @@ -173,6 +174,7 @@ CGPIOSetupScreen::show() _printMenuText(Column2, Line2, msg, _rowSel == 7); } break; + case CGPIOout2::HtrActive: msgText ="OnSts"; break; } if(msgText) _printMenuText(Column2, Line2, msgText, _rowSel == 5); @@ -256,6 +258,7 @@ CGPIOSetupScreen::animate() else pMsg = " Output 2: Active if under temperature. Hold LEFT to set under. Hold RIGHT to set over. "; break; + case CGPIOout2::HtrActive: pMsg = " Output 2: Active if heater is running. "; break; } if(pMsg) _scrollMessage(56, pMsg, _scrollChar); @@ -272,6 +275,7 @@ CGPIOSetupScreen::animate() else pMsg = " Output 1: Active if under temperature. Hold LEFT to set under. Hold RIGHT to set over. "; break; + case CGPIOout1::HtrActive: pMsg = " Output 1: Active if heater is running. "; break; } if(pMsg) _scrollMessage(56, pMsg, _scrollChar); @@ -483,13 +487,13 @@ CGPIOSetupScreen::_adjust(int dir) case 5: // outputs mode tVal = _GPIOparams.out2Mode; tVal += dir; - WRAPLIMITS(tVal, 0, 2); + WRAPLIMITS(tVal, 0, 3); _GPIOparams.out2Mode = (CGPIOout2::Modes)tVal; break; case 6: // outputs mode tVal = _GPIOparams.out1Mode; tVal += dir; - WRAPLIMITS(tVal, 0, 3); + WRAPLIMITS(tVal, 0, 4); _GPIOparams.out1Mode = (CGPIOout1::Modes)tVal; break; case 7: diff --git a/src/OLED/ScreenHeader.cpp b/src/OLED/ScreenHeader.cpp index 5e435b6..7dede16 100644 --- a/src/OLED/ScreenHeader.cpp +++ b/src/OLED/ScreenHeader.cpp @@ -229,6 +229,15 @@ CScreenHeader::showWifiIcon() { if(isWifiConnected() || isWifiAP()) { // STA or AP mode active _drawBitmap(X_WIFI_ICON, Y_WIFI_ICON, WifiWideIconInfo, WHITE, BLACK); // wide icon erases annotations! + int8_t RSSI = getWifiRSSI(); + if(RSSI < -70) { + _display.fillRect(X_WIFI_ICON, Y_WIFI_ICON, WifiWideIconInfo.width, 6, BLACK); + } + else if(RSSI < -55) { + _display.fillRect(X_WIFI_ICON, Y_WIFI_ICON, WifiWideIconInfo.width, 3, BLACK); + _display.fillRect(X_WIFI_ICON, Y_WIFI_ICON, 1, 4, BLACK); + _display.fillRect(X_WIFI_ICON+WifiIconInfo.width-1, Y_WIFI_ICON, 1, 4, BLACK); + } int xPos = X_WIFI_ICON + WifiIconInfo.width + 1; // x loaction of upload/download arrows diff --git a/src/OLED/ScreenManager.cpp b/src/OLED/ScreenManager.cpp index a72037d..87b701f 100644 --- a/src/OLED/ScreenManager.cpp +++ b/src/OLED/ScreenManager.cpp @@ -24,6 +24,7 @@ #include "BasicScreen.h" #include "PrimingScreen.h" #include "WiFiScreen.h" +#include "WiFiSTAScreen.h" #include "FuelMixtureScreen.h" #include "SetClockScreen.h" #include "SetTimerScreen.h" @@ -492,6 +493,7 @@ CScreenManager::_loadScreens() menuloop.push_back(new CHourMeterScreen(*_pDisplay, *this)); // Hour Meter screen } menuloop.push_back(new CWiFiScreen(*_pDisplay, *this)); + menuloop.push_back(new CWiFiSTAScreen(*_pDisplay, *this)); menuloop.push_back(new CMQTTScreen(*_pDisplay, *this)); menuloop.push_back(new CBTScreen(*_pDisplay, *this)); if(getTempSensor().getBME280().getCount()) { diff --git a/src/OLED/WiFiSTAScreen.cpp b/src/OLED/WiFiSTAScreen.cpp new file mode 100644 index 0000000..248679c --- /dev/null +++ b/src/OLED/WiFiSTAScreen.cpp @@ -0,0 +1,123 @@ +/* + * This file is part of the "bluetoothheater" distribution + * (https://gitlab.com/mrjones.id.au/bluetoothheater) + * + * Copyright (C) 2018 Ray Jones + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "WiFiSTAScreen.h" +#include "KeyPad.h" +#include "../Utility/helpers.h" +#include "../Utility/NVStorage.h" +#include "../WiFi/BTCWifi.h" +#include "fonts/Arial.h" +#include "fonts/Icons.h" + +/////////////////////////////////////////////////////////////////////////// +// +// CWiFiSTAScreen +// +// This screen presents information about the STA connection +// +/////////////////////////////////////////////////////////////////////////// + + +CWiFiSTAScreen::CWiFiSTAScreen(C128x64_OLED& display, CScreenManager& mgr) : CScreen(display, mgr) +{ +} + + +bool +CWiFiSTAScreen::show() +{ + CScreen::show(); + + _display.clearDisplay(); + _showTitle("WiFi STA status"); + + int yPos = 15; + + if(NVstore.getUserSettings().wifiMode == 0 || !isWifiSTA()) { + if(NVstore.getUserSettings().wifiMode == 0) + _printMenuText(border, yPos, "DISABLED"); + else + _printMenuText(border, yPos, "NOT CONNECTED"); + } + else { + _printMenuText(0, yPos, "ADDR:"); + _printMenuText(31, yPos, getWifiSTAAddrStr()); + + yPos += _display.textHeight() + 3; + _printMenuText(0, yPos, " GW:"); + _printMenuText(31, yPos, getWifiGatewayAddrStr()); + + // yPos += _display.textHeight() + 2; + // _printMenuText(0, yPos, " MAC:"); + // _printMenuText(31, yPos, getWifiSTAMACStr()); + + yPos += _display.textHeight() + 3; + _printMenuText(0, yPos, "RSSI:"); + int8_t RSSI = getWifiRSSI(); + char RSSIstr[16]; + sprintf(RSSIstr, "%ddBm", RSSI); + _printMenuText(31, yPos, RSSIstr); + + int xPos = 90; + _drawBitmap(xPos, yPos, WifiIconInfo, WHITE, BLACK); // wide icon erases annotations! + if(RSSI < -70) { + _display.fillRect(xPos, yPos, WifiIconInfo.width, 6, BLACK); + } + else if(RSSI < -55) { + _display.fillRect(xPos, yPos, WifiWideIconInfo.width, 3, BLACK); + _display.fillRect(xPos, yPos, 1, 4, BLACK); + _display.fillRect(xPos+WifiIconInfo.width-1, yPos, 1, 4, BLACK); + } + } + + _printMenuText(_display.xCentre(), 53, "\021 \020", true, eCentreJustify); + _printMenuText(_display.xCentre(), 53, "Exit", false, eCentreJustify); + return true; +} + + +bool +CWiFiSTAScreen::keyHandler(uint8_t event) +{ + if(event & keyPressed) { + // press LEFT + if(event & key_Left) { + _ScreenManager.prevMenu(); + } + // press RIGHT + if(event & key_Right) { + _ScreenManager.nextMenu(); + } + // press UP + if(event & key_Up) { + } + // press DOWN + if(event & key_Down) { + } + if(event & key_Centre) { + _ScreenManager.selectMenu(CScreenManager::RootMenuLoop); // force return to main menu + } + _ScreenManager.reqUpdate(); + } + + return true; +} + diff --git a/src/OLED/WiFiSTAScreen.h b/src/OLED/WiFiSTAScreen.h new file mode 100644 index 0000000..37a9e27 --- /dev/null +++ b/src/OLED/WiFiSTAScreen.h @@ -0,0 +1,38 @@ +/* + * This file is part of the "bluetoothheater" distribution + * (https://gitlab.com/mrjones.id.au/bluetoothheater) + * + * Copyright (C) 2018 Ray Jones + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef __WIFISTASCREEN_H__ +#define __WIFISTASCREEN_H__ + +#include +#include "Screen.h" + +class C128x64_OLED; +class CScreenManager; + +class CWiFiSTAScreen : public CScreen { +public: + CWiFiSTAScreen(C128x64_OLED& display, CScreenManager& mgr); + bool show(); + bool keyHandler(uint8_t event); +}; + +#endif diff --git a/src/OLED/fonts/Icons.cpp b/src/OLED/fonts/Icons.cpp index 38cea23..c424df6 100644 --- a/src/OLED/fonts/Icons.cpp +++ b/src/OLED/fonts/Icons.cpp @@ -1301,6 +1301,22 @@ const uint8_t PROGMEM threshIcon[] = const BITMAP_INFO threshIconInfo(9, 9, threshIcon); +// +// Image data for onOff +// + +const uint8_t PROGMEM onOffIcon[] = +{ + 0x10, // # + 0x54, // # # # + 0x92, // # # # + 0x92, // # # # + 0x82, // # # + 0x44, // # # + 0x38, // ### +}; + +const BITMAP_INFO onOffIconInfo(7, 7, onOffIcon); // // Image data for frost // diff --git a/src/OLED/fonts/Icons.h b/src/OLED/fonts/Icons.h index 862ee36..5e1eef0 100644 --- a/src/OLED/fonts/Icons.h +++ b/src/OLED/fonts/Icons.h @@ -159,5 +159,6 @@ extern const BITMAP_INFO algIconInfo; extern const BITMAP_INFO passwordIconInfo; extern const BITMAP_INFO threshIconInfo; +extern const BITMAP_INFO onOffIconInfo; extern const BITMAP_INFO frostIconInfo; extern const BITMAP_INFO humidityIconInfo; diff --git a/src/Protocol/Protocol.cpp b/src/Protocol/Protocol.cpp index e63e2f7..f30e6e7 100644 --- a/src/Protocol/Protocol.cpp +++ b/src/Protocol/Protocol.cpp @@ -238,6 +238,13 @@ CProtocol::setAltitude(float altitude) Controller.Altitude_LSB = (alt >> 0) & 0xff; } +int +CProtocol::getAltitude() const +{ + int alt = (Controller.Altitude_MSB << 8) | Controller.Altitude_LSB; + return alt; +} + void CProtocol::Init(int FrameMode) diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h index 2ea0611..fa6503b 100644 --- a/src/Protocol/Protocol.h +++ b/src/Protocol/Protocol.h @@ -163,6 +163,7 @@ public: void setTemperature_HeatExchg(uint16_t degC); // temperature of heat exchanger // altitude void setAltitude(float altitude); + int getAltitude() const; void DebugReport(const char* hdr, const char* ftr); @@ -210,6 +211,7 @@ public: float getGlow_Current() const { return Heater.getGlowPlug_Current(); }; float getSystemVoltage() const { return Controller.getSystemVoltage(); }; int getGlow_Drive() const { return Controller.getGlowDrive(); }; + int getAltitude() const { return Controller.getAltitude(); }; // void setRefTime(); void reportFrames(bool isOEM); diff --git a/src/Protocol/SmartError.cpp b/src/Protocol/SmartError.cpp index a3daea3..de460aa 100644 --- a/src/Protocol/SmartError.cpp +++ b/src/Protocol/SmartError.cpp @@ -126,6 +126,8 @@ CSmartError::monitor(uint8_t newRunState) int CSmartError::checkVolts(float ipVolts, float glowI, bool throwfault) { + const unsigned long LVCholdoffTime = 10000; + static unsigned long LVCShutdownHoldoff = 0; // check for low voltage // Native NV values here are x10 integers @@ -139,27 +141,32 @@ CSmartError::checkVolts(float ipVolts, float glowI, bool throwfault) // test low voltage cutout if(ipVolts < threshLVC) { - if(throwfault) { // only throw faults if directed to do so - _Error = 2; // internals error codes are +1 over displayed error code - requestOff(); // shut heater down + if(LVCShutdownHoldoff == 0) { + // initial detection of LVC - introduce a hold off period + DebugPort.println("LVC holdoff enagaged"); + LVCShutdownHoldoff = (millis() + LVCholdoffTime) | 1; // ensure non zero! + } + else { + long tDelta = millis() - LVCShutdownHoldoff; + if(tDelta > 0) { + LVCShutdownHoldoff = 0; + if(throwfault) { // only throw faults if directed to do so + DebugPort.println("LVC shutting heater down"); + _Error = 2; // internals error codes are +1 over displayed error code + requestOff(); // shut heater down + } + } } return 2; // Low voltage return value = 2 } + else { + if(LVCShutdownHoldoff) + DebugPort.println("LVC holdoff cancelled"); + LVCShutdownHoldoff = 0; // disable holdoff, voltage now OK + } // warning threshold float threshWarn = threshLVC + 0.5; // nominally create a warning threshold, 0.5V over LVC threhsold -/* - float alertlimit; // but always introduce it if below system voltage - if(NVstore.getHeaterTuning().sysVoltage == 120) { - alertlimit = 12.0 - cableComp; - } - else { - alertlimit = 24.0 - cableComp; - } - if(threshWarn < alertlimit) { - threshWarn = alertlimit; - } -*/ if(ipVolts < threshWarn) { return 1; // LVC OK, but below warning threshold, return code = 1 diff --git a/src/Protocol/TxManage.cpp b/src/Protocol/TxManage.cpp index f37a202..49ad8c6 100644 --- a/src/Protocol/TxManage.cpp +++ b/src/Protocol/TxManage.cpp @@ -55,6 +55,7 @@ CTxManage::CTxManage(int TxGatePin, HardwareSerial& serial) : m_BlueWireSerial(serial), m_TxFrame(CProtocol::CtrlMode) { + m_sysUpdate = 0; m_bOnReq = false; m_bOffReq = false; m_bTxPending = false; @@ -107,6 +108,12 @@ CTxManage::queueRawCommand(uint8_t val) _rawCommand = val; } +void +CTxManage::queueSysUpdate() +{ + m_sysUpdate = 10; +} + void CTxManage::PrepareFrame(const CProtocol& basisFrame, bool isBTCmaster) { @@ -139,7 +146,13 @@ CTxManage::PrepareFrame(const CProtocol& basisFrame, bool isBTCmaster) // 0x78 prevents the controller showing bum information when we parrot the OEM controller // heater is happy either way, the OEM controller has set the max/min stuff already if(isBTCmaster) { - m_TxFrame.setActiveMode(); // this allows heater to save the tuning params to EEPROM + if(m_sysUpdate) { + m_sysUpdate--; + m_TxFrame.setActiveMode(); // this allows heater to save the tuning params to EEPROM + } + else { + m_TxFrame.setPassiveMode(); // this prevents the tuning parameters being saved by heater + } m_TxFrame.setFan_Min(NVstore.getHeaterTuning().Fmin); m_TxFrame.setFan_Max(NVstore.getHeaterTuning().Fmax); m_TxFrame.setPump_Min(NVstore.getHeaterTuning().getPmin()); diff --git a/src/Protocol/TxManage.h b/src/Protocol/TxManage.h index 5070841..2b2989e 100644 --- a/src/Protocol/TxManage.h +++ b/src/Protocol/TxManage.h @@ -26,6 +26,7 @@ class CTxManage const int m_nStartDelay = 20; const int m_nFrameTime = 14; const int m_nFrontPorch = 0; + int m_sysUpdate; public: CTxManage(int TxGatePin, HardwareSerial& serial); @@ -38,6 +39,7 @@ public: void begin(); const CProtocol& getFrame() const { return m_TxFrame; }; static void GateTerminate(); + void queueSysUpdate(); // use to implant NV settings into heater private: HardwareSerial& m_BlueWireSerial; diff --git a/src/Utility/ABpreferences.cpp b/src/Utility/ABpreferences.cpp new file mode 100644 index 0000000..1ce25d9 --- /dev/null +++ b/src/Utility/ABpreferences.cpp @@ -0,0 +1,42 @@ +#include "ABpreferences.h" + +#include "nvs.h" + +const char * nvs_errors2[] = { "OTHER", "NOT_INITIALIZED", "NOT_FOUND", "TYPE_MISMATCH", "READ_ONLY", "NOT_ENOUGH_SPACE", "INVALID_NAME", "INVALID_HANDLE", "REMOVE_FAILED", "KEY_TOO_LONG", "PAGE_FULL", "INVALID_STATE", "INVALID_LENGHT"}; +#define nvs_error(e) (((e)>ESP_ERR_NVS_BASE)?nvs_errors2[(e)&~(ESP_ERR_NVS_BASE)]:nvs_errors2[0]) + +bool +ABpreferences::hasBytes(const char *key) +{ + size_t len = 0; + if(!_started || !key){ + return false; + } + esp_err_t err = nvs_get_blob(_handle, key, NULL, &len); + if(err == ESP_ERR_NVS_NOT_FOUND) { // NOT FOUND - expected! + return false; + } + if(err) { // remaining errors - print it + log_e("nvs_get_blob len fail: %s %s", key, nvs_error(err)); + return false; + } + return len != 0; +} + +bool +ABpreferences::hasString(const char *key) +{ + size_t len = 0; + if(!_started || !key){ + return false; + } + esp_err_t err = nvs_get_str(_handle, key, NULL, &len); + if(err == ESP_ERR_NVS_NOT_FOUND) { // NOT FOUND - expected! + return false; + } + if(err) { // remaining errors - print it + log_e("nvs_get_str len fail: %s %s", key, nvs_error(err)); + return false; + } + return len != 0; +} \ No newline at end of file diff --git a/src/Utility/ABpreferences.h b/src/Utility/ABpreferences.h new file mode 100644 index 0000000..36c0491 --- /dev/null +++ b/src/Utility/ABpreferences.h @@ -0,0 +1,9 @@ + + +#include + +class ABpreferences : public Preferences { +public: + bool hasBytes(const char* key); + bool hasString(const char* key); +}; \ No newline at end of file diff --git a/src/Utility/BTC_GPIO.cpp b/src/Utility/BTC_GPIO.cpp index c7b91a6..896e74c 100644 --- a/src/Utility/BTC_GPIO.cpp +++ b/src/Utility/BTC_GPIO.cpp @@ -50,12 +50,14 @@ const char* GPIOout1Names[] = { "Disabled", "Status", "User", - "Thresh" + "Thresh", + "HeaterOn" }; const char* GPIOout2Names[] = { "Disabled", "User", - "Thresh" + "Thresh", + "HeaterOn" }; const char* GPIOalgNames[] = { @@ -216,7 +218,7 @@ CGPIOin2::_doThermostat(bool active) } const char* -CGPIOin2::getExtThermTime() +CGPIOin2:: getExtThermTime() { if((_OffHoldoff == 0) || (NVstore.getUserSettings().ThermostatMethod != 3) || (NVstore.getUserSettings().ExtThermoTimeout == 0)) return NULL; @@ -406,6 +408,12 @@ CGPIOoutBase::_doThresh() } } +void +CGPIOoutBase::_doActive() +{ + int runstate = getHeaterInfo().getRunState(); // raw state, not suspend mode enhanced + digitalWrite(_pin, runstate ? HIGH : LOW); // activates output when heater is not in standby +} /********************************************************************************************************* ** GPIO out manager @@ -503,7 +511,7 @@ CGPIOout1::begin(int pin, CGPIOout1::Modes mode) void CGPIOout1::setMode(CGPIOout1::Modes mode) { - if(mode >= Disabled && mode <= Thresh) + if(mode >= Disabled && mode <= HtrActive) _Mode = mode; _prevState = -1; if(_getPin()) @@ -523,6 +531,7 @@ CGPIOout1::manage() case CGPIOout1::Status: _doStatus(); break; case CGPIOout1::User: _doUser(); break; case CGPIOout1::Thresh: _doThresh(); break; + case CGPIOout1::HtrActive: _doActive(); break; } } @@ -579,6 +588,7 @@ CGPIOout1::_doStatus() ledcAttachPin(pin, 0); // attach PWM to GPIO line ledcWrite(0, _statusState); _breatheDelay = millis() + BREATHINTERVAL; + _ledState = 2; break; case 2: ledcDetachPin(pin); // detach PWM from IO line @@ -590,11 +600,13 @@ CGPIOout1::_doStatus() _statusState = 255; ledcWrite(0, _statusState); _breatheDelay = millis() + BREATHINTERVAL; + _ledState = 3; break; case 4: ledcDetachPin(pin); // detach PWM from IO line _breatheDelay += (FLASHPERIOD - ONFLASHINTERVAL); // extended off _setPinState(LOW); + _ledState = 4; break; } } @@ -631,7 +643,7 @@ CGPIOout1::_doStopMode() // breath down PWM _statusState &= 0xff; ledcWrite(0, _statusState); } - _ledState = 2; + _ledState = 3; } void @@ -657,7 +669,7 @@ CGPIOout1::_doSuspendMode() // brief flash if(tDelta >= 0) stretch = 0; } - _ledState = stretch ? 1 : 0; + _ledState = 4; } uint8_t @@ -666,6 +678,7 @@ CGPIOout1::getState() switch(_Mode) { case User: case Thresh: + case HtrActive: return _getPinState(); case Status: return _ledState; // special pulse extender for suspend mode @@ -695,7 +708,7 @@ CGPIOout2::begin(int pin, Modes mode) void CGPIOout2::setMode(CGPIOout2::Modes mode) { - if(mode >= Disabled && mode <= Thresh) + if(mode >= Disabled && mode <= HtrActive) _Mode = mode; int pin = _getPin(); if(pin) @@ -714,6 +727,7 @@ CGPIOout2::manage() case CGPIOout2::Disabled: break; case CGPIOout2::User: _doUser(); break; case CGPIOout2::Thresh: _doThresh(); break; + case CGPIOout2::HtrActive: _doActive(); break; } } @@ -724,6 +738,7 @@ CGPIOout2::getState() switch (_Mode) { case CGPIOout2::User: case CGPIOout2::Thresh: + case CGPIOout2::HtrActive: return _getPinState(); default: return 0; diff --git a/src/Utility/BTC_GPIO.h b/src/Utility/BTC_GPIO.h index c5fc756..8ab443d 100644 --- a/src/Utility/BTC_GPIO.h +++ b/src/Utility/BTC_GPIO.h @@ -107,6 +107,7 @@ class CGPIOoutBase { bool _userState; int _pin; protected: + void _doActive(); void _doThresh(); void _doUser(); bool _getUserState(); @@ -126,7 +127,8 @@ public: Disabled, Status, User, - Thresh + Thresh, + HtrActive }; CGPIOout1(); void begin(int pin, Modes mode); @@ -152,7 +154,8 @@ public: enum Modes { Disabled, User, - Thresh + Thresh, + HtrActive, }; CGPIOout2(); void begin(int pin, Modes mode); @@ -207,8 +210,8 @@ struct sGPIOparams { }; struct sGPIO { - bool outState[2]; - bool inState[2]; + uint8_t outState[2]; + uint8_t inState[2]; int algVal; sGPIO() { outState[0] = outState[1] = false; diff --git a/src/Utility/BTC_JSON.cpp b/src/Utility/BTC_JSON.cpp index 2e88d79..2591808 100644 --- a/src/Utility/BTC_JSON.cpp +++ b/src/Utility/BTC_JSON.cpp @@ -142,26 +142,26 @@ bool makeJSONString(CModerator& moderator, char* opStr, int len) float tidyTemp = getTemperatureSensor(); tidyTemp = int(tidyTemp * 10 + 0.5) * 0.1f; // round to 0.1 resolution if(tidyTemp > -80) { - bSend |= moderator.addJson("TempCurrent", tidyTemp, root); + bSend |= moderator.addJson("TempCurrent", tidyTemp, root, 5000); } if(getTempSensor().getNumSensors() > 1) { getTempSensor().getTemperature(1, tidyTemp); tidyTemp = int(tidyTemp * 10 + 0.5) * 0.1f; // round to 0.1 resolution if(tidyTemp > -80) { - bSend |= moderator.addJson("Temp2Current", tidyTemp, root); + bSend |= moderator.addJson("Temp2Current", tidyTemp, root, 5000); } if(getTempSensor().getNumSensors() > 2) { getTempSensor().getTemperature(2, tidyTemp); tidyTemp = int(tidyTemp * 10 + 0.5) * 0.1f; // round to 0.1 resolution if(tidyTemp > -80) { - bSend |= moderator.addJson("Temp3Current", tidyTemp, root); + bSend |= moderator.addJson("Temp3Current", tidyTemp, root, 5000); } } if(getTempSensor().getNumSensors() > 3) { getTempSensor().getTemperature(3, tidyTemp); tidyTemp = int(tidyTemp * 10 + 0.5) * 0.1f; // round to 0.1 resolution if(tidyTemp > -80) { - bSend |= moderator.addJson("Temp4Current", tidyTemp, root); + bSend |= moderator.addJson("Temp4Current", tidyTemp, root, 5000); } } } @@ -170,7 +170,7 @@ bool makeJSONString(CModerator& moderator, char* opStr, int len) if(NVstore.getUserSettings().menuMode < 2) { bSend |= moderator.addJson("TempMin", getHeaterInfo().getTemperature_Min(), root); bSend |= moderator.addJson("TempMax", getHeaterInfo().getTemperature_Max(), root); - bSend |= moderator.addJson("TempBody", getHeaterInfo().getTemperature_HeatExchg(), root); + bSend |= moderator.addJson("TempBody", getHeaterInfo().getTemperature_HeatExchg(), root, 5000); bSend |= moderator.addJson("RunState", getHeaterInfo().getRunStateEx(), root); bSend |= moderator.addJson("RunString", getHeaterInfo().getRunStateStr(), root); // verbose it up! bSend |= moderator.addJson("ErrorState", getHeaterInfo().getErrState(), root ); @@ -182,13 +182,13 @@ bool makeJSONString(CModerator& moderator, char* opStr, int len) bSend |= moderator.addJson("PumpActual", getHeaterInfo().getPump_Actual(), root ); bSend |= moderator.addJson("FanMin", getHeaterInfo().getFan_Min(), root ); bSend |= moderator.addJson("FanMax", getHeaterInfo().getFan_Max(), root ); - bSend |= moderator.addJson("FanRPM", getFanSpeed(), root ); - bSend |= moderator.addJson("FanVoltage", getHeaterInfo().getFan_Voltage(), root ); + bSend |= moderator.addJson("FanRPM", getFanSpeed(), root, 2000 ); + bSend |= moderator.addJson("FanVoltage", getHeaterInfo().getFan_Voltage(), root, 2500 ); bSend |= moderator.addJson("FanSensor", getHeaterInfo().getFan_Sensor(), root ); - bSend |= moderator.addJson("InputVoltage", getBatteryVoltage(false), root ); + bSend |= moderator.addJson("InputVoltage", getBatteryVoltage(false), root, 10000 ); bSend |= moderator.addJson("SystemVoltage", getHeaterInfo().getSystemVoltage(), root ); - bSend |= moderator.addJson("GlowVoltage", getGlowVolts(), root ); - bSend |= moderator.addJson("GlowCurrent", getGlowCurrent(), root ); + bSend |= moderator.addJson("GlowVoltage", getGlowVolts(), root, 5000 ); + bSend |= moderator.addJson("GlowCurrent", getGlowCurrent(), root, 5000 ); bSend |= moderator.addJson("BluewireStat", getBlueWireStatStr(), root ); } @@ -218,9 +218,12 @@ bool makeJSONStringEx(CModerator& moderator, char* opStr, int len) bSend |= moderator.addJson("CyclicOn", NVstore.getUserSettings().cyclic.Start, root); // threshold of under temp for cyclic mode bSend |= moderator.addJson("FrostOn", NVstore.getUserSettings().FrostOn, root); // temp drops below this, auto start - 0 = disable bSend |= moderator.addJson("FrostRise", NVstore.getUserSettings().FrostRise, root); // temp rise in frost mode till auto off - bSend |= moderator.addJson("PumpCount", RTC_Store.getFuelGauge(), root); // running count of pump strokes + bSend |= moderator.addJson("PumpCount", RTC_Store.getFuelGauge(), root, 10000); // running count of pump strokes bSend |= moderator.addJson("PumpCal", NVstore.getHeaterTuning().pumpCal, root); // mL/stroke bSend |= moderator.addJson("LowVoltCutout", NVstore.getHeaterTuning().getLVC(), root); // low voltage cutout + if(getTempSensor().getBME280().getCount()) { + bSend |= moderator.addJson("HumidStart", NVstore.getUserSettings().humidityStart, root); // BME280 ONLY + } } bSend |= moderator.addJson("TempOffset", getTempSensor().getOffset(0), root); // degC offset bSend |= moderator.addJson("TempType", getTempSensor().getID(0), root); // BME280 vs DS18B20 @@ -236,6 +239,15 @@ bool makeJSONStringEx(CModerator& moderator, char* opStr, int len) bSend |= moderator.addJson("Temp4Offset", getTempSensor().getOffset(3), root); // degC offset bSend |= moderator.addJson("Temp4Type", getTempSensor().getID(3), root); // BME280 vs DS18B20 } + if(getTempSensor().getBME280().getCount()) { + + bSend |= moderator.addJson("Altitude", getHeaterInfo().getAltitude(), root, 60000); // BME280 ONLY + float humidity; + getTempSensor().getHumidity(humidity); + humidity = int(humidity * 10 + 0.5) * 0.1f; // round to 0.1 resolution + bSend |= moderator.addJson("Humidity", humidity, root, 30000); // BME280 ONLY + } + if(bSend) { root.printTo(opStr, len); } @@ -285,8 +297,8 @@ bool makeJSONStringGPIO(CModerator& moderator, char* opStr, int len) bSend |= moderator.addJson("GPmodeIn2", GPIOin2Names[info.in2Mode], root); bSend |= moderator.addJson("GPmodeOut1", GPIOout1Names[info.out1Mode], root); bSend |= moderator.addJson("GPmodeOut2", GPIOout2Names[info.out2Mode], root); - bSend |= moderator.addJson("GPOutThr1", NVstore.getUserSettings().GPIO.thresh[0], root); - bSend |= moderator.addJson("GPOutThr2", NVstore.getUserSettings().GPIO.thresh[1], root); + bSend |= moderator.addJson("GPoutThr1", NVstore.getUserSettings().GPIO.thresh[0], root); + bSend |= moderator.addJson("GPoutThr2", NVstore.getUserSettings().GPIO.thresh[1], root); bSend |= moderator.addJson("GPmodeAnlg", GPIOalgNames[info.algMode], root); bSend |= moderator.addJson("ExtThermoTmout", (uint32_t)NVstore.getUserSettings().ExtThermoTimeout, root); const char* stop = getExternalThermostatHoldTime(); @@ -311,7 +323,8 @@ bool makeJSONStringMQTT(CModerator& moderator, char* opStr, int len) sMQTTparams info = NVstore.getMQTTinfo(); bSend |= moderator.addJson("MEn", info.enabled, root); - bSend |= moderator.addJson("MOnline", isMQTTconnected(), root); + uint8_t online = isMQTTconnected() ? 1 : 0; + bSend |= moderator.addJson("MOnline", online, root); bSend |= moderator.addJson("MHost", info.host, root); bSend |= moderator.addJson("MPort", info.port, root); bSend |= moderator.addJson("MUser", info.username, root); @@ -375,6 +388,8 @@ bool makeJSONStringIP(CModerator& moderator, char* opStr, int len) bSend |= moderator.addJson("IP_STA", getWifiSTAAddrStr(), root); bSend |= moderator.addJson("IP_STAMAC", getWifiSTAMACStr(), root); bSend |= moderator.addJson("IP_STASSID", getSSID().c_str(), root); + bSend |= moderator.addJson("IP_STAGATEWAY", getWifiGatewayAddrStr(), root); + bSend |= moderator.addJson("IP_STARSSI", getWifiRSSI(), root, 10000); bSend |= moderator.addJson("IP_OTA", NVstore.getUserSettings().enableOTA, root); bSend |= moderator.addJson("BT_MAC", getBluetoothClient().getMAC(), root); @@ -518,10 +533,12 @@ void resetAllJSONmoderators() #else initJSONTimermoderator(); #endif - initJSONMQTTmoderator(); - initJSONIPmoderator(); - initJSONSysModerator(); + resetJSONMQTTmoderator(); // initJSONMQTTmoderator(); + resetJSONIPmoderator(); // initJSONIPmoderator(); + resetJSONSysModerator(); // initJSONSysModerator(); GPIOmoderator.reset(); + // create and send a validation code (then client knows AB is capable of reboot over JSON) + doJSONreboot(0); } void initJSONMQTTmoderator() @@ -575,3 +592,30 @@ void Expand(std::string& str) } } +void sendJSONtext(const char* jsonStr) +{ + sendWebSocketString( jsonStr ); + mqttPublishJSON(jsonStr); + getBluetoothClient().send( jsonStr ); +} + +void doJSONreboot(uint16_t PIN) +{ + char jsonStr[20]; + static uint16_t validate = 0; + if(PIN == 0) { + validate = random(1, 10000); + + char jsonStr[20]; + sprintf(jsonStr, "{\"Reboot\":%04d}", validate); + + sendJSONtext( jsonStr ); + } + else if(PIN == validate) { + strcpy(jsonStr, "{\"Reboot\":-1}"); + sendJSONtext( jsonStr ); + + delay(1000); + ESP.restart(); // reboot + } +} diff --git a/src/Utility/BTC_JSON.h b/src/Utility/BTC_JSON.h index 2d295b7..0a759f1 100644 --- a/src/Utility/BTC_JSON.h +++ b/src/Utility/BTC_JSON.h @@ -40,6 +40,7 @@ void resetJSONIPmoderator(); void resetJSONSysModerator(); void resetJSONMQTTmoderator(); void validateTimer(int ID); +void doJSONreboot(uint16_t code); template const char* createJSON(const char* name, T value) diff --git a/src/Utility/Moderator.h b/src/Utility/Moderator.h index ee24e37..daa116d 100644 --- a/src/Utility/Moderator.h +++ b/src/Utility/Moderator.h @@ -51,13 +51,42 @@ public: void reset(const char* name); }; +class sModeratorHoldoff { + unsigned long period; + unsigned long tripTime; +public: + sModeratorHoldoff() { + period = 0; + tripTime = 0; + } + sModeratorHoldoff(const sModeratorHoldoff& rhs) { + period = rhs.period; + tripTime = rhs.tripTime; + } + void set(unsigned long per) { + period = per; + reArm(); + } + void reArm() { + tripTime = (millis() + period) | 1; + } + bool expired() { + long tDelta = millis() - tripTime; + return tDelta >= 0; + } + void expire() { + tripTime = millis(); + } +}; template class TModerator { std::map Memory; + std::map _holdoff; public: bool shouldSend(const char* name, T value); - bool addJson(const char* name, T value, JsonObject& root); + void setHoldoff(const char* name, unsigned long period); + bool addJson(const char* name, T value, JsonObject& root, unsigned long holdoff=0); void reset(); void reset(const char* name); }; @@ -69,7 +98,21 @@ bool TModerator::shouldSend(const char* name, T value) auto it = Memory.find(name); if(it != Memory.end()) { retval = it->second != value; - it->second = value; + if(retval) { + // check if a minimum refresh interval has been defined + auto holdoff = _holdoff.find(name); + if(holdoff != _holdoff.end()) { + if(holdoff->second.expired()) { + holdoff->second.reArm(); + } + else { + retval = false; + } + } + } + if(retval) { + it->second = value; + } } else { Memory[name] = value; @@ -78,11 +121,26 @@ bool TModerator::shouldSend(const char* name, T value) } template -bool TModerator::addJson(const char* name, T value, JsonObject& root) +void TModerator::setHoldoff(const char* name, unsigned long period) { + if(period) { + auto it = _holdoff.find(name); + if(it == _holdoff.end()) { + sModeratorHoldoff holdoff; + holdoff.set(period); + _holdoff[name] = holdoff; + } + } +} + +template +bool TModerator::addJson(const char* name, T value, JsonObject& root, unsigned long holdoff) +{ + setHoldoff(name, holdoff); bool retval = shouldSend(name, value); - if(retval) + if(retval) { root.set(name, value); + } return retval; } @@ -93,15 +151,26 @@ void TModerator::reset() for(auto it = Memory.begin(); it != Memory.end(); ++it) { it->second = it->second+100; } + for(auto it = _holdoff.begin(); it != _holdoff.end(); ++it) { + it->second.expire(); + } } template void TModerator::reset(const char* name) { - auto it = Memory.find(name); - if(it != Memory.end()) { - DebugPort.printf("Resetting moderator: \"%s\"", name); - it->second = it->second+100; + { + auto it = Memory.find(name); + if(it != Memory.end()) { + DebugPort.printf("Resetting moderator: \"%s\"", name); + it->second = it->second+100; + } + } + { + auto it = _holdoff.find(name); + if(it != _holdoff.end()) { + it->second.expire(); + } } } @@ -123,8 +192,8 @@ public: return u32Moderator.addJson(name, value, root); }; // float values - bool addJson(const char* name, float value, JsonObject& root) { - return fModerator.addJson(name, value, root); + bool addJson(const char* name, float value, JsonObject& root, unsigned long holdoff=0) { + return fModerator.addJson(name, value, root, holdoff); }; // uint8_t values bool addJson(const char* name, uint8_t value, JsonObject& root) { diff --git a/src/Utility/NVCore.cpp b/src/Utility/NVCore.cpp index f2202e9..ff88582 100644 --- a/src/Utility/NVCore.cpp +++ b/src/Utility/NVCore.cpp @@ -24,6 +24,7 @@ #include "DebugPort.h" #include #include +#include #define INBOUNDS(TST, MIN, MAX) (((TST) >= (MIN)) && ((TST) <= (MAX))) @@ -31,20 +32,31 @@ bool CESP32_NVStorage::validatedLoad(const char* key, char* val, int maxlen, const char* defVal) { - char probe[128]; bool retval = true; - strcpy(probe, "TestPresence"); - int len = preferences.getString(key, probe, 127); - if(len == 0 || strcmp(probe, "TestPresence") == 0) { + if(!preferences.hasString(key)) { preferences.putString(key, defVal); - DebugPort.printf("CESP32HeaterStorage::validatedLoad default installed %s=%s", key, defVal); - retval = false; + DebugPort.printf("CESP32HeaterStorage::validatedLoad default installed %s=%s\r\n", key, defVal); } preferences.getString(key, val, maxlen); val[maxlen] = 0; // ensure null terminated return retval; } +bool +CESP32_NVStorage::validatedLoad(const char* key, uint8_t* val, int len) +{ + bool retval = true; + if(!preferences.hasBytes(key)) { + DebugPort.printf("CESP32HeaterStorage::validatedLoad default installed for %s\r\n", key); + preferences.putBytes(key, val, len); + } + len = preferences.getBytes(key, val, len); + if(len == 0) { + retval = false; + } + return retval; +} + bool CESP32_NVStorage::validatedLoad(const char* key, uint8_t& val, uint8_t defVal, std::function validator, uint8_t min, uint8_t max, uint8_t mask) { @@ -129,19 +141,42 @@ CESP32_NVStorage::validatedLoad(const char* key, uint32_t& val, uint32_t defVal, bool CESP32_NVStorage::validatedLoad(const char* key, float& val, float defVal, float min, float max) { - val = preferences.getFloat(key, defVal); +// preferences.getFloat() does not do a default value for use +// use some skull duggery via unsigned long to get one installed + unsigned long* pUL = (unsigned long*)&defVal; // point to bytes of float default value as a long + unsigned long ULVal = *pUL; // copy as an unsigned long + + unsigned long tmpVal = preferences.getULong(key, ULVal); + +// pUL = (unsigned long*)&val; // point to val we exchange, as an usigned long + float* ptmpVal = (float*)&tmpVal; // create a pointer to flaot, that was our UL returned value + float* pVal = &val; // point to our FP exchange value + *pVal = *ptmpVal; // copy as a float + +// val = preferences.getFloat(key, defVal); if(!INBOUNDS(val, min, max)) { DebugPort.printf("CESP32HeaterStorage::validatedLoad invalid read %s=%f", key, val); DebugPort.printf(" validator(%f,%f) reset to %f\r\n", min, max, defVal); val = defVal; - preferences.putFloat(key, val); +// preferences.putFloat(key, val); + pUL = (unsigned long*)&val; // point to bytes of float default value as a long + ULVal = *pUL; // copy as an unsigned long + preferences.putULong(key, ULVal); return false; } return true; } +size_t +CESP32_NVStorage::saveFloat(const char* key, float val) +{ + unsigned long* pUL = (unsigned long*)&val; // point to bytes of float default value as a long + unsigned long ULVal = *pUL; // copy as an unsigned long + return preferences.putULong(key, ULVal); +} + bool finBounds(float test, float minLim, float maxLim) { return INBOUNDS(test, minLim, maxLim); diff --git a/src/Utility/NVCore.h b/src/Utility/NVCore.h index aff8969..1bf6e67 100644 --- a/src/Utility/NVCore.h +++ b/src/Utility/NVCore.h @@ -22,7 +22,7 @@ #ifndef __BTC_NV_CORE_H__ #define __BTC_NV_CORE_H__ -#include +#include "ABpreferences.h" #include @@ -46,15 +46,17 @@ class CNVStorage { class CESP32_NVStorage { protected: - Preferences preferences; + ABpreferences preferences; protected: bool validatedLoad(const char* key, int8_t& val, int8_t defVal, std::function validator, int8_t min, int8_t max); bool validatedLoad(const char* key, uint8_t& val, uint8_t defVal, std::function validator, uint8_t min, uint8_t max, uint8_t mask=0xff); bool validatedLoad(const char* key, uint16_t& val, uint16_t defVal, std::function validator, uint16_t min, uint16_t max); bool validatedLoad(const char* key, long& val, long defVal, long min, long max); - bool validatedLoad(const char* key, char* val, int maxlen, const char* defVal); + bool validatedLoad(const char* key, char* val, int maxlen, const char* defVal); // string + bool validatedLoad(const char* key, uint8_t* val, int len); // bytes bool validatedLoad(const char* key, float& val, float defVal, float min, float max); bool validatedLoad(const char* key, uint32_t& val, uint32_t defVal, uint32_t min, uint32_t max); + size_t saveFloat(const char* key, float val); }; diff --git a/src/Utility/NVStorage.cpp b/src/Utility/NVStorage.cpp index 31de48b..8ead09f 100644 --- a/src/Utility/NVStorage.cpp +++ b/src/Utility/NVStorage.cpp @@ -23,7 +23,7 @@ #include "NVStorage.h" #include "DebugPort.h" #include - +#include bool sNVStore::valid() @@ -131,6 +131,10 @@ CHeaterStorage::getMQTTinfo() const void CHeaterStorage::setMQTTinfo(const sMQTTparams& info) { + if(_calValues.MQTT != info) { + requestMQTTrestart(); + } + _calValues.MQTT = info; } @@ -268,13 +272,18 @@ sHeaterTuning::load() validatedLoad("tempOffset0", DS18B20probe[0].offset, 0.0, -10.0, +10.0); validatedLoad("tempOffset1", DS18B20probe[1].offset, 0.0, -10.0, +10.0); validatedLoad("tempOffset2", DS18B20probe[2].offset, 0.0, -10.0, +10.0); - preferences.getBytes("probeSerial0", DS18B20probe[0].romCode.bytes, 8); - preferences.getBytes("probeSerial1", DS18B20probe[1].romCode.bytes, 8); - preferences.getBytes("probeSerial2", DS18B20probe[2].romCode.bytes, 8); + memset(DS18B20probe[0].romCode.bytes, 0, 8); + memset(DS18B20probe[1].romCode.bytes, 0, 8); + memset(DS18B20probe[2].romCode.bytes, 0, 8); + validatedLoad("probeSerial0", DS18B20probe[0].romCode.bytes, 8); + validatedLoad("probeSerial1", DS18B20probe[1].romCode.bytes, 8); + validatedLoad("probeSerial2", DS18B20probe[2].romCode.bytes, 8); validatedLoad("tempOffsetBME", BME280probe.offset, 0.0, -10.0, +10.0); validatedLoad("probeBMEPrmy", BME280probe.bPrimary, 0, u8inBounds, 0, 1); preferences.end(); +// save(); + // for(int i=0; i<3; i++) { // DebugPort.printf("Rd Probe[%d] %02X:%02X:%02X:%02X:%02X:%02X\r\n", // i, @@ -291,6 +300,20 @@ sHeaterTuning::load() void sHeaterTuning::save() { + sHeaterTuning currentSettings; + currentSettings.load(); + + bool requestHeaterUpdate = false; + requestHeaterUpdate |= currentSettings.sysVoltage != this->sysVoltage; + requestHeaterUpdate |= currentSettings.fanSensor != this->fanSensor; + requestHeaterUpdate |= currentSettings.glowDrive != this->glowDrive; + requestHeaterUpdate |= currentSettings.Pmin != this->Pmin; + requestHeaterUpdate |= currentSettings.Pmax != this->Pmax; + requestHeaterUpdate |= currentSettings.Fmin != this->Fmin; + requestHeaterUpdate |= currentSettings.Fmax != this->Fmax; + if(requestHeaterUpdate) { + reqHeaterCalUpdate(); + } // section for heater calibration params // **** MAX LENGTH is 15 for names **** preferences.begin("Calibration", false); @@ -302,15 +325,55 @@ sHeaterTuning::save() preferences.putUChar("fanSensor", fanSensor); preferences.putUChar("glowDrive", glowDrive); preferences.putUChar("lowVolts", lowVolts); - preferences.putFloat("pumpCal", pumpCal); - preferences.putFloat("tempOffset0", DS18B20probe[0].offset); - preferences.putFloat("tempOffset1", DS18B20probe[1].offset); - preferences.putFloat("tempOffset2", DS18B20probe[2].offset); + saveFloat("pumpCal", pumpCal); + saveFloat("tempOffset0", DS18B20probe[0].offset); + saveFloat("tempOffset1", DS18B20probe[1].offset); + saveFloat("tempOffset2", DS18B20probe[2].offset); + // preferences.putFloat("pumpCal", pumpCal); + // preferences.putFloat("tempOffset0", DS18B20probe[0].offset); + // preferences.putFloat("tempOffset1", DS18B20probe[1].offset); + // preferences.putFloat("tempOffset2", DS18B20probe[2].offset); + + /*// START TESTO + for(int i=0; i<8; i++) + DS18B20probe[0].romCode.bytes[i] = i; + // END TESTO*/ + preferences.putBytes("probeSerial0", DS18B20probe[0].romCode.bytes, 8); preferences.putBytes("probeSerial1", DS18B20probe[1].romCode.bytes, 8); preferences.putBytes("probeSerial2", DS18B20probe[2].romCode.bytes, 8); - preferences.putFloat("tempOffsetBME", BME280probe.offset); + // preferences.putFloat("tempOffsetBME", BME280probe.offset); + saveFloat("tempOffsetBME", BME280probe.offset); preferences.putUChar("probeBMEPrmy", BME280probe.bPrimary); + + + /*// START TESTO + preferences.getBytes("probeSerial0", DS18B20probe[0].romCode.bytes, 8); + DebugPort.printf("getBytes %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", + DS18B20probe[0].romCode.bytes[0], + DS18B20probe[0].romCode.bytes[1], + DS18B20probe[0].romCode.bytes[2], + DS18B20probe[0].romCode.bytes[3], + DS18B20probe[0].romCode.bytes[4], + DS18B20probe[0].romCode.bytes[5], + DS18B20probe[0].romCode.bytes[6], + DS18B20probe[0].romCode.bytes[7] + ); + + uint64_t tVal = preferences.getULong64("probeSerial0", 0xffffffffffffffff); + unsigned char* pTst = (unsigned char*)&tVal; + DebugPort.printf("getULong %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", + pTst[0], + pTst[1], + pTst[2], + pTst[3], + pTst[4], + pTst[5], + pTst[6], + pTst[7] + ); + // END TESTO*/ + preferences.end(); // for(int i=0; i<3; i++) { @@ -417,8 +480,8 @@ sUserSettings::load() preferences.putUChar("GPIOout1Mode", GPIO.out1Mode); // set new preferences.putUChar("GPIOout2Mode", GPIO.out2Mode); // set new } - validatedLoad("GPIOout1Mode", tVal, 0, u8inBounds, 0, 3); GPIO.out1Mode = (CGPIOout1::Modes)tVal; - validatedLoad("GPIOout2Mode", tVal, 0, u8inBounds, 0, 2); GPIO.out2Mode = (CGPIOout2::Modes)tVal; + validatedLoad("GPIOout1Mode", tVal, 0, u8inBounds, 0, 4); GPIO.out1Mode = (CGPIOout1::Modes)tVal; + validatedLoad("GPIOout2Mode", tVal, 0, u8inBounds, 0, 3); GPIO.out2Mode = (CGPIOout2::Modes)tVal; validatedLoad("GPIOout1Thresh", GPIO.thresh[0], 0, s8inBounds, -50, 50); validatedLoad("GPIOout2Thresh", GPIO.thresh[1], 0, s8inBounds, -50, 50); validatedLoad("GPIOalgMode", tVal, 0, u8inBounds, 0, 2); GPIO.algMode = (CGPIOalg::Modes)tVal; @@ -447,7 +510,8 @@ sUserSettings::save() preferences.putUChar("thermostat", useThermostat); preferences.putUChar("degF", degF); preferences.putUChar("thermoMethod", ThermostatMethod); - preferences.putFloat("thermoWindow", ThermostatWindow); +// preferences.putFloat("thermoWindow", ThermostatWindow); + saveFloat("thermoWindow", ThermostatWindow); preferences.putUChar("frostOn", FrostOn); preferences.putUChar("frostRise", FrostRise); // preferences.putUChar("enableWifi", enableWifi); @@ -484,9 +548,9 @@ sMQTTparams::load() validatedLoad("enabled", enabled, 0, u8inBounds, 0, 1); validatedLoad("port", port, 1883, u16inBounds, 0, 0xffff); validatedLoad("qos", qos, 0, u8inBounds, 0, 2); - validatedLoad("host", host, 127, ""); - validatedLoad("username", username, 31, ""); - validatedLoad("password", password, 31, ""); + validatedLoad("host", host, 127, "broker"); + validatedLoad("username", username, 31, "username"); + validatedLoad("password", password, 31, "password"); validatedLoad("topic", topicPrefix, 31, "Afterburner"); preferences.end(); } @@ -572,4 +636,4 @@ sHourMeter::load() validatedLoad("GlowTime", GlowTime, 0, 0, 0xffffffffL); preferences.end(); DebugPort.printf("Hourmeter NV read: Run=%d, Glow=%d\r\n", RunTime, GlowTime); -} \ No newline at end of file +} diff --git a/src/Utility/NVStorage.h b/src/Utility/NVStorage.h index 009db5c..7f6a191 100644 --- a/src/Utility/NVStorage.h +++ b/src/Utility/NVStorage.h @@ -272,6 +272,16 @@ struct sMQTTparams : public CESP32_NVStorage { topicPrefix[31] = 0; return *this; } + bool operator!=(const sMQTTparams& rhs) { + bool retval = false; + retval |= enabled != rhs.enabled; + retval |= port != rhs.port; + retval |= qos != rhs.qos; + retval |= strcmp(host, rhs.host) != 0; + retval |= strcmp(password, rhs.password) != 0; + retval |= strcmp(topicPrefix, rhs.topicPrefix) != 0; + return retval; + } void load(); void save(); bool valid(); diff --git a/src/Utility/TempSense.cpp b/src/Utility/TempSense.cpp index 615e468..d602db7 100644 --- a/src/Utility/TempSense.cpp +++ b/src/Utility/TempSense.cpp @@ -432,7 +432,7 @@ CBME280Sensor::getTemperature(float& tempReading, bool filtered) } CSensor::getTemperature(tempReading, filtered); - tempReading += NVstore.getHeaterTuning().BME280probe.offset;; +// tempReading += NVstore.getHeaterTuning().BME280probe.offset;; return true; } @@ -559,16 +559,26 @@ CTempSense::setOffset(int usrIdx, float offset) bool CTempSense::getTemperature(int usrIdx, float& temperature, bool filtered) { + bool bRetVal = false; + float offset = 0; switch(getSensorType(usrIdx)) { case 0: - return BME280.getTemperature(temperature, filtered); + bRetVal = BME280.getTemperature(temperature, filtered); + offset = getOffset(usrIdx); + break; case 1: - return DS18B20.getTemperature(usrIdx, temperature, filtered); + bRetVal = DS18B20.getTemperature(usrIdx, temperature, filtered); + offset = getOffset(usrIdx); + break; case 2: - return DS18B20.getTemperature(usrIdx-1, temperature, filtered); - default: - return false; + bRetVal = DS18B20.getTemperature(usrIdx-1, temperature, filtered); + offset = getOffset(usrIdx-1); + break; } + if(bRetVal) { + temperature += offset; + } + return bRetVal; } int diff --git a/src/Utility/UtilClasses.cpp b/src/Utility/UtilClasses.cpp index e4bbe4f..a673df5 100644 --- a/src/Utility/UtilClasses.cpp +++ b/src/Utility/UtilClasses.cpp @@ -422,10 +422,22 @@ void DecodeCmd(const char* cmd, String& payload) else if(strcmp("FrostRise", cmd) == 0) { sUserSettings us = NVstore.getUserSettings(); us.FrostRise = payload.toInt(); - if(INBOUNDS(us.FrostRise, 1, 30)) { + if(INBOUNDS(us.FrostRise, 0, 30)) { NVstore.setUserSettings(us); NVstore.save(); } } + else if(strcmp("HumidStart", cmd) == 0) { + sUserSettings us = NVstore.getUserSettings(); + us.humidityStart = payload.toInt(); + if((us.humidityStart == 0) || INBOUNDS(us.humidityStart, 50, 100)) { + NVstore.setUserSettings(us); + NVstore.save(); + } + } + else if(strcmp("Reboot", cmd) == 0) { + int16_t code = payload.toInt(); + doJSONreboot(code); + } } diff --git a/src/Utility/helpers.h b/src/Utility/helpers.h index eb473d4..6b089a4 100644 --- a/src/Utility/helpers.h +++ b/src/Utility/helpers.h @@ -90,6 +90,7 @@ extern int sysUptime(); extern void doJSONwatchdog(int topup); extern void reloadScreens(); extern CTempSense& getTempSensor() ; +extern void reqHeaterCalUpdate(); void setSSID(const char* name); @@ -99,5 +100,6 @@ extern void ShowOTAScreen(int percent=0, eOTAmodes updateType=eOTAnormal); extern void updateMQTT(); extern void refreshMQTT(); +extern void requestMQTTrestart(); #endif \ No newline at end of file diff --git a/src/WiFi/ABMQTT.cpp b/src/WiFi/ABMQTT.cpp index fcdac19..8cfd346 100644 --- a/src/WiFi/ABMQTT.cpp +++ b/src/WiFi/ABMQTT.cpp @@ -51,7 +51,8 @@ extern void DecodeCmd(const char* cmd, String& payload); AsyncMqttClient MQTTclient; char topicnameJSONin[128]; char topicnameCmd[128]; -CModerator MQTTmoderator; +CModerator MQTTmoderator; // for basic MQTT interface +unsigned long MQTTrestart = 0; void subscribe(const char* topic); @@ -100,14 +101,10 @@ void onMqttConnect(bool sessionPresent) sprintf(statusTopic, "%s/status", NVstore.getMQTTinfo().topicPrefix); sprintf(topicnameJSONin, "%s/JSONin", NVstore.getMQTTinfo().topicPrefix); sprintf(topicnameCmd, "%s/cmd/#", NVstore.getMQTTinfo().topicPrefix); - // subscribe to that topic - // DebugPort.printf("MQTT: Subscribing to \"%s\"\r\n", topicnameJSONin); - // MQTTclient.subscribe(topicnameJSONin, NVstore.getMQTTinfo().qos); - // MQTTclient.subscribe(topicnameCmd, NVstore.getMQTTinfo().qos); - // MQTTclient.subscribe(statusTopic, NVstore.getMQTTinfo().qos); - subscribe(topicnameJSONin); - subscribe(topicnameCmd); - subscribe(statusTopic); + + subscribe(topicnameJSONin); // subscribe to the JSONin topic + subscribe(topicnameCmd); // subscribe to the basic command topic + subscribe(statusTopic); // subscribe to the status topic // spit out an "I'm here" message MQTTclient.publish(statusTopic, NVstore.getMQTTinfo().qos, true, "online"); @@ -152,7 +149,7 @@ void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties } else if(strcmp(topic, statusTopic) == 0) { // check if incoming topic is our general status if(strcmp(szPayload, "1") == 0) { - MQTTmoderator.reset(); + // MQTTmoderator.reset(); MQTTclient.publish(statusTopic, NVstore.getMQTTinfo().qos, true, "online"); } } @@ -199,9 +196,11 @@ bool mqttInit() #else mqttReconnect = 0; #endif + MQTTrestart = 0; memset(topicnameJSONin, 0, sizeof(topicnameJSONin)); + DebugPort.println("MQTT: Initialising..."); MQTTclient.disconnect(true); long escape = millis() + 10000; while(MQTTclient.connected()) { @@ -274,6 +273,16 @@ void kickMQTT() { void doMQTT() { + // manage restart of MQTT + if(MQTTrestart) { + long tDelta = millis() - MQTTrestart; + if(tDelta > 0) { + MQTTrestart = 0; + mqttInit(); + // connectToMqtt(); + } + } + // most MQTT is managed via callbacks!!! if(NVstore.getMQTTinfo().enabled) { #ifndef USE_RTOS_MQTTTIMER @@ -411,4 +420,9 @@ void subscribe(const char* topic) MQTTclient.subscribe(topic, NVstore.getMQTTinfo().qos); } +void requestMQTTrestart() +{ + MQTTrestart = (millis() + 1000) | 1; +} + #endif \ No newline at end of file diff --git a/src/WiFi/BTCWebServer.cpp b/src/WiFi/BTCWebServer.cpp index 69ec06d..574af78 100644 --- a/src/WiFi/BTCWebServer.cpp +++ b/src/WiFi/BTCWebServer.cpp @@ -162,6 +162,7 @@ String getContentType(String filename) { // convert the file extension to the MI bool handleFileRead(String path) { // send the right file to the client (if it exists) DebugPort.println("handleFileRead: " + path); if (path.endsWith("/")) path += "index.html"; // If a folder is requested, send the index file + path.replace("%20", " "); // convert HTML spaces to normal spaces String contentType = getContentType(path); // Get the MIME type String pathWithGz = path + ".gz"; if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) { // If the file exists as a compressed archive, or normal @@ -622,8 +623,10 @@ void listSPIFFS(const char * dirname, uint8_t levels, String& HTMLreport, int wi String ers; String rename; if(withHTMLanchors == 2) { - rename = ""; - ers = ""; + String htmlNm = fn; + htmlNm.replace(" ", "%20"); + rename = ""; + ers = ""; } if(withHTMLanchors) { String fn2; @@ -638,6 +641,7 @@ void listSPIFFS(const char * dirname, uint8_t levels, String& HTMLreport, int wi fn2.remove(fn2.length()-3, 3); // strip trailing ".gz" } if(fn2.length() != 0) { + fn2.replace(" ", "%20"); // create hyperlink if web page file fn = "" + file.name() + ""; } @@ -678,6 +682,7 @@ void addTableData(String& HTML, String dta) void onErase() { String filename = server.arg("filename"); // get request argument value by name + filename.replace("%20", " "); // convert HTML spaces to real spaces if(filename.length() != 0) { DebugPort.printf("onErase: %s ", filename.c_str()); @@ -986,6 +991,8 @@ void onRename() DebugPort.println("WEB: POST /reboot"); String oldname = server.arg("oldname"); // get request argument value by name String newname = server.arg("newname"); // get request argument value by name + newname.replace("%20", " "); // convert html spaces to real spaces + oldname.replace("%20", " "); if(oldname != "" && newname != "") { DebugPort.printf("Renaming %s to %s\r\n", oldname.c_str(), newname.c_str()); SPIFFS.rename(oldname.c_str(), newname.c_str()); diff --git a/src/WiFi/BTCWifi.cpp b/src/WiFi/BTCWifi.cpp index aaa89d0..9b0f640 100644 --- a/src/WiFi/BTCWifi.cpp +++ b/src/WiFi/BTCWifi.cpp @@ -279,6 +279,38 @@ const char* getWifiSTAAddrStr() else return ""; } + +const char* getWifiGatewayAddrStr() +{ + if(NVstore.getUserSettings().wifiMode) { + IPAddress IPaddr = WiFi.gatewayIP(); // use stepping stone - function returns an automatic stack var - LAME! + static char GWIPaddr[16]; + sprintf(GWIPaddr, "%d.%d.%d.%d", IPaddr[0], IPaddr[1], IPaddr[2], IPaddr[3]); + return GWIPaddr; + } + else + return ""; +} + +int8_t getWifiRSSI() +{ + if(NVstore.getUserSettings().wifiMode) { + static unsigned long updateRSSI = millis() + 2500; + static int8_t RSSI = 0; + long tDelta = millis() - updateRSSI; + if(tDelta > 0) { + updateRSSI = millis() + 2500; + RSSI = WiFi.RSSI(); + } + return RSSI; + } + else { + return 0; + } +} + + + const char* getWifiAPMACStr() { diff --git a/src/WiFi/BTCWifi.h b/src/WiFi/BTCWifi.h index bff901b..4f8d93f 100644 --- a/src/WiFi/BTCWifi.h +++ b/src/WiFi/BTCWifi.h @@ -28,8 +28,10 @@ void doWiFiManager(); bool initWifi(const char *failedssid, const char *failedpassword); const char* getWifiAPAddrStr(); const char* getWifiSTAAddrStr(); +const char* getWifiGatewayAddrStr(); const char* getWifiAPMACStr(); const char* getWifiSTAMACStr(); +int8_t getWifiRSSI(); String getSSID(); bool isWifiConnected(); diff --git a/src/WiFi/BrowserUpload.cpp b/src/WiFi/BrowserUpload.cpp index 1682ea7..ae8a61b 100644 --- a/src/WiFi/BrowserUpload.cpp +++ b/src/WiFi/BrowserUpload.cpp @@ -63,6 +63,8 @@ sBrowserUpload::begin(String& filename, int filesize) } } else { + SrcFile.name.replace("%20", " "); // convert HTML spaces to real spaces + // SPIFFS UPLOAD START DebugPort.printf("Starting SPIFFS upload: %s\r\n", SrcFile.name.c_str());