diff --git a/platformio.ini b/platformio.ini index 8fec70c..866c2af 100644 --- a/platformio.ini +++ b/platformio.ini @@ -32,6 +32,7 @@ extra_scripts = post:add_CRC.py ; replace shitty Arduino millis with a linear time version build_flags = -Wl,--wrap,millis + -DHTTPS_LOGLEVEL=2 debug_tool = esp-prog ;upload_protocol = esp-prog debug_init_break = \ No newline at end of file diff --git a/src/Afterburner.cpp b/src/Afterburner.cpp index 5504ea4..871c28c 100644 --- a/src/Afterburner.cpp +++ b/src/Afterburner.cpp @@ -134,7 +134,7 @@ const int FirmwareRevision = 32; const int FirmwareSubRevision = 0; const int FirmwareMinorRevision = 6; -const char* FirmwareDate = "26 Apr 2020"; +const char* FirmwareDate = "12 May 2020"; /* * Macro to check the outputs of TWDT functions and trigger an abort if an @@ -158,6 +158,7 @@ const char* FirmwareDate = "26 Apr 2020"; bool validateFrame(const CProtocol& frame, const char* name); void checkDisplayUpdate(); void checkDebugCommands(); +void manageStopStartMode(); void manageCyclicMode(); void manageFrostMode(); void manageHumidity(); @@ -315,10 +316,10 @@ void checkBlueWireEvents() } // trap being in state 0 with a heater error - cancel user on memory to avoid unexpected cyclic restarts - if(RTC_Store.getCyclicEngaged() && (BlueWireRxData.getRunState() == 0) && (BlueWireRxData.getErrState() > 1)) { + if(RTC_Store.getUserStart() && (BlueWireRxData.getRunState() == 0) && (BlueWireRxData.getErrState() > 1)) { DebugPort.println("Forcing cyclic cancel due to error induced shutdown"); // DebugPort.println("Forcing cyclic cancel due to error induced shutdown"); - RTC_Store.setCyclicEngaged(false); + RTC_Store.setUserStart(false); } pHourMeter->monitor(BlueWireRxData); @@ -553,8 +554,7 @@ void setup() { RTC_Store.begin(); FuelGauge.init(RTC_Store.getFuelGauge()); -// bCyclicEngaged = RTC_Store.getCyclicEngaged(); - DebugPort.printf("Previous cyclic active = %d\r\n", RTC_Store.getCyclicEngaged()); // state flag required for cyclic mode to persist properly after a WD reboot :-) + DebugPort.printf("Previous user start = %d\r\n", RTC_Store.getUserStart()); // state flag required for cyclic mode to persist properly after a WD reboot :-) pHourMeter = new CHourMeter(persistentRunTime, persistentGlowTime); // persistent vars passed by reference so they can be valid after SW reboots pHourMeter->init(bESP32PowerUpInit || RTC_Store.getBootInit()); // ensure persistent memory variable are reset after powerup, or OTA update @@ -665,6 +665,7 @@ bool checkTemperatureSensors() manageCyclicMode(); manageFrostMode(); manageHumidity(); + manageStopStartMode(); } } else { @@ -679,10 +680,31 @@ bool checkTemperatureSensors() return false; } +void manageStopStartMode() +{ + if(NVstore.getUserSettings().ThermostatMethod == 4 && RTC_Store.getUserStart() ) { + float deltaT = getTemperatureSensor() - CDemandManager::getDegC(); + float thresh = NVstore.getUserSettings().ThermostatWindow/2; + int heaterState = getHeaterInfo().getRunState(); // native heater state + if(deltaT > thresh) { + if(heaterState > 0 && heaterState <= 5) { + DebugPort.printf("STOP START MODE: Stopping heater, deltaT > +%.1f\r\n", thresh); + heaterOff(); // over temp - request heater stop + } + } + if(deltaT < -thresh) { + if(heaterState == 0) { + DebugPort.printf("STOP START MODE: Restarting heater, deltaT <%.1f\r\n", thresh); + heaterOn(); // under temp, start heater again + } + } + } +} + void manageCyclicMode() { const sCyclicThermostat& cyclic = NVstore.getUserSettings().cyclic; - if(cyclic.Stop && RTC_Store.getCyclicEngaged()) { // cyclic mode enabled, and user has started heater + if(cyclic.Stop && RTC_Store.getUserStart()) { // cyclic mode enabled, and user has started heater int stopDeltaT = cyclic.Stop + 1; // bump up by 1 degree - no point invoking at 1 deg over! float deltaT = getTemperatureSensor() - CDemandManager::getDegC(); // DebugPort.printf("Cyclic=%d bUserOn=%d deltaT=%d\r\n", cyclic, bUserON, deltaT); @@ -725,7 +747,7 @@ void manageFrostMode() 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 + RTC_Store.setUserStart(true); // enable cyclic mode if user stop heaterOn(); } } @@ -735,7 +757,7 @@ void manageFrostMode() 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 + RTC_Store.setUserStart(false); // for cyclic mode } } } @@ -768,7 +790,7 @@ requestOn() } bool LVCOK = 2 != SmartError.checkVolts(FilteredSamples.FastipVolts.getValue(), FilteredSamples.FastGlowAmps.getValue()); if(hasHtrData() && LVCOK) { - RTC_Store.setCyclicEngaged(true); // for cyclic mode + RTC_Store.setUserStart(true); // for cyclic mode RTC_Store.setFrostOn(false); // cancel frost mode // only start if below appropriate temperature threshold, raised for cyclic mode // int denied = checkStartTemp(); @@ -795,7 +817,7 @@ void requestOff() { DebugPort.println("Stop Request!"); heaterOff(); - RTC_Store.setCyclicEngaged(false); // for cyclic mode + RTC_Store.setUserStart(false); // for cyclic mode RTC_Store.setFrostOn(false); // cancel active frost mode CTimerManager::cancelActiveTimer(); } @@ -1022,9 +1044,9 @@ int getSmartError() return SmartError.getError(); } -bool isCyclicActive() +bool isCyclicStopStartActive() { - return RTC_Store.getCyclicEngaged() && NVstore.getUserSettings().cyclic.isEnabled(); + return RTC_Store.getUserStart() && (NVstore.getUserSettings().cyclic.isEnabled() || NVstore.getUserSettings().ThermostatMethod == 4); } void setupGPIO() diff --git a/src/OLED/ScreenManager.cpp b/src/OLED/ScreenManager.cpp index e9622de..9f08485 100644 --- a/src/OLED/ScreenManager.cpp +++ b/src/OLED/ScreenManager.cpp @@ -292,6 +292,7 @@ CScreenManager::CScreenManager() _pRebootScreen = NULL; _bDimmed = false; _bReload = true; + _OTAholdoff = 0; } CScreenManager::~CScreenManager() @@ -471,9 +472,25 @@ CScreenManager::_loadScreens() showSplash(); } +bool +CScreenManager::_checkOTAholdoff() +{ + if(_OTAholdoff) { + long tDelta = millis() - _OTAholdoff; + if(tDelta < 0) + return false; + _pDisplay->clearDisplay(); + _pDisplay->display(); // blank screen + _OTAholdoff = 0; + } + return true; +} bool CScreenManager::checkUpdate() { + if(!_checkOTAholdoff()) + return false; + if(_bReload) _loadScreens(); @@ -585,6 +602,9 @@ CScreenManager::reqUpdate() bool CScreenManager::animate() { + if(!_checkOTAholdoff()) + return false; + if(_pRebootScreen) return false; @@ -733,6 +753,7 @@ CScreenManager::showOTAMessage(int percent, eOTAmodes updateType) static int prevPercent = -1; if(percent != prevPercent) { + DebugPort.printf("%d%%\r\n", percent); prevPercent = percent; _pDisplay->clearDisplay(); if(percent < 0) @@ -759,6 +780,7 @@ CScreenManager::showOTAMessage(int percent, eOTAmodes updateType) } _pDisplay->display(); } + _OTAholdoff = millis() + 1000; } void diff --git a/src/OLED/ScreenManager.h b/src/OLED/ScreenManager.h index 01d2e71..0f3ad63 100644 --- a/src/OLED/ScreenManager.h +++ b/src/OLED/ScreenManager.h @@ -33,6 +33,7 @@ class CScreenManager { std::vector> _Screens; CRebootScreen* _pRebootScreen; C128x64_OLED* _pDisplay; + unsigned long _OTAholdoff; int _menu; int _subMenu; int _rootMenu; @@ -49,6 +50,7 @@ class CScreenManager { void _dim(bool state); void _loadScreens(); void _unloadScreens(); + bool _checkOTAholdoff(); public: enum eUIMenuSets { RootMenuLoop, TimerMenuLoop, UserSettingsLoop, SystemSettingsLoop, TuningMenuLoop, BranchMenu }; enum eUIRootMenus { DetailedControlUI, BasicControlUI, ClockUI, ModeUI, GPIOInfoUI, TrunkUI }; diff --git a/src/OLED/ThermostatModeScreen.cpp b/src/OLED/ThermostatModeScreen.cpp index f39b5e1..08af941 100644 --- a/src/OLED/ThermostatModeScreen.cpp +++ b/src/OLED/ThermostatModeScreen.cpp @@ -90,6 +90,7 @@ CThermostatModeScreen::show() case 1: modeStr = "Deadband"; break; case 2: modeStr = "Linear Hz"; break; case 3: modeStr = "Ext thermostat"; break; + case 4: modeStr = "Stop Start"; break; } if(modeStr) _printMenuText(Column, Line3, modeStr, _rowSel == 4); @@ -170,6 +171,9 @@ CThermostatModeScreen::animate() case 3: pMsg = " The heater runs according to GPIO input #2: Open:minimum, Closed:maximum. "; break; + case 4: + pMsg = " The heater is stopped then started according to the defined window. "; + break; } if(pMsg) _scrollMessage(56, pMsg, _scrollChar); @@ -287,7 +291,6 @@ CThermostatModeScreen::keyHandler(uint8_t event) void CThermostatModeScreen::_adjust(int dir) { - int wrap; switch(_rowSel) { case 1: _cyclicMode.Stop += dir; @@ -304,12 +307,14 @@ CThermostatModeScreen::_adjust(int dir) case 4: // thermostat mode _thermoMode += dir; #if USE_JTAG == 0 - wrap = GPIOin.usesExternalThermostat() ? 3 : 2; + if(_thermoMode == 3 && !GPIOin.usesExternalThermostat()) + _thermoMode += dir; #else - //CANNOT USE GPIO WITH JTAG DEBUG - wrap = 2; + // CANNOT USE GPIO WITH JTAG DEBUG + if(_thermoMode == 3 + _thermoMode += dir; #endif - WRAPLIMITS(_thermoMode, 0, wrap); + WRAPLIMITS(_thermoMode, 0, 4); break; } } diff --git a/src/Protocol/Protocol.cpp b/src/Protocol/Protocol.cpp index fa27007..d406d17 100644 --- a/src/Protocol/Protocol.cpp +++ b/src/Protocol/Protocol.cpp @@ -328,7 +328,7 @@ CProtocol::setSystemVoltage(float fVal) int CProtocolPackage::getRunStateEx() const { int runstate = getRunState(); - if(isCyclicActive()) { + if(isCyclicStopStartActive()) { // special states for cyclic suspended switch(runstate) { case 0: runstate = 10; break; // standby, awaiting temperature drop @@ -405,7 +405,7 @@ const char* ErrstatesEx [] PROGMEM = { "E-09: Temp sense", // [10] E-09 "E-10: Ignition fail", // [11] E-10 SmartError manufactured state - sensing runstate 2 -> >5 "E-11: Failed 1st ignite", // [12] E-11 SmartError manufactured state - sensing runstate 2 -> 3 - "E-12 Excess fuel shutdown", // [13] E-12 SmartError manufactured state - excess fuel consumed + "E-12: Excess fuel shutdown", // [13] E-12 SmartError manufactured state - excess fuel consumed "Unknown error?" // mystery code! }; diff --git a/src/Protocol/TxManage.cpp b/src/Protocol/TxManage.cpp index eb074c5..e237450 100644 --- a/src/Protocol/TxManage.cpp +++ b/src/Protocol/TxManage.cpp @@ -290,6 +290,12 @@ CTxManage::PrepareFrame(const CProtocol& basisFrame, bool isBTCmaster) } #endif break; + + case 4: + m_TxFrame.setThermostatModeProtocol(0); // direct heater to use Hz Mode + m_TxFrame.setTemperature_Actual(0); // must force actual to 0 for Hz mode + m_TxFrame.setHeaterDemand(m_TxFrame.getTemperature_Max()); // maximum Hz + break; } } diff --git a/src/RTC/RTCStore.cpp b/src/RTC/RTCStore.cpp index 09e43d7..16ecfa4 100644 --- a/src/RTC/RTCStore.cpp +++ b/src/RTC/RTCStore.cpp @@ -52,7 +52,7 @@ CRTC_Store::CRTC_Store() _fuelgauge = 0; _demandDegC = 22; _demandPump = 22; - _CyclicEngaged = false; + _userStart = false; _BootInit = true; _RunTime = 0; _GlowTime = 0; @@ -65,7 +65,7 @@ CRTC_Store::begin() // RTC lost power - reset internal NV values to defaults DebugPort.println("CRTC_Store::begin() RTC lost power, re-initialising NV aspect"); _demandPump = _demandDegC = 22; - _CyclicEngaged = false; + _userStart = false; setFuelGauge(0); setDesiredTemp(_demandDegC); setDesiredPump(_demandPump); @@ -127,16 +127,16 @@ CRTC_Store::setBootInit(bool val) } bool -CRTC_Store::getCyclicEngaged() +CRTC_Store::getUserStart() { _ReadAndUnpackByte4(); - return _CyclicEngaged; + return _userStart; } void -CRTC_Store::setCyclicEngaged(bool active) +CRTC_Store::setUserStart(bool active) { - _CyclicEngaged = active; + _userStart = active; _PackAndSaveByte4(); } @@ -168,6 +168,20 @@ CRTC_Store::getFrostOn() return _frostOn; } +void +CRTC_Store::setSpare(bool state) +{ + _spare = state; + _PackAndSaveByte5(); +} + +bool +CRTC_Store::getSpare() +{ + _ReadAndUnpackByte5(); + return _spare; +} + void CRTC_Store::resetRunTime() { @@ -221,17 +235,17 @@ CRTC_Store::_ReadAndUnpackByte4() uint8_t NVval = 0; Clock.readData((uint8_t*)&NVval, 1, 4); _demandDegC = NVval & 0x3f; - _CyclicEngaged = (NVval & 0x80) != 0; + _userStart = (NVval & 0x80) != 0; _BootInit = (NVval & 0x40) != 0; _accessed[1] = true; - DebugPort.printf("RTC_Store - read byte4: degC=%d, CyclicOn=%d, BootInit=%d\r\n", _demandDegC, _CyclicEngaged, _BootInit); + DebugPort.printf("RTC_Store - read byte4: degC=%d, UserStart=%d, BootInit=%d\r\n", _demandDegC, _userStart, _BootInit); } } void CRTC_Store::_PackAndSaveByte4() { - uint8_t NVval = (_CyclicEngaged ? 0x80 : 0x00) + uint8_t NVval = (_userStart ? 0x80 : 0x00) | (_BootInit ? 0x40 : 0x00) | (_demandDegC & 0x3f); Clock.saveData((uint8_t*)&NVval, 1, 4); @@ -245,6 +259,7 @@ CRTC_Store::_ReadAndUnpackByte5() Clock.readData((uint8_t*)&NVval, 1, 5); _demandPump = NVval & 0x3f; _frostOn = (NVval & 0x40) != 0; + _spare = (NVval & 0x80) != 0; _accessed[2] = true; DebugPort.printf("RTC_Store - read byte5: pump=%d\r\n", _demandPump); } @@ -255,6 +270,7 @@ CRTC_Store::_PackAndSaveByte5() { uint8_t NVval = (_demandPump & 0x3f); NVval |= _frostOn ? 0x40 : 0; + NVval |= _spare ? 0x80 : 0; Clock.saveData((uint8_t*)&NVval, 1, 5); } diff --git a/src/RTC/RTCStore.h b/src/RTC/RTCStore.h index 7ec0ba8..502a8e3 100644 --- a/src/RTC/RTCStore.h +++ b/src/RTC/RTCStore.h @@ -30,9 +30,10 @@ class CRTC_Store { float _fuelgauge; // Byte0..Byte3 uint8_t _demandDegC; // Byte4[0..5] bool _BootInit; // Byte4[6] - bool _CyclicEngaged; // Byte4[7] + bool _userStart; // Byte4[7] uint8_t _demandPump; // Byte5[0..5] bool _frostOn; // Byte5[6] + bool _spare; // Byte5[7] uint8_t _RunTime; // Byte6[0..4] uint8_t _GlowTime; // Byte6[5..7] void _ReadAndUnpackByte4(); @@ -51,12 +52,12 @@ public: void resetGlowTime(); bool incRunTime(); bool incGlowTime(); - void setCyclicEngaged(bool _CyclicEngaged); + void setUserStart(bool state); void setBootInit(bool val = true); float getFuelGauge(); uint8_t getDesiredTemp(); uint8_t getDesiredPump(); - bool getCyclicEngaged(); + bool getUserStart(); bool getBootInit(); int getRunTime(); int getGlowTime(); @@ -64,6 +65,8 @@ public: int getMaxRunTime() const { return 32; }; void setFrostOn(bool state); bool getFrostOn(); + void setSpare(bool state); + bool getSpare(); }; extern CRTC_Store RTC_Store; diff --git a/src/RTC/TimerManager.cpp b/src/RTC/TimerManager.cpp index 92f319b..263d685 100644 --- a/src/RTC/TimerManager.cpp +++ b/src/RTC/TimerManager.cpp @@ -283,7 +283,6 @@ CTimerManager::manageTime(int _hour, int _minute, int _dow) } } else { -// if(!RTC_Store.getFrostOn() && !RTC_Store.getCyclicEngaged()) if(!RTC_Store.getFrostOn()) requestOff(); retval = 2; diff --git a/src/Utility/BTC_JSON.cpp b/src/Utility/BTC_JSON.cpp index 641e76c..32e21b4 100644 --- a/src/Utility/BTC_JSON.cpp +++ b/src/Utility/BTC_JSON.cpp @@ -415,17 +415,13 @@ void updateJSONclients(bool report) char jsonStr[800]; { if(makeJSONString(JSONmoderator, jsonStr, sizeof(jsonStr))) { - if (report) DebugPort.printf(" %ld JSON send: %s", millis(), jsonStr); - sendJSONtext(jsonStr); - if (report) DebugPort.println(" Done"); + sendJSONtext(jsonStr, report); } } // update extended params { if(makeJSONStringEx(JSONmoderator, jsonStr, sizeof(jsonStr))) { - if (report) DebugPort.printf(" %ld JSON send: %s", millis(), jsonStr); - sendJSONtext(jsonStr); - if (report) DebugPort.println(" Done"); + sendJSONtext(jsonStr, report); } } // update timer parameters @@ -433,9 +429,7 @@ void updateJSONclients(bool report) for(int tmr=0; tmr<14; tmr++) { if(makeJSONTimerString(tmr, jsonStr, sizeof(jsonStr))) { - if (report) DebugPort.printf(" %ld JSON send: %s", millis(), jsonStr); - sendJSONtext(jsonStr); - if (report) DebugPort.println("Done"); + sendJSONtext(jsonStr, report); bNewTimerInfo = true; } } @@ -451,43 +445,33 @@ void updateJSONclients(bool report) root.set("TimerRefresh", 1); root.printTo(jsonStr, 800); - if (report) DebugPort.printf(" %ld JSON send: %s", millis(), jsonStr); - sendJSONtext(jsonStr); - if (report) DebugPort.println(" Done"); + sendJSONtext(jsonStr, report); } // report MQTT params { if(makeJSONStringMQTT(MQTTJSONmoderator, jsonStr, sizeof(jsonStr))) { - if (report) DebugPort.printf(" %ld JSON send: %s", millis(), jsonStr); - sendJSONtext(jsonStr); - if (report) DebugPort.println(" Done"); + sendJSONtext(jsonStr, report); } } // report IP params { if(makeJSONStringIP(IPmoderator, jsonStr, sizeof(jsonStr))) { - if (report) DebugPort.printf(" %ld JSON send: %s", millis(), jsonStr); - sendJSONtext(jsonStr); - if (report) DebugPort.println(" Done"); + sendJSONtext(jsonStr, report); } } // report System info { if(makeJSONStringSysInfo(SysModerator, jsonStr, sizeof(jsonStr))) { - if (report) DebugPort.printf(" %ld JSON send: %s", millis(), jsonStr); - sendJSONtext(jsonStr); - if (report) DebugPort.println(" Done"); + sendJSONtext(jsonStr, report); } } { if(makeJSONStringGPIO(GPIOmoderator, jsonStr, sizeof(jsonStr))) { - if (report) DebugPort.printf(" %ld JSON send: %s", millis(), jsonStr); - sendJSONtext(jsonStr); - if (report) DebugPort.println(" Done"); + sendJSONtext(jsonStr, report); } } @@ -508,7 +492,7 @@ void resetAllJSONmoderators() GPIOmoderator.reset(); // create and send a validation code (then client knows AB is capable of reboot over JSON) doJSONreboot(0); - sendJSONtext("{\"LoadWebContent\":\"Supported\"}"); + sendJSONtext("{\"LoadWebContent\":\"Supported\"}", false); } void initJSONMQTTmoderator() @@ -562,8 +546,10 @@ void Expand(std::string& str) } } -void sendJSONtext(const char* jsonStr) +void sendJSONtext(const char* jsonStr, bool report) { + if (report) DebugPort.printf("JSON send: %s\r\n", jsonStr); + #ifdef REPORT_JSONSENDS std::string dest; DebugPort.print("1"); @@ -601,11 +587,11 @@ void doJSONreboot(uint16_t PIN) char jsonStr[20]; sprintf(jsonStr, "{\"Reboot\":\"%04d\"}", validate); - sendJSONtext( jsonStr ); + sendJSONtext( jsonStr, false ); } else if(PIN == validate) { strcpy(jsonStr, "{\"Reboot\":\"-1\"}"); - sendJSONtext( jsonStr ); + sendJSONtext( jsonStr, false ); // initate reboot const char* content[2]; diff --git a/src/Utility/BTC_JSON.h b/src/Utility/BTC_JSON.h index 68b9231..2c1c205 100644 --- a/src/Utility/BTC_JSON.h +++ b/src/Utility/BTC_JSON.h @@ -41,7 +41,7 @@ void resetJSONSysModerator(); void resetJSONMQTTmoderator(); void validateTimer(int ID); void doJSONreboot(uint16_t code); -void sendJSONtext(const char* JSONstr); +void sendJSONtext(const char* JSONstr, bool report); template const char* createJSON(const char* name, T value) diff --git a/src/Utility/NVStorage.cpp b/src/Utility/NVStorage.cpp index f37c2ca..d96110f 100644 --- a/src/Utility/NVStorage.cpp +++ b/src/Utility/NVStorage.cpp @@ -452,11 +452,13 @@ sUserSettings::load() validatedLoad("thermostat", useThermostat, 1, u8inBounds, 0, 1); validatedLoad("thermoMethod", ThermostatMethod, 0, u8inBounds, 0, 255); // catch and migrate old combined method & window - if(ThermostatMethod & 0xFC) { - float defVal = float(ThermostatMethod>>2) * 0.1f; - validatedLoad("thermoWindow", ThermostatWindow, defVal, 0.2f, 10.0f); - preferences.putUChar("thermoMethod", ThermostatMethod & 0x03); // strip old window - } + // if(ThermostatMethod & 0xFC) { + // float defVal = float(ThermostatMethod>>2) * 0.1f; + // validatedLoad("thermoWindow", ThermostatWindow, defVal, 0.2f, 10.0f); + // preferences.putUChar("thermoMethod", ThermostatMethod & 0x03); // strip old window + // } + if(ThermostatMethod > 4) + ThermostatMethod = 0; validatedLoad("thermoWindow", ThermostatWindow, 1.0f, 0.2f, 10.f); DebugPort.printf("2) Window = %f\r\n", ThermostatWindow); validatedLoad("frostOn", FrostOn, 0, u8inBounds, 0, 10); diff --git a/src/Utility/NVStorage.h b/src/Utility/NVStorage.h index e8bc429..891bc7f 100644 --- a/src/Utility/NVStorage.h +++ b/src/Utility/NVStorage.h @@ -310,7 +310,7 @@ struct sUserSettings : public CESP32_NVStorage { long menuTimeout; long ExtThermoTimeout; uint8_t degF; - uint8_t ThermostatMethod; // 0: standard heater, 1: Narrow Hysterisis, 2:Managed Hz mode + uint8_t ThermostatMethod; // 0: standard heater, 1: Narrow Hysterisis, 2:Managed Hz mode, 3: External contact, 4: Stop/Start float ThermostatWindow; uint8_t FrostOn; uint8_t FrostRise; @@ -333,7 +333,7 @@ struct sUserSettings : public CESP32_NVStorage { retval &= INBOUNDS(menuTimeout, 0, 300000); // 5 mins retval &= INBOUNDS(ExtThermoTimeout, 0, 3600000); // 1 hour retval &= (degF == 0) || (degF == 1); - retval &= ThermostatMethod <= 3; // only modes 0, 1 or 2, 3 + retval &= ThermostatMethod <= 4; // only modes 0, 1, 2, 3 or 4 retval &= INBOUNDS(ThermostatWindow, 0.2f, 10.f); retval &= useThermostat < 2; retval &= INBOUNDS(wifiMode, 0, 3); diff --git a/src/Utility/UtilClasses.cpp b/src/Utility/UtilClasses.cpp index 8a7fe9b..fcb643b 100644 --- a/src/Utility/UtilClasses.cpp +++ b/src/Utility/UtilClasses.cpp @@ -131,11 +131,11 @@ void DecodeCmd(const char* cmd, String& payload) if(payload.toInt()) { CDemandManager::eStartCode result = requestOn(); switch(result) { - case CDemandManager::eStartOK: sendJSONtext("{\"StartString\":\"\"}"); break; - case CDemandManager::eStartTooWarm: sendJSONtext("{\"StartString\":\"Ambient too warm!\"}"); break; - case CDemandManager::eStartSuspend: sendJSONtext("{\"StartString\":\"Immediate Cyclic suspension!\"}"); break; - case CDemandManager::eStartLVC: sendJSONtext("{\"StartString\":\"Battery below LVC!\"}"); break; - case CDemandManager::eStartLowFuel: sendJSONtext("{\"StartString\":\"Fuel Empty!\"}"); break; + case CDemandManager::eStartOK: sendJSONtext("{\"StartString\":\"\"}", true); break; + case CDemandManager::eStartTooWarm: sendJSONtext("{\"StartString\":\"Ambient too warm!\"}", true); break; + case CDemandManager::eStartSuspend: sendJSONtext("{\"StartString\":\"Immediate Cyclic suspension!\"}", true); break; + case CDemandManager::eStartLVC: sendJSONtext("{\"StartString\":\"Battery below LVC!\"}", true); break; + case CDemandManager::eStartLowFuel: sendJSONtext("{\"StartString\":\"Fuel Empty!\"}", true); break; } } else { @@ -185,7 +185,7 @@ void DecodeCmd(const char* cmd, String& payload) else if(strcmp("ThermostatMethod", cmd) == 0) { sUserSettings settings = NVstore.getUserSettings(); settings.ThermostatMethod = payload.toInt(); - if(INBOUNDS(settings.ThermostatMethod, 0, 3)) + if(INBOUNDS(settings.ThermostatMethod, 0, 4)) NVstore.setUserSettings(settings); } else if(strcmp("ThermostatWindow", cmd) == 0) { diff --git a/src/Utility/helpers.h b/src/Utility/helpers.h index 679f640..1b14b7a 100644 --- a/src/Utility/helpers.h +++ b/src/Utility/helpers.h @@ -48,7 +48,7 @@ extern bool hasOEMLCDcontroller(); extern bool hasHtrData(); extern int getBlueWireStat(); extern int getSmartError(); -extern bool isCyclicActive(); +extern bool isCyclicStopStartActive(); extern float getVersion(); const char* getVersionStr(bool beta=false); extern const char* getVersionDate(); diff --git a/src/WiFi/ABMQTT.cpp b/src/WiFi/ABMQTT.cpp index 4c2aeb4..0e6667c 100644 --- a/src/WiFi/ABMQTT.cpp +++ b/src/WiFi/ABMQTT.cpp @@ -39,6 +39,9 @@ #include "../Utility/BTC_JSON.h" #include "../Utility/TempSense.h" #include "../Utility/DemandManager.h" +#include "../Utility/FuelGauge.h" +#include "../Utility/BoardDetect.h" +#include "../Utility/NVStorage.h" #include extern void DecodeCmd(const char* cmd, String& payload); @@ -348,9 +351,15 @@ void updateMQTT() pubTopic("InputVoltage", getBatteryVoltage(false)); pubTopic("GlowVoltage", getGlowVolts()); pubTopic("GlowCurrent", getGlowCurrent()); - sGPIO info; - getGPIOinfo(info); - pubTopic("GPanlg", info.algVal * 100 / 4096); + if(getBoardRevision() != BRD_V2_GPIO_NOALG && getBoardRevision() != BRD_V3_GPIO_NOALG) { // has GPIO support + sGPIO info; + getGPIOinfo(info); + pubTopic("GPanlg", info.algVal * 100 / 4096); + } + pubTopic("FuelUsage", FuelGauge.Used_mL()); + float fuelRate = getHeaterInfo().getPump_Actual() * NVstore.getHeaterTuning().pumpCal * 60 * 60; + pubTopic("FuelRate", fuelRate); + } void refreshMQTT() diff --git a/src/WiFi/BTCWebServer.cpp b/src/WiFi/BTCWebServer.cpp index ef3cd2f..81eb436 100644 --- a/src/WiFi/BTCWebServer.cpp +++ b/src/WiFi/BTCWebServer.cpp @@ -21,6 +21,7 @@ */ #define USE_EMBEDDED_WEBUPDATECODE +#define HTTPS_LOGLEVEL 2 #include #include "BTCWifi.h" @@ -53,7 +54,7 @@ #include #include #include "../OLED/ScreenManager.h" -// #include "../Utility/ABpreferences.h" +#include "esp_task_wdt.h" // Max clients to be connected to the JSON handler #define MAX_CLIENTS 4 @@ -77,6 +78,7 @@ void streamFileCoreSSL(const size_t fileSize, const String & fileName, const Str void processWebsocketQueue(); QueueHandle_t webSocketQueue = NULL; +QueueHandle_t JSONcommandQueue = NULL; TaskHandle_t handleWebServerTask; #if USE_HTTPS == 1 SSLCert* pCert; @@ -121,6 +123,8 @@ void doDefaultWebHandler(HTTPRequest * req, HTTPResponse * res); void build404Response(HTTPRequest * req, String& content, String file); void build500Response(String& content, String file); bool checkAuthentication(HTTPRequest * req, HTTPResponse * res, int credID=0); +bool addRxJSONcommand(const char* str); +bool checkRxJSONcommand(); // As websockets are more complex, they need a custom class that is derived from WebsocketHandler @@ -176,14 +180,31 @@ JSONHandler::onMessage(WebsocketInputStreambuf * inbuf) { bRxWebData = true; - char cmd[256]; - memset(cmd, 0, 256); - if(msg.length() < 256) { - strcpy(cmd, msg.c_str()); - // TODO: use a queue to hand over message - interpretJsonCommand(cmd); // send to the main heater controller decode routine + // use a queue to hand over messages - ensures any commands that affect the I2C bus + // (typ. various RTC operations) are performed in line with all other accesses + addRxJSONcommand(msg.c_str()); +} + +bool addRxJSONcommand(const char* str) +{ + if(JSONcommandQueue) { + char *pMsg = new char[strlen(str)+1]; + strcpy(pMsg, str); + xQueueSend(JSONcommandQueue, &pMsg, 0); + return true; } - + return false; +} + +bool checkRxJSONcommand() +{ + char* pMsg = NULL; + if(xQueueReceive(JSONcommandQueue, &pMsg, 0)) { + interpretJsonCommand(pMsg); + delete[] pMsg; + return true; + } + return false; } @@ -470,10 +491,10 @@ void initWebServer(void) { DebugPort.println("HTTPS started"); + JSONcommandQueue = xQueueCreate(50, sizeof(char*) ); // setup task to handle webserver webSocketQueue = xQueueCreate(50, sizeof(char*) ); - bStopWebServer = false; xTaskCreate(SSLloopTask, "Web server task", @@ -512,6 +533,8 @@ void SSLloopTask(void *) { bool doWebServer(void) { GetWebContent.manage(); + BrowserUpload.queueProcess(); // manage data queued from web update + checkRxJSONcommand(); return true; } @@ -1061,9 +1084,6 @@ void onWMConfig(HTTPRequest * req, httpsserver::HTTPResponse * res) newMode.eraseCreds = false; newMode.delay = 500; scheduleWMreboot(newMode); - - // delay(500); - // wifiEnterConfigPortal(true, false, 10000); } @@ -1077,8 +1097,6 @@ void onResetWifi(HTTPRequest * req, httpsserver::HTTPResponse * res) newMode.eraseCreds = true; newMode.delay = 500; scheduleWMreboot(newMode); - // delay(500); - // wifiEnterConfigPortal(true, true, 3000); } @@ -1118,7 +1136,7 @@ void processWebsocketQueue() // DebugPort.println("->"); } } - delete pMsg; + delete[] pMsg; } } } @@ -1461,19 +1479,40 @@ void onUploadProgression(HTTPRequest * req, httpsserver::HTTPResponse * res) } while (!parser->endOfField()) { + + // file upload and writing to SPIFFS is not a happy combination as the web server is running at an elevated level here + // best to pass the data to the normal Arduino processing task via a queue, but maintain synchronism with the processing + // by spinning here until ready. + while(!BrowserUpload.Ready()) { + taskYIELD(); + } + + esp_task_wdt_reset(); upload.currentSize = parser->read(upload.buf, HTTP_UPLOAD_BUFLEN); - sts = BrowserUpload.fragment(upload, res); + + BrowserUpload.queueFragment(upload); // let user task process the fresh data + + while(!BrowserUpload.Ready()) { + taskYIELD(); + } + esp_task_wdt_reset(); + +// sts = BrowserUpload.fragment(upload, res); + sts = BrowserUpload.queueResult(); + if(sts < 0) { if(pUpdateHandler) { sprintf(JSON, "{\"updateProgress\":%d}", sts); pUpdateHandler->send(JSON, WebsocketHandler::SEND_TYPE_TEXT); } + DebugPort.printf("Upload code %d\r\n", sts); break; } else { // upload still in progress? if(BrowserUpload.bUploadActive) { // show progress unless a write error has occured - DebugPort.print("."); + // DebugPort.printf(" p%d ", uxTaskPriorityGet(NULL)); + // DebugPort.print("."); if(upload.totalSize) { // feed back bytes received over web socket for progressbar update on browser (via javascript) if(pUpdateHandler) { @@ -1481,13 +1520,14 @@ void onUploadProgression(HTTPRequest * req, httpsserver::HTTPResponse * res) pUpdateHandler->send(JSON, WebsocketHandler::SEND_TYPE_TEXT); } } - // show percentage on OLED +/* // show percentage on OLED int percent = 0; if(_SuppliedFileSize) percent = 100 * upload.totalSize / _SuppliedFileSize; #if USE_SSL_LOOP_TASK != 1 ShowOTAScreen(percent, eOTAbrowser); // browser update #endif +*/ } } } diff --git a/src/WiFi/BTCota.cpp b/src/WiFi/BTCota.cpp index 9017540..74ffc47 100644 --- a/src/WiFi/BTCota.cpp +++ b/src/WiFi/BTCota.cpp @@ -122,7 +122,7 @@ void doOTA() if ((WiFi.status() == WL_CONNECTED)) // bug workaround in FOTA where execHTTPcheck does not return false in this condition { #endif - FOTA.onProgress(onWebProgress); // important - keeps watchdog fed + FOTA.onProgress(NULL); // important - keeps watchdog fed FOTA.onComplete(CheckFirmwareCRC); // upload complete, but not yet verified FOTA.onSuccess(onSuccess); #ifdef TESTFOTA @@ -137,16 +137,20 @@ void doOTA() if(FOTA.execHTTPcheck()) { DebugPort.println("New firmware available on web server!"); if(FOTAauth == 2) { // user has authorised update (was == 1 before auth.) + FOTA.onProgress(onWebProgress); // important - keeps watchdog fed FOTA.execOTA(); // go ahead and do the update, reading new file from web server + FOTA.onProgress(NULL); // avoid rogue web update pop ups during browser update! FOTAauth = 0; // and we're done. } - else + else { FOTAauth = 1; // flag that new firmware is available + } } else { FOTAauth = 0; // cancel } } // Wifi (STA) Connected + FOTA.onProgress(NULL); #else @@ -161,7 +165,9 @@ void doOTA() // version number is collected asynchronously after initiating the update check if(FOTA.getNewVersion()) { if(FOTAauth == 2) { // user has authorised update (was == 1 before auth.) + FOTA.onProgress(onWebProgress); // important - keeps watchdog fed FOTA.execOTA(); // go ahead and do the update, reading new file from web server + FOTA.onProgress(NULL); // avoid rogue web update pop ups during browser update! FOTAauth = 0; // and we're done. } else { @@ -247,8 +253,6 @@ void onWebProgress(size_t progress, size_t total) static int prevPC = 0; if(percent != prevPC) { prevPC = percent; - DebugPort.printf("Web progress: %u%%\r\n", percent); - DebugPort.handle(); // keep telnet spy alive ShowOTAScreen(percent, eOTAWWW); } } diff --git a/src/WiFi/BrowserUpload.cpp b/src/WiFi/BrowserUpload.cpp index 20002d2..09b138f 100644 --- a/src/WiFi/BrowserUpload.cpp +++ b/src/WiFi/BrowserUpload.cpp @@ -29,6 +29,9 @@ #include "BTCota.h" #include "../Utility/helpers.h" +QueueHandle_t webUpdateQueue = NULL; + + void sBrowserUpload::init() { @@ -103,7 +106,7 @@ sBrowserUpload::begin(String& filename, int filesize) } int -sBrowserUpload::fragment(HTTPUpload& upload, httpsserver::HTTPResponse * res) +sBrowserUpload::doFragment(HTTPUpload& upload, httpsserver::HTTPResponse * res) { if(isSPIFFSupload()) { // SPIFFS update (may be error state) @@ -129,6 +132,7 @@ sBrowserUpload::fragment(HTTPUpload& upload, httpsserver::HTTPResponse * res) } } else { + // DebugPort.print("."); // Firmware update, add new fragment to OTA partition if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { // ERROR ! @@ -138,6 +142,13 @@ sBrowserUpload::fragment(HTTPUpload& upload, httpsserver::HTTPResponse * res) if(res) upload.totalSize += upload.currentSize; } + // show percentage on OLED + int percent = 0; + if(SrcFile.size) { + percent = 100 * upload.totalSize / SrcFile.size; + } + ShowOTAScreen(percent, eOTAbrowser); // browser update + return upload.totalSize; } @@ -187,3 +198,47 @@ sBrowserUpload::isOK() const else return !Update.hasError(); } + +bool +sBrowserUpload::Ready() const +{ + return _bProcessed; +} + +int +sBrowserUpload::queueFragment(HTTPUpload& upload) +{ + _bProcessed = false; + sUpdateFragment fragment; + fragment.pUploadInfo = &upload; + xQueueSend(webUpdateQueue, &fragment, 0); + return upload.currentSize; +} + +bool +sBrowserUpload::queueProcess() +{ + sUpdateFragment fragment; + if(webUpdateQueue && xQueueReceive(webUpdateQueue, &fragment, 0)) { + + HTTPUpload& upload = *fragment.pUploadInfo; + _queueResult = doFragment(upload, (httpsserver::HTTPResponse *)1); + _bProcessed = true; + return true; + } + return false; +} + +int +sBrowserUpload::queueResult() +{ + return _queueResult; +} + +void +sBrowserUpload::createQueue() +{ + if(webUpdateQueue == NULL) { + webUpdateQueue = xQueueCreate(2, sizeof(sUpdateFragment) ); + } +} \ No newline at end of file diff --git a/src/WiFi/BrowserUpload.h b/src/WiFi/BrowserUpload.h index 12efe56..690857c 100644 --- a/src/WiFi/BrowserUpload.h +++ b/src/WiFi/BrowserUpload.h @@ -37,10 +37,16 @@ struct sBrowserUpload{ int state; } DstFile; bool bUploadActive; + int _queueResult; + volatile bool _bProcessed; //methods sBrowserUpload() { reset(); + createQueue(); + _bProcessed = true; } + void createQueue(); + void reset() { if(DstFile.file) { DstFile.file.close(); @@ -50,10 +56,21 @@ struct sBrowserUpload{ } void init(); int begin(String& filename, int filesize = -1); - int fragment(HTTPUpload& upload, httpsserver::HTTPResponse * res = NULL); + int doFragment(HTTPUpload& upload, httpsserver::HTTPResponse * res = NULL); int end(HTTPUpload& upload); bool isSPIFFSupload() const { return DstFile.state != 0; }; bool isOK() const; + bool Ready() const; + int queueFragment(HTTPUpload& upload); + bool queueProcess(); + int queueResult(); + +}; + +struct sUpdateFragment { + // uint16_t len; + // uint8_t buf[1500]; + HTTPUpload *pUploadInfo; }; diff --git a/src/WiFi/WebContentDL.cpp b/src/WiFi/WebContentDL.cpp index 277f7bb..cf99e6e 100644 --- a/src/WiFi/WebContentDL.cpp +++ b/src/WiFi/WebContentDL.cpp @@ -395,6 +395,6 @@ CGetWebContent::_sendJSON(const char* name) else JSONmsg += name; JSONmsg += "\"}"; - sendJSONtext(JSONmsg.c_str()); + sendJSONtext(JSONmsg.c_str(), false); }