Added Stop/Start thermostat mode

FuelUsage & FuelRate added as basic MQTT status topics
This commit is contained in:
Ray Jones 2020-05-13 10:37:31 +10:00
parent 24d8a4a7f1
commit 775f235ba8
22 changed files with 292 additions and 103 deletions

View file

@ -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 =

View file

@ -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()

View file

@ -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

View file

@ -33,6 +33,7 @@ class CScreenManager {
std::vector<std::vector<CScreen*>> _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 };

View file

@ -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;
}
}

View file

@ -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!
};

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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;

View file

@ -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];

View file

@ -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<class T>
const char* createJSON(const char* name, T value)

View file

@ -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);

View file

@ -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);

View file

@ -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) {

View file

@ -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();

View file

@ -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 <FreeRTOS.h>
extern void DecodeCmd(const char* cmd, String& payload);
@ -348,9 +351,15 @@ void updateMQTT()
pubTopic("InputVoltage", getBatteryVoltage(false));
pubTopic("GlowVoltage", getGlowVolts());
pubTopic("GlowCurrent", getGlowCurrent());
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()

View file

@ -21,6 +21,7 @@
*/
#define USE_EMBEDDED_WEBUPDATECODE
#define HTTPS_LOGLEVEL 2
#include <Arduino.h>
#include "BTCWifi.h"
@ -53,7 +54,7 @@
#include <WebsocketHandler.hpp>
#include <FreeRTOS.h>
#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
*/
}
}
}

View file

@ -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);
}
}

View file

@ -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) );
}
}

View file

@ -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;
};

View file

@ -395,6 +395,6 @@ CGetWebContent::_sendJSON(const char* name)
else
JSONmsg += name;
JSONmsg += "\"}";
sendJSONtext(JSONmsg.c_str());
sendJSONtext(JSONmsg.c_str(), false);
}