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
This commit is contained in:
Ray Jones 2020-03-23 16:54:15 +11:00
parent 3ca3e633ae
commit 4986a4d741
34 changed files with 737 additions and 137 deletions

View File

@ -1,18 +1,20 @@
Afterburner operation checklist 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 OLED intact
Latest firmware uploaded Latest firmware uploaded
SPIFFS uploaded SPIFFS uploaded
Check temperature sensor
Keypad functions OK Keypad functions OK
Indicator LEDs function Indicator LEDs function
Web server functions and serves heater control page Web server functions and serves heater control page
Bluetooth pairs and streams SPP data Bluetooth pairs and streams SPP data
Set time
Install battery
GPIO units only
~~~~~~~~~~~~~~~
Check pressure sensor
GPIO functions: GPIO functions:
Input x2 Input x2
Output x2 Output x2

View File

@ -97,7 +97,7 @@
#include "Protocol/Protocol.h" #include "Protocol/Protocol.h"
#include "Protocol/TxManage.h" #include "Protocol/TxManage.h"
#include "Protocol/SmartError.h" #include "Protocol/SmartError.h"
#include "Utility/helpers.h" #include "Utility/helpers.h"
#include "Utility/NVStorage.h" #include "Utility/NVStorage.h"
#include "Utility/DebugPort.h" #include "Utility/DebugPort.h"
#include "Utility/macros.h" #include "Utility/macros.h"
@ -124,10 +124,10 @@
#define RX_DATA_TIMOUT 50 #define RX_DATA_TIMOUT 50
const int FirmwareRevision = 31; const int FirmwareRevision = 32;
const int FirmwareSubRevision = 9; const int FirmwareSubRevision = 0;
const int FirmwareMinorRevision = 1; const int FirmwareMinorRevision = 0;
const char* FirmwareDate = "15 Jan 2020"; const char* FirmwareDate = "21 Mar 2020";
#ifdef ESP32 #ifdef ESP32
@ -367,9 +367,6 @@ void setup() {
digitalWrite(GPIOout2_pin, LOW); digitalWrite(GPIOout2_pin, LOW);
#endif #endif
nvs_stats_t nvs_stats;
nvs_get_stats(NULL, &nvs_stats);
// initialise TelnetSpy (port 23) as well as Serial to 115200 // initialise TelnetSpy (port 23) as well as Serial to 115200
// Serial is the usual USB connection to a PC // Serial is the usual USB connection to a PC
// DO THIS BEFORE WE TRY AND SEND DEBUG INFO! // DO THIS BEFORE WE TRY AND SEND DEBUG INFO!
@ -383,6 +380,11 @@ void setup() {
DebugPort.begin(115200); DebugPort.begin(115200);
DebugPort.println("_______________________________________________________________"); 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("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 :-) // 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(); setupGPIO();
// pinMode(0, OUTPUT);
// digitalWrite(0, LOW);
#if USE_SW_WATCHDOG == 1 && USE_JTAG == 0 #if USE_SW_WATCHDOG == 1 && USE_JTAG == 0
// create a high priority FreeRTOS task as a watchdog monitor // create a high priority FreeRTOS task as a watchdog monitor
TaskHandle_t wdTask; TaskHandle_t wdTask;
@ -984,15 +989,18 @@ void manageFrostMode()
if(heaterState == 0) { if(heaterState == 0) {
RTC_Store.setFrostOn(true); RTC_Store.setFrostOn(true);
DebugPort.printf("FROST MODE: Starting heater, < %d`C\r\n", engage); 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(); heaterOn();
} }
} }
uint8_t rise = NVstore.getUserSettings().FrostRise; 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()) { if(RTC_Store.getFrostOn()) {
DebugPort.printf("FROST MODE: Stopping heater, > %d`C\r\n", engage+rise); DebugPort.printf("FROST MODE: Stopping heater, > %d`C\r\n", engage+rise);
heaterOff(); heaterOff();
RTC_Store.setFrostOn(false); // cancel active frost mode 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) float getTemperatureSensor(int source)
{ {
static long lasttime = millis();
static float tempsave = 0;
long tDelta;
// NVstore always holds primary sensor as index 0
float retval; float retval;
TempSensor.getTemperature(source, retval); TempSensor.getTemperature(source, retval);
return 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) void setPumpMin(float val)
@ -2031,4 +2020,7 @@ CTempSense& getTempSensor()
return TempSensor; return TempSensor;
} }
void reqHeaterCalUpdate()
{
TxManage.queueSysUpdate();
}

View File

@ -135,6 +135,10 @@ CGPIOInfoScreen::animate()
_drawBitmap(99, 15, threshIconInfo); _drawBitmap(99, 15, threshIconInfo);
_drawBitmap(110, 13, bulbmode ? BulbOnIconInfo : BulbOffIconInfo); _drawBitmap(110, 13, bulbmode ? BulbOnIconInfo : BulbOffIconInfo);
break; break;
case CGPIOout1::HtrActive:
_drawBitmap(99, 15, onOffIconInfo);
_drawBitmap(110, 13, bulbmode ? BulbOnIconInfo : BulbOffIconInfo);
break;
} }
#if USE_JTAG == 0 #if USE_JTAG == 0
@ -151,6 +155,9 @@ CGPIOInfoScreen::animate()
_drawBitmap(99, 27, threshIconInfo); _drawBitmap(99, 27, threshIconInfo);
_drawBitmap(110, 26, bulbmode ? BulbOnIconInfo : BulbOffIconInfo); _drawBitmap(110, 26, bulbmode ? BulbOnIconInfo : BulbOffIconInfo);
break; break;
case CGPIOout2::HtrActive:
_drawBitmap(99, 27, onOffIconInfo);
_drawBitmap(110, 26, bulbmode ? BulbOnIconInfo : BulbOffIconInfo);
} }

View File

@ -138,7 +138,7 @@ CGPIOSetupScreen::show()
const char* msgText = NULL; const char* msgText = NULL;
switch(_GPIOparams.out1Mode) { switch(_GPIOparams.out1Mode) {
case CGPIOout1::Disabled: msgText = "---"; break; 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::User: msgText = "User"; break;
case CGPIOout1::Thresh: case CGPIOout1::Thresh:
if(_rowSel == 6) { if(_rowSel == 6) {
@ -151,6 +151,7 @@ CGPIOSetupScreen::show()
_printMenuText(Column2, Line3, msg, _rowSel == 8); _printMenuText(Column2, Line3, msg, _rowSel == 8);
} }
break; break;
case CGPIOout1::HtrActive: msgText ="OnSts"; break;
} }
if(msgText) if(msgText)
_printMenuText(Column2, Line3, msgText, _rowSel == 6); _printMenuText(Column2, Line3, msgText, _rowSel == 6);
@ -173,6 +174,7 @@ CGPIOSetupScreen::show()
_printMenuText(Column2, Line2, msg, _rowSel == 7); _printMenuText(Column2, Line2, msg, _rowSel == 7);
} }
break; break;
case CGPIOout2::HtrActive: msgText ="OnSts"; break;
} }
if(msgText) if(msgText)
_printMenuText(Column2, Line2, msgText, _rowSel == 5); _printMenuText(Column2, Line2, msgText, _rowSel == 5);
@ -256,6 +258,7 @@ CGPIOSetupScreen::animate()
else else
pMsg = " Output 2: Active if under temperature. Hold LEFT to set under. Hold RIGHT to set over. "; pMsg = " Output 2: Active if under temperature. Hold LEFT to set under. Hold RIGHT to set over. ";
break; break;
case CGPIOout2::HtrActive: pMsg = " Output 2: Active if heater is running. "; break;
} }
if(pMsg) if(pMsg)
_scrollMessage(56, pMsg, _scrollChar); _scrollMessage(56, pMsg, _scrollChar);
@ -272,6 +275,7 @@ CGPIOSetupScreen::animate()
else else
pMsg = " Output 1: Active if under temperature. Hold LEFT to set under. Hold RIGHT to set over. "; pMsg = " Output 1: Active if under temperature. Hold LEFT to set under. Hold RIGHT to set over. ";
break; break;
case CGPIOout1::HtrActive: pMsg = " Output 1: Active if heater is running. "; break;
} }
if(pMsg) if(pMsg)
_scrollMessage(56, pMsg, _scrollChar); _scrollMessage(56, pMsg, _scrollChar);
@ -483,13 +487,13 @@ CGPIOSetupScreen::_adjust(int dir)
case 5: // outputs mode case 5: // outputs mode
tVal = _GPIOparams.out2Mode; tVal = _GPIOparams.out2Mode;
tVal += dir; tVal += dir;
WRAPLIMITS(tVal, 0, 2); WRAPLIMITS(tVal, 0, 3);
_GPIOparams.out2Mode = (CGPIOout2::Modes)tVal; _GPIOparams.out2Mode = (CGPIOout2::Modes)tVal;
break; break;
case 6: // outputs mode case 6: // outputs mode
tVal = _GPIOparams.out1Mode; tVal = _GPIOparams.out1Mode;
tVal += dir; tVal += dir;
WRAPLIMITS(tVal, 0, 3); WRAPLIMITS(tVal, 0, 4);
_GPIOparams.out1Mode = (CGPIOout1::Modes)tVal; _GPIOparams.out1Mode = (CGPIOout1::Modes)tVal;
break; break;
case 7: case 7:

View File

@ -229,6 +229,15 @@ CScreenHeader::showWifiIcon()
{ {
if(isWifiConnected() || isWifiAP()) { // STA or AP mode active if(isWifiConnected() || isWifiAP()) { // STA or AP mode active
_drawBitmap(X_WIFI_ICON, Y_WIFI_ICON, WifiWideIconInfo, WHITE, BLACK); // wide icon erases annotations! _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 int xPos = X_WIFI_ICON + WifiIconInfo.width + 1; // x loaction of upload/download arrows

View File

@ -24,6 +24,7 @@
#include "BasicScreen.h" #include "BasicScreen.h"
#include "PrimingScreen.h" #include "PrimingScreen.h"
#include "WiFiScreen.h" #include "WiFiScreen.h"
#include "WiFiSTAScreen.h"
#include "FuelMixtureScreen.h" #include "FuelMixtureScreen.h"
#include "SetClockScreen.h" #include "SetClockScreen.h"
#include "SetTimerScreen.h" #include "SetTimerScreen.h"
@ -492,6 +493,7 @@ CScreenManager::_loadScreens()
menuloop.push_back(new CHourMeterScreen(*_pDisplay, *this)); // Hour Meter screen menuloop.push_back(new CHourMeterScreen(*_pDisplay, *this)); // Hour Meter screen
} }
menuloop.push_back(new CWiFiScreen(*_pDisplay, *this)); 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 CMQTTScreen(*_pDisplay, *this));
menuloop.push_back(new CBTScreen(*_pDisplay, *this)); menuloop.push_back(new CBTScreen(*_pDisplay, *this));
if(getTempSensor().getBME280().getCount()) { if(getTempSensor().getBME280().getCount()) {

123
src/OLED/WiFiSTAScreen.cpp Normal file
View File

@ -0,0 +1,123 @@
/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2018 Ray Jones <ray@mrjones.id.au>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#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;
}

38
src/OLED/WiFiSTAScreen.h Normal file
View File

@ -0,0 +1,38 @@
/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2018 Ray Jones <ray@mrjones.id.au>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#ifndef __WIFISTASCREEN_H__
#define __WIFISTASCREEN_H__
#include <stdint.h>
#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

View File

@ -1301,6 +1301,22 @@ const uint8_t PROGMEM threshIcon[] =
const BITMAP_INFO threshIconInfo(9, 9, 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 // Image data for frost
// //

View File

@ -159,5 +159,6 @@ extern const BITMAP_INFO algIconInfo;
extern const BITMAP_INFO passwordIconInfo; extern const BITMAP_INFO passwordIconInfo;
extern const BITMAP_INFO threshIconInfo; extern const BITMAP_INFO threshIconInfo;
extern const BITMAP_INFO onOffIconInfo;
extern const BITMAP_INFO frostIconInfo; extern const BITMAP_INFO frostIconInfo;
extern const BITMAP_INFO humidityIconInfo; extern const BITMAP_INFO humidityIconInfo;

View File

@ -238,6 +238,13 @@ CProtocol::setAltitude(float altitude)
Controller.Altitude_LSB = (alt >> 0) & 0xff; Controller.Altitude_LSB = (alt >> 0) & 0xff;
} }
int
CProtocol::getAltitude() const
{
int alt = (Controller.Altitude_MSB << 8) | Controller.Altitude_LSB;
return alt;
}
void void
CProtocol::Init(int FrameMode) CProtocol::Init(int FrameMode)

View File

@ -163,6 +163,7 @@ public:
void setTemperature_HeatExchg(uint16_t degC); // temperature of heat exchanger void setTemperature_HeatExchg(uint16_t degC); // temperature of heat exchanger
// altitude // altitude
void setAltitude(float altitude); void setAltitude(float altitude);
int getAltitude() const;
void DebugReport(const char* hdr, const char* ftr); void DebugReport(const char* hdr, const char* ftr);
@ -210,6 +211,7 @@ public:
float getGlow_Current() const { return Heater.getGlowPlug_Current(); }; float getGlow_Current() const { return Heater.getGlowPlug_Current(); };
float getSystemVoltage() const { return Controller.getSystemVoltage(); }; float getSystemVoltage() const { return Controller.getSystemVoltage(); };
int getGlow_Drive() const { return Controller.getGlowDrive(); }; int getGlow_Drive() const { return Controller.getGlowDrive(); };
int getAltitude() const { return Controller.getAltitude(); };
// void setRefTime(); // void setRefTime();
void reportFrames(bool isOEM); void reportFrames(bool isOEM);

View File

@ -126,6 +126,8 @@ CSmartError::monitor(uint8_t newRunState)
int int
CSmartError::checkVolts(float ipVolts, float glowI, bool throwfault) CSmartError::checkVolts(float ipVolts, float glowI, bool throwfault)
{ {
const unsigned long LVCholdoffTime = 10000;
static unsigned long LVCShutdownHoldoff = 0;
// check for low voltage // check for low voltage
// Native NV values here are x10 integers // Native NV values here are x10 integers
@ -139,27 +141,32 @@ CSmartError::checkVolts(float ipVolts, float glowI, bool throwfault)
// test low voltage cutout // test low voltage cutout
if(ipVolts < threshLVC) { if(ipVolts < threshLVC) {
if(throwfault) { // only throw faults if directed to do so if(LVCShutdownHoldoff == 0) {
_Error = 2; // internals error codes are +1 over displayed error code // initial detection of LVC - introduce a hold off period
requestOff(); // shut heater down 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 return 2; // Low voltage return value = 2
} }
else {
if(LVCShutdownHoldoff)
DebugPort.println("LVC holdoff cancelled");
LVCShutdownHoldoff = 0; // disable holdoff, voltage now OK
}
// warning threshold // warning threshold
float threshWarn = threshLVC + 0.5; // nominally create a warning threshold, 0.5V over LVC threhsold 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) { if(ipVolts < threshWarn) {
return 1; // LVC OK, but below warning threshold, return code = 1 return 1; // LVC OK, but below warning threshold, return code = 1

View File

@ -55,6 +55,7 @@ CTxManage::CTxManage(int TxGatePin, HardwareSerial& serial) :
m_BlueWireSerial(serial), m_BlueWireSerial(serial),
m_TxFrame(CProtocol::CtrlMode) m_TxFrame(CProtocol::CtrlMode)
{ {
m_sysUpdate = 0;
m_bOnReq = false; m_bOnReq = false;
m_bOffReq = false; m_bOffReq = false;
m_bTxPending = false; m_bTxPending = false;
@ -107,6 +108,12 @@ CTxManage::queueRawCommand(uint8_t val)
_rawCommand = val; _rawCommand = val;
} }
void
CTxManage::queueSysUpdate()
{
m_sysUpdate = 10;
}
void void
CTxManage::PrepareFrame(const CProtocol& basisFrame, bool isBTCmaster) 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 // 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 // heater is happy either way, the OEM controller has set the max/min stuff already
if(isBTCmaster) { 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_Min(NVstore.getHeaterTuning().Fmin);
m_TxFrame.setFan_Max(NVstore.getHeaterTuning().Fmax); m_TxFrame.setFan_Max(NVstore.getHeaterTuning().Fmax);
m_TxFrame.setPump_Min(NVstore.getHeaterTuning().getPmin()); m_TxFrame.setPump_Min(NVstore.getHeaterTuning().getPmin());

View File

@ -26,6 +26,7 @@ class CTxManage
const int m_nStartDelay = 20; const int m_nStartDelay = 20;
const int m_nFrameTime = 14; const int m_nFrameTime = 14;
const int m_nFrontPorch = 0; const int m_nFrontPorch = 0;
int m_sysUpdate;
public: public:
CTxManage(int TxGatePin, HardwareSerial& serial); CTxManage(int TxGatePin, HardwareSerial& serial);
@ -38,6 +39,7 @@ public:
void begin(); void begin();
const CProtocol& getFrame() const { return m_TxFrame; }; const CProtocol& getFrame() const { return m_TxFrame; };
static void GateTerminate(); static void GateTerminate();
void queueSysUpdate(); // use to implant NV settings into heater
private: private:
HardwareSerial& m_BlueWireSerial; HardwareSerial& m_BlueWireSerial;

View File

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

View File

@ -0,0 +1,9 @@
#include <Preferences.h>
class ABpreferences : public Preferences {
public:
bool hasBytes(const char* key);
bool hasString(const char* key);
};

View File

@ -50,12 +50,14 @@ const char* GPIOout1Names[] = {
"Disabled", "Disabled",
"Status", "Status",
"User", "User",
"Thresh" "Thresh",
"HeaterOn"
}; };
const char* GPIOout2Names[] = { const char* GPIOout2Names[] = {
"Disabled", "Disabled",
"User", "User",
"Thresh" "Thresh",
"HeaterOn"
}; };
const char* GPIOalgNames[] = { const char* GPIOalgNames[] = {
@ -216,7 +218,7 @@ CGPIOin2::_doThermostat(bool active)
} }
const char* const char*
CGPIOin2::getExtThermTime() CGPIOin2:: getExtThermTime()
{ {
if((_OffHoldoff == 0) || (NVstore.getUserSettings().ThermostatMethod != 3) || (NVstore.getUserSettings().ExtThermoTimeout == 0)) if((_OffHoldoff == 0) || (NVstore.getUserSettings().ThermostatMethod != 3) || (NVstore.getUserSettings().ExtThermoTimeout == 0))
return NULL; 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 ** GPIO out manager
@ -503,7 +511,7 @@ CGPIOout1::begin(int pin, CGPIOout1::Modes mode)
void void
CGPIOout1::setMode(CGPIOout1::Modes mode) CGPIOout1::setMode(CGPIOout1::Modes mode)
{ {
if(mode >= Disabled && mode <= Thresh) if(mode >= Disabled && mode <= HtrActive)
_Mode = mode; _Mode = mode;
_prevState = -1; _prevState = -1;
if(_getPin()) if(_getPin())
@ -523,6 +531,7 @@ CGPIOout1::manage()
case CGPIOout1::Status: _doStatus(); break; case CGPIOout1::Status: _doStatus(); break;
case CGPIOout1::User: _doUser(); break; case CGPIOout1::User: _doUser(); break;
case CGPIOout1::Thresh: _doThresh(); 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 ledcAttachPin(pin, 0); // attach PWM to GPIO line
ledcWrite(0, _statusState); ledcWrite(0, _statusState);
_breatheDelay = millis() + BREATHINTERVAL; _breatheDelay = millis() + BREATHINTERVAL;
_ledState = 2;
break; break;
case 2: case 2:
ledcDetachPin(pin); // detach PWM from IO line ledcDetachPin(pin); // detach PWM from IO line
@ -590,11 +600,13 @@ CGPIOout1::_doStatus()
_statusState = 255; _statusState = 255;
ledcWrite(0, _statusState); ledcWrite(0, _statusState);
_breatheDelay = millis() + BREATHINTERVAL; _breatheDelay = millis() + BREATHINTERVAL;
_ledState = 3;
break; break;
case 4: case 4:
ledcDetachPin(pin); // detach PWM from IO line ledcDetachPin(pin); // detach PWM from IO line
_breatheDelay += (FLASHPERIOD - ONFLASHINTERVAL); // extended off _breatheDelay += (FLASHPERIOD - ONFLASHINTERVAL); // extended off
_setPinState(LOW); _setPinState(LOW);
_ledState = 4;
break; break;
} }
} }
@ -631,7 +643,7 @@ CGPIOout1::_doStopMode() // breath down PWM
_statusState &= 0xff; _statusState &= 0xff;
ledcWrite(0, _statusState); ledcWrite(0, _statusState);
} }
_ledState = 2; _ledState = 3;
} }
void void
@ -657,7 +669,7 @@ CGPIOout1::_doSuspendMode() // brief flash
if(tDelta >= 0) if(tDelta >= 0)
stretch = 0; stretch = 0;
} }
_ledState = stretch ? 1 : 0; _ledState = 4;
} }
uint8_t uint8_t
@ -666,6 +678,7 @@ CGPIOout1::getState()
switch(_Mode) { switch(_Mode) {
case User: case User:
case Thresh: case Thresh:
case HtrActive:
return _getPinState(); return _getPinState();
case Status: case Status:
return _ledState; // special pulse extender for suspend mode return _ledState; // special pulse extender for suspend mode
@ -695,7 +708,7 @@ CGPIOout2::begin(int pin, Modes mode)
void void
CGPIOout2::setMode(CGPIOout2::Modes mode) CGPIOout2::setMode(CGPIOout2::Modes mode)
{ {
if(mode >= Disabled && mode <= Thresh) if(mode >= Disabled && mode <= HtrActive)
_Mode = mode; _Mode = mode;
int pin = _getPin(); int pin = _getPin();
if(pin) if(pin)
@ -714,6 +727,7 @@ CGPIOout2::manage()
case CGPIOout2::Disabled: break; case CGPIOout2::Disabled: break;
case CGPIOout2::User: _doUser(); break; case CGPIOout2::User: _doUser(); break;
case CGPIOout2::Thresh: _doThresh(); break; case CGPIOout2::Thresh: _doThresh(); break;
case CGPIOout2::HtrActive: _doActive(); break;
} }
} }
@ -724,6 +738,7 @@ CGPIOout2::getState()
switch (_Mode) { switch (_Mode) {
case CGPIOout2::User: case CGPIOout2::User:
case CGPIOout2::Thresh: case CGPIOout2::Thresh:
case CGPIOout2::HtrActive:
return _getPinState(); return _getPinState();
default: default:
return 0; return 0;

View File

@ -107,6 +107,7 @@ class CGPIOoutBase {
bool _userState; bool _userState;
int _pin; int _pin;
protected: protected:
void _doActive();
void _doThresh(); void _doThresh();
void _doUser(); void _doUser();
bool _getUserState(); bool _getUserState();
@ -126,7 +127,8 @@ public:
Disabled, Disabled,
Status, Status,
User, User,
Thresh Thresh,
HtrActive
}; };
CGPIOout1(); CGPIOout1();
void begin(int pin, Modes mode); void begin(int pin, Modes mode);
@ -152,7 +154,8 @@ public:
enum Modes { enum Modes {
Disabled, Disabled,
User, User,
Thresh Thresh,
HtrActive,
}; };
CGPIOout2(); CGPIOout2();
void begin(int pin, Modes mode); void begin(int pin, Modes mode);
@ -207,8 +210,8 @@ struct sGPIOparams {
}; };
struct sGPIO { struct sGPIO {
bool outState[2]; uint8_t outState[2];
bool inState[2]; uint8_t inState[2];
int algVal; int algVal;
sGPIO() { sGPIO() {
outState[0] = outState[1] = false; outState[0] = outState[1] = false;

View File

@ -142,26 +142,26 @@ bool makeJSONString(CModerator& moderator, char* opStr, int len)
float tidyTemp = getTemperatureSensor(); float tidyTemp = getTemperatureSensor();
tidyTemp = int(tidyTemp * 10 + 0.5) * 0.1f; // round to 0.1 resolution tidyTemp = int(tidyTemp * 10 + 0.5) * 0.1f; // round to 0.1 resolution
if(tidyTemp > -80) { if(tidyTemp > -80) {
bSend |= moderator.addJson("TempCurrent", tidyTemp, root); bSend |= moderator.addJson("TempCurrent", tidyTemp, root, 5000);
} }
if(getTempSensor().getNumSensors() > 1) { if(getTempSensor().getNumSensors() > 1) {
getTempSensor().getTemperature(1, tidyTemp); getTempSensor().getTemperature(1, tidyTemp);
tidyTemp = int(tidyTemp * 10 + 0.5) * 0.1f; // round to 0.1 resolution tidyTemp = int(tidyTemp * 10 + 0.5) * 0.1f; // round to 0.1 resolution
if(tidyTemp > -80) { if(tidyTemp > -80) {
bSend |= moderator.addJson("Temp2Current", tidyTemp, root); bSend |= moderator.addJson("Temp2Current", tidyTemp, root, 5000);
} }
if(getTempSensor().getNumSensors() > 2) { if(getTempSensor().getNumSensors() > 2) {
getTempSensor().getTemperature(2, tidyTemp); getTempSensor().getTemperature(2, tidyTemp);
tidyTemp = int(tidyTemp * 10 + 0.5) * 0.1f; // round to 0.1 resolution tidyTemp = int(tidyTemp * 10 + 0.5) * 0.1f; // round to 0.1 resolution
if(tidyTemp > -80) { if(tidyTemp > -80) {
bSend |= moderator.addJson("Temp3Current", tidyTemp, root); bSend |= moderator.addJson("Temp3Current", tidyTemp, root, 5000);
} }
} }
if(getTempSensor().getNumSensors() > 3) { if(getTempSensor().getNumSensors() > 3) {
getTempSensor().getTemperature(3, tidyTemp); getTempSensor().getTemperature(3, tidyTemp);
tidyTemp = int(tidyTemp * 10 + 0.5) * 0.1f; // round to 0.1 resolution tidyTemp = int(tidyTemp * 10 + 0.5) * 0.1f; // round to 0.1 resolution
if(tidyTemp > -80) { 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) { if(NVstore.getUserSettings().menuMode < 2) {
bSend |= moderator.addJson("TempMin", getHeaterInfo().getTemperature_Min(), root); bSend |= moderator.addJson("TempMin", getHeaterInfo().getTemperature_Min(), root);
bSend |= moderator.addJson("TempMax", getHeaterInfo().getTemperature_Max(), 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("RunState", getHeaterInfo().getRunStateEx(), root);
bSend |= moderator.addJson("RunString", getHeaterInfo().getRunStateStr(), root); // verbose it up! bSend |= moderator.addJson("RunString", getHeaterInfo().getRunStateStr(), root); // verbose it up!
bSend |= moderator.addJson("ErrorState", getHeaterInfo().getErrState(), root ); 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("PumpActual", getHeaterInfo().getPump_Actual(), root );
bSend |= moderator.addJson("FanMin", getHeaterInfo().getFan_Min(), root ); bSend |= moderator.addJson("FanMin", getHeaterInfo().getFan_Min(), root );
bSend |= moderator.addJson("FanMax", getHeaterInfo().getFan_Max(), root ); bSend |= moderator.addJson("FanMax", getHeaterInfo().getFan_Max(), root );
bSend |= moderator.addJson("FanRPM", getFanSpeed(), root ); bSend |= moderator.addJson("FanRPM", getFanSpeed(), root, 2000 );
bSend |= moderator.addJson("FanVoltage", getHeaterInfo().getFan_Voltage(), root ); bSend |= moderator.addJson("FanVoltage", getHeaterInfo().getFan_Voltage(), root, 2500 );
bSend |= moderator.addJson("FanSensor", getHeaterInfo().getFan_Sensor(), root ); 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("SystemVoltage", getHeaterInfo().getSystemVoltage(), root );
bSend |= moderator.addJson("GlowVoltage", getGlowVolts(), root ); bSend |= moderator.addJson("GlowVoltage", getGlowVolts(), root, 5000 );
bSend |= moderator.addJson("GlowCurrent", getGlowCurrent(), root ); bSend |= moderator.addJson("GlowCurrent", getGlowCurrent(), root, 5000 );
bSend |= moderator.addJson("BluewireStat", getBlueWireStatStr(), root ); 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("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("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("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("PumpCal", NVstore.getHeaterTuning().pumpCal, root); // mL/stroke
bSend |= moderator.addJson("LowVoltCutout", NVstore.getHeaterTuning().getLVC(), root); // low voltage cutout 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("TempOffset", getTempSensor().getOffset(0), root); // degC offset
bSend |= moderator.addJson("TempType", getTempSensor().getID(0), root); // BME280 vs DS18B20 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("Temp4Offset", getTempSensor().getOffset(3), root); // degC offset
bSend |= moderator.addJson("Temp4Type", getTempSensor().getID(3), root); // BME280 vs DS18B20 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) { if(bSend) {
root.printTo(opStr, len); 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("GPmodeIn2", GPIOin2Names[info.in2Mode], root);
bSend |= moderator.addJson("GPmodeOut1", GPIOout1Names[info.out1Mode], root); bSend |= moderator.addJson("GPmodeOut1", GPIOout1Names[info.out1Mode], root);
bSend |= moderator.addJson("GPmodeOut2", GPIOout2Names[info.out2Mode], root); bSend |= moderator.addJson("GPmodeOut2", GPIOout2Names[info.out2Mode], root);
bSend |= moderator.addJson("GPOutThr1", NVstore.getUserSettings().GPIO.thresh[0], root); bSend |= moderator.addJson("GPoutThr1", NVstore.getUserSettings().GPIO.thresh[0], root);
bSend |= moderator.addJson("GPOutThr2", NVstore.getUserSettings().GPIO.thresh[1], root); bSend |= moderator.addJson("GPoutThr2", NVstore.getUserSettings().GPIO.thresh[1], root);
bSend |= moderator.addJson("GPmodeAnlg", GPIOalgNames[info.algMode], root); bSend |= moderator.addJson("GPmodeAnlg", GPIOalgNames[info.algMode], root);
bSend |= moderator.addJson("ExtThermoTmout", (uint32_t)NVstore.getUserSettings().ExtThermoTimeout, root); bSend |= moderator.addJson("ExtThermoTmout", (uint32_t)NVstore.getUserSettings().ExtThermoTimeout, root);
const char* stop = getExternalThermostatHoldTime(); const char* stop = getExternalThermostatHoldTime();
@ -311,7 +323,8 @@ bool makeJSONStringMQTT(CModerator& moderator, char* opStr, int len)
sMQTTparams info = NVstore.getMQTTinfo(); sMQTTparams info = NVstore.getMQTTinfo();
bSend |= moderator.addJson("MEn", info.enabled, root); 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("MHost", info.host, root);
bSend |= moderator.addJson("MPort", info.port, root); bSend |= moderator.addJson("MPort", info.port, root);
bSend |= moderator.addJson("MUser", info.username, 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_STA", getWifiSTAAddrStr(), root);
bSend |= moderator.addJson("IP_STAMAC", getWifiSTAMACStr(), root); bSend |= moderator.addJson("IP_STAMAC", getWifiSTAMACStr(), root);
bSend |= moderator.addJson("IP_STASSID", getSSID().c_str(), 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("IP_OTA", NVstore.getUserSettings().enableOTA, root);
bSend |= moderator.addJson("BT_MAC", getBluetoothClient().getMAC(), root); bSend |= moderator.addJson("BT_MAC", getBluetoothClient().getMAC(), root);
@ -518,10 +533,12 @@ void resetAllJSONmoderators()
#else #else
initJSONTimermoderator(); initJSONTimermoderator();
#endif #endif
initJSONMQTTmoderator(); resetJSONMQTTmoderator(); // initJSONMQTTmoderator();
initJSONIPmoderator(); resetJSONIPmoderator(); // initJSONIPmoderator();
initJSONSysModerator(); resetJSONSysModerator(); // initJSONSysModerator();
GPIOmoderator.reset(); GPIOmoderator.reset();
// create and send a validation code (then client knows AB is capable of reboot over JSON)
doJSONreboot(0);
} }
void initJSONMQTTmoderator() 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
}
}

View File

@ -40,6 +40,7 @@ void resetJSONIPmoderator();
void resetJSONSysModerator(); void resetJSONSysModerator();
void resetJSONMQTTmoderator(); void resetJSONMQTTmoderator();
void validateTimer(int ID); void validateTimer(int ID);
void doJSONreboot(uint16_t code);
template<class T> template<class T>
const char* createJSON(const char* name, T value) const char* createJSON(const char* name, T value)

View File

@ -51,13 +51,42 @@ public:
void reset(const char* name); 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 T> template <class T>
class TModerator { class TModerator {
std::map<const char*, T> Memory; std::map<const char*, T> Memory;
std::map<const char*, sModeratorHoldoff> _holdoff;
public: public:
bool shouldSend(const char* name, T value); 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();
void reset(const char* name); void reset(const char* name);
}; };
@ -69,7 +98,21 @@ bool TModerator<T>::shouldSend(const char* name, T value)
auto it = Memory.find(name); auto it = Memory.find(name);
if(it != Memory.end()) { if(it != Memory.end()) {
retval = it->second != value; 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 { else {
Memory[name] = value; Memory[name] = value;
@ -78,11 +121,26 @@ bool TModerator<T>::shouldSend(const char* name, T value)
} }
template<class T> template<class T>
bool TModerator<T>::addJson(const char* name, T value, JsonObject& root) void TModerator<T>::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<class T>
bool TModerator<T>::addJson(const char* name, T value, JsonObject& root, unsigned long holdoff)
{
setHoldoff(name, holdoff);
bool retval = shouldSend(name, value); bool retval = shouldSend(name, value);
if(retval) if(retval) {
root.set(name, value); root.set(name, value);
}
return retval; return retval;
} }
@ -93,15 +151,26 @@ void TModerator<T>::reset()
for(auto it = Memory.begin(); it != Memory.end(); ++it) { for(auto it = Memory.begin(); it != Memory.end(); ++it) {
it->second = it->second+100; it->second = it->second+100;
} }
for(auto it = _holdoff.begin(); it != _holdoff.end(); ++it) {
it->second.expire();
}
} }
template<class T> template<class T>
void TModerator<T>::reset(const char* name) void TModerator<T>::reset(const char* name)
{ {
auto it = Memory.find(name); {
if(it != Memory.end()) { auto it = Memory.find(name);
DebugPort.printf("Resetting moderator: \"%s\"", name); if(it != Memory.end()) {
it->second = it->second+100; 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); return u32Moderator.addJson(name, value, root);
}; };
// float values // float values
bool addJson(const char* name, float value, JsonObject& root) { bool addJson(const char* name, float value, JsonObject& root, unsigned long holdoff=0) {
return fModerator.addJson(name, value, root); return fModerator.addJson(name, value, root, holdoff);
}; };
// uint8_t values // uint8_t values
bool addJson(const char* name, uint8_t value, JsonObject& root) { bool addJson(const char* name, uint8_t value, JsonObject& root) {

View File

@ -24,6 +24,7 @@
#include "DebugPort.h" #include "DebugPort.h"
#include <functional> #include <functional>
#include <string.h> #include <string.h>
#include <math.h>
#define INBOUNDS(TST, MIN, MAX) (((TST) >= (MIN)) && ((TST) <= (MAX))) #define INBOUNDS(TST, MIN, MAX) (((TST) >= (MIN)) && ((TST) <= (MAX)))
@ -31,20 +32,31 @@
bool bool
CESP32_NVStorage::validatedLoad(const char* key, char* val, int maxlen, const char* defVal) CESP32_NVStorage::validatedLoad(const char* key, char* val, int maxlen, const char* defVal)
{ {
char probe[128];
bool retval = true; bool retval = true;
strcpy(probe, "TestPresence"); if(!preferences.hasString(key)) {
int len = preferences.getString(key, probe, 127);
if(len == 0 || strcmp(probe, "TestPresence") == 0) {
preferences.putString(key, defVal); preferences.putString(key, defVal);
DebugPort.printf("CESP32HeaterStorage::validatedLoad<char*> default installed %s=%s", key, defVal); DebugPort.printf("CESP32HeaterStorage::validatedLoad<char*> default installed %s=%s\r\n", key, defVal);
retval = false;
} }
preferences.getString(key, val, maxlen); preferences.getString(key, val, maxlen);
val[maxlen] = 0; // ensure null terminated val[maxlen] = 0; // ensure null terminated
return retval; 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<uint8_t*> 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 bool
CESP32_NVStorage::validatedLoad(const char* key, uint8_t& val, uint8_t defVal, std::function<bool(uint8_t, uint8_t, uint8_t)> validator, uint8_t min, uint8_t max, uint8_t mask) CESP32_NVStorage::validatedLoad(const char* key, uint8_t& val, uint8_t defVal, std::function<bool(uint8_t, uint8_t, uint8_t)> 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 bool
CESP32_NVStorage::validatedLoad(const char* key, float& val, float defVal, float min, float max) 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)) { if(!INBOUNDS(val, min, max)) {
DebugPort.printf("CESP32HeaterStorage::validatedLoad<float> invalid read %s=%f", key, val); DebugPort.printf("CESP32HeaterStorage::validatedLoad<float> invalid read %s=%f", key, val);
DebugPort.printf(" validator(%f,%f) reset to %f\r\n", min, max, defVal); DebugPort.printf(" validator(%f,%f) reset to %f\r\n", min, max, defVal);
val = 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 false;
} }
return true; 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) bool finBounds(float test, float minLim, float maxLim)
{ {
return INBOUNDS(test, minLim, maxLim); return INBOUNDS(test, minLim, maxLim);

View File

@ -22,7 +22,7 @@
#ifndef __BTC_NV_CORE_H__ #ifndef __BTC_NV_CORE_H__
#define __BTC_NV_CORE_H__ #define __BTC_NV_CORE_H__
#include <Preferences.h> #include "ABpreferences.h"
#include <functional> #include <functional>
@ -46,15 +46,17 @@ class CNVStorage {
class CESP32_NVStorage { class CESP32_NVStorage {
protected: protected:
Preferences preferences; ABpreferences preferences;
protected: protected:
bool validatedLoad(const char* key, int8_t& val, int8_t defVal, std::function<bool(int8_t, int8_t, int8_t)> validator, int8_t min, int8_t max); bool validatedLoad(const char* key, int8_t& val, int8_t defVal, std::function<bool(int8_t, int8_t, int8_t)> validator, int8_t min, int8_t max);
bool validatedLoad(const char* key, uint8_t& val, uint8_t defVal, std::function<bool(uint8_t, uint8_t, uint8_t)> validator, uint8_t min, uint8_t max, uint8_t mask=0xff); bool validatedLoad(const char* key, uint8_t& val, uint8_t defVal, std::function<bool(uint8_t, uint8_t, uint8_t)> validator, uint8_t min, uint8_t max, uint8_t mask=0xff);
bool validatedLoad(const char* key, uint16_t& val, uint16_t defVal, std::function<bool(uint16_t, uint16_t, uint16_t)> validator, uint16_t min, uint16_t max); bool validatedLoad(const char* key, uint16_t& val, uint16_t defVal, std::function<bool(uint16_t, uint16_t, uint16_t)> 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, 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, 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); 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);
}; };

View File

@ -23,7 +23,7 @@
#include "NVStorage.h" #include "NVStorage.h"
#include "DebugPort.h" #include "DebugPort.h"
#include <driver/adc.h> #include <driver/adc.h>
#include <string.h>
bool bool
sNVStore::valid() sNVStore::valid()
@ -131,6 +131,10 @@ CHeaterStorage::getMQTTinfo() const
void void
CHeaterStorage::setMQTTinfo(const sMQTTparams& info) CHeaterStorage::setMQTTinfo(const sMQTTparams& info)
{ {
if(_calValues.MQTT != info) {
requestMQTTrestart();
}
_calValues.MQTT = info; _calValues.MQTT = info;
} }
@ -268,13 +272,18 @@ sHeaterTuning::load()
validatedLoad("tempOffset0", DS18B20probe[0].offset, 0.0, -10.0, +10.0); validatedLoad("tempOffset0", DS18B20probe[0].offset, 0.0, -10.0, +10.0);
validatedLoad("tempOffset1", DS18B20probe[1].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); validatedLoad("tempOffset2", DS18B20probe[2].offset, 0.0, -10.0, +10.0);
preferences.getBytes("probeSerial0", DS18B20probe[0].romCode.bytes, 8); memset(DS18B20probe[0].romCode.bytes, 0, 8);
preferences.getBytes("probeSerial1", DS18B20probe[1].romCode.bytes, 8); memset(DS18B20probe[1].romCode.bytes, 0, 8);
preferences.getBytes("probeSerial2", DS18B20probe[2].romCode.bytes, 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("tempOffsetBME", BME280probe.offset, 0.0, -10.0, +10.0);
validatedLoad("probeBMEPrmy", BME280probe.bPrimary, 0, u8inBounds, 0, 1); validatedLoad("probeBMEPrmy", BME280probe.bPrimary, 0, u8inBounds, 0, 1);
preferences.end(); preferences.end();
// save();
// for(int i=0; i<3; i++) { // for(int i=0; i<3; i++) {
// DebugPort.printf("Rd Probe[%d] %02X:%02X:%02X:%02X:%02X:%02X\r\n", // DebugPort.printf("Rd Probe[%d] %02X:%02X:%02X:%02X:%02X:%02X\r\n",
// i, // i,
@ -291,6 +300,20 @@ sHeaterTuning::load()
void void
sHeaterTuning::save() 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 // section for heater calibration params
// **** MAX LENGTH is 15 for names **** // **** MAX LENGTH is 15 for names ****
preferences.begin("Calibration", false); preferences.begin("Calibration", false);
@ -302,15 +325,55 @@ sHeaterTuning::save()
preferences.putUChar("fanSensor", fanSensor); preferences.putUChar("fanSensor", fanSensor);
preferences.putUChar("glowDrive", glowDrive); preferences.putUChar("glowDrive", glowDrive);
preferences.putUChar("lowVolts", lowVolts); preferences.putUChar("lowVolts", lowVolts);
preferences.putFloat("pumpCal", pumpCal); saveFloat("pumpCal", pumpCal);
preferences.putFloat("tempOffset0", DS18B20probe[0].offset); saveFloat("tempOffset0", DS18B20probe[0].offset);
preferences.putFloat("tempOffset1", DS18B20probe[1].offset); saveFloat("tempOffset1", DS18B20probe[1].offset);
preferences.putFloat("tempOffset2", DS18B20probe[2].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("probeSerial0", DS18B20probe[0].romCode.bytes, 8);
preferences.putBytes("probeSerial1", DS18B20probe[1].romCode.bytes, 8); preferences.putBytes("probeSerial1", DS18B20probe[1].romCode.bytes, 8);
preferences.putBytes("probeSerial2", DS18B20probe[2].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); 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(); preferences.end();
// for(int i=0; i<3; i++) { // for(int i=0; i<3; i++) {
@ -417,8 +480,8 @@ sUserSettings::load()
preferences.putUChar("GPIOout1Mode", GPIO.out1Mode); // set new preferences.putUChar("GPIOout1Mode", GPIO.out1Mode); // set new
preferences.putUChar("GPIOout2Mode", GPIO.out2Mode); // set new preferences.putUChar("GPIOout2Mode", GPIO.out2Mode); // set new
} }
validatedLoad("GPIOout1Mode", tVal, 0, u8inBounds, 0, 3); GPIO.out1Mode = (CGPIOout1::Modes)tVal; validatedLoad("GPIOout1Mode", tVal, 0, u8inBounds, 0, 4); GPIO.out1Mode = (CGPIOout1::Modes)tVal;
validatedLoad("GPIOout2Mode", tVal, 0, u8inBounds, 0, 2); GPIO.out2Mode = (CGPIOout2::Modes)tVal; validatedLoad("GPIOout2Mode", tVal, 0, u8inBounds, 0, 3); GPIO.out2Mode = (CGPIOout2::Modes)tVal;
validatedLoad("GPIOout1Thresh", GPIO.thresh[0], 0, s8inBounds, -50, 50); validatedLoad("GPIOout1Thresh", GPIO.thresh[0], 0, s8inBounds, -50, 50);
validatedLoad("GPIOout2Thresh", GPIO.thresh[1], 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; validatedLoad("GPIOalgMode", tVal, 0, u8inBounds, 0, 2); GPIO.algMode = (CGPIOalg::Modes)tVal;
@ -447,7 +510,8 @@ sUserSettings::save()
preferences.putUChar("thermostat", useThermostat); preferences.putUChar("thermostat", useThermostat);
preferences.putUChar("degF", degF); preferences.putUChar("degF", degF);
preferences.putUChar("thermoMethod", ThermostatMethod); preferences.putUChar("thermoMethod", ThermostatMethod);
preferences.putFloat("thermoWindow", ThermostatWindow); // preferences.putFloat("thermoWindow", ThermostatWindow);
saveFloat("thermoWindow", ThermostatWindow);
preferences.putUChar("frostOn", FrostOn); preferences.putUChar("frostOn", FrostOn);
preferences.putUChar("frostRise", FrostRise); preferences.putUChar("frostRise", FrostRise);
// preferences.putUChar("enableWifi", enableWifi); // preferences.putUChar("enableWifi", enableWifi);
@ -484,9 +548,9 @@ sMQTTparams::load()
validatedLoad("enabled", enabled, 0, u8inBounds, 0, 1); validatedLoad("enabled", enabled, 0, u8inBounds, 0, 1);
validatedLoad("port", port, 1883, u16inBounds, 0, 0xffff); validatedLoad("port", port, 1883, u16inBounds, 0, 0xffff);
validatedLoad("qos", qos, 0, u8inBounds, 0, 2); validatedLoad("qos", qos, 0, u8inBounds, 0, 2);
validatedLoad("host", host, 127, ""); validatedLoad("host", host, 127, "broker");
validatedLoad("username", username, 31, ""); validatedLoad("username", username, 31, "username");
validatedLoad("password", password, 31, ""); validatedLoad("password", password, 31, "password");
validatedLoad("topic", topicPrefix, 31, "Afterburner"); validatedLoad("topic", topicPrefix, 31, "Afterburner");
preferences.end(); preferences.end();
} }
@ -572,4 +636,4 @@ sHourMeter::load()
validatedLoad("GlowTime", GlowTime, 0, 0, 0xffffffffL); validatedLoad("GlowTime", GlowTime, 0, 0, 0xffffffffL);
preferences.end(); preferences.end();
DebugPort.printf("Hourmeter NV read: Run=%d, Glow=%d\r\n", RunTime, GlowTime); DebugPort.printf("Hourmeter NV read: Run=%d, Glow=%d\r\n", RunTime, GlowTime);
} }

View File

@ -272,6 +272,16 @@ struct sMQTTparams : public CESP32_NVStorage {
topicPrefix[31] = 0; topicPrefix[31] = 0;
return *this; 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 load();
void save(); void save();
bool valid(); bool valid();

View File

@ -432,7 +432,7 @@ CBME280Sensor::getTemperature(float& tempReading, bool filtered)
} }
CSensor::getTemperature(tempReading, filtered); CSensor::getTemperature(tempReading, filtered);
tempReading += NVstore.getHeaterTuning().BME280probe.offset;; // tempReading += NVstore.getHeaterTuning().BME280probe.offset;;
return true; return true;
} }
@ -559,16 +559,26 @@ CTempSense::setOffset(int usrIdx, float offset)
bool bool
CTempSense::getTemperature(int usrIdx, float& temperature, bool filtered) CTempSense::getTemperature(int usrIdx, float& temperature, bool filtered)
{ {
bool bRetVal = false;
float offset = 0;
switch(getSensorType(usrIdx)) { switch(getSensorType(usrIdx)) {
case 0: case 0:
return BME280.getTemperature(temperature, filtered); bRetVal = BME280.getTemperature(temperature, filtered);
offset = getOffset(usrIdx);
break;
case 1: case 1:
return DS18B20.getTemperature(usrIdx, temperature, filtered); bRetVal = DS18B20.getTemperature(usrIdx, temperature, filtered);
offset = getOffset(usrIdx);
break;
case 2: case 2:
return DS18B20.getTemperature(usrIdx-1, temperature, filtered); bRetVal = DS18B20.getTemperature(usrIdx-1, temperature, filtered);
default: offset = getOffset(usrIdx-1);
return false; break;
} }
if(bRetVal) {
temperature += offset;
}
return bRetVal;
} }
int int

View File

@ -422,10 +422,22 @@ void DecodeCmd(const char* cmd, String& payload)
else if(strcmp("FrostRise", cmd) == 0) { else if(strcmp("FrostRise", cmd) == 0) {
sUserSettings us = NVstore.getUserSettings(); sUserSettings us = NVstore.getUserSettings();
us.FrostRise = payload.toInt(); us.FrostRise = payload.toInt();
if(INBOUNDS(us.FrostRise, 1, 30)) { if(INBOUNDS(us.FrostRise, 0, 30)) {
NVstore.setUserSettings(us); NVstore.setUserSettings(us);
NVstore.save(); 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);
}
} }

View File

@ -90,6 +90,7 @@ extern int sysUptime();
extern void doJSONwatchdog(int topup); extern void doJSONwatchdog(int topup);
extern void reloadScreens(); extern void reloadScreens();
extern CTempSense& getTempSensor() ; extern CTempSense& getTempSensor() ;
extern void reqHeaterCalUpdate();
void setSSID(const char* name); void setSSID(const char* name);
@ -99,5 +100,6 @@ extern void ShowOTAScreen(int percent=0, eOTAmodes updateType=eOTAnormal);
extern void updateMQTT(); extern void updateMQTT();
extern void refreshMQTT(); extern void refreshMQTT();
extern void requestMQTTrestart();
#endif #endif

View File

@ -51,7 +51,8 @@ extern void DecodeCmd(const char* cmd, String& payload);
AsyncMqttClient MQTTclient; AsyncMqttClient MQTTclient;
char topicnameJSONin[128]; char topicnameJSONin[128];
char topicnameCmd[128]; char topicnameCmd[128];
CModerator MQTTmoderator; CModerator MQTTmoderator; // for basic MQTT interface
unsigned long MQTTrestart = 0;
void subscribe(const char* topic); void subscribe(const char* topic);
@ -100,14 +101,10 @@ void onMqttConnect(bool sessionPresent)
sprintf(statusTopic, "%s/status", NVstore.getMQTTinfo().topicPrefix); sprintf(statusTopic, "%s/status", NVstore.getMQTTinfo().topicPrefix);
sprintf(topicnameJSONin, "%s/JSONin", NVstore.getMQTTinfo().topicPrefix); sprintf(topicnameJSONin, "%s/JSONin", NVstore.getMQTTinfo().topicPrefix);
sprintf(topicnameCmd, "%s/cmd/#", NVstore.getMQTTinfo().topicPrefix); sprintf(topicnameCmd, "%s/cmd/#", NVstore.getMQTTinfo().topicPrefix);
// subscribe to that topic
// DebugPort.printf("MQTT: Subscribing to \"%s\"\r\n", topicnameJSONin); subscribe(topicnameJSONin); // subscribe to the JSONin topic
// MQTTclient.subscribe(topicnameJSONin, NVstore.getMQTTinfo().qos); subscribe(topicnameCmd); // subscribe to the basic command topic
// MQTTclient.subscribe(topicnameCmd, NVstore.getMQTTinfo().qos); subscribe(statusTopic); // subscribe to the status topic
// MQTTclient.subscribe(statusTopic, NVstore.getMQTTinfo().qos);
subscribe(topicnameJSONin);
subscribe(topicnameCmd);
subscribe(statusTopic);
// spit out an "I'm here" message // spit out an "I'm here" message
MQTTclient.publish(statusTopic, NVstore.getMQTTinfo().qos, true, "online"); 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 else if(strcmp(topic, statusTopic) == 0) { // check if incoming topic is our general status
if(strcmp(szPayload, "1") == 0) { if(strcmp(szPayload, "1") == 0) {
MQTTmoderator.reset(); // MQTTmoderator.reset();
MQTTclient.publish(statusTopic, NVstore.getMQTTinfo().qos, true, "online"); MQTTclient.publish(statusTopic, NVstore.getMQTTinfo().qos, true, "online");
} }
} }
@ -199,9 +196,11 @@ bool mqttInit()
#else #else
mqttReconnect = 0; mqttReconnect = 0;
#endif #endif
MQTTrestart = 0;
memset(topicnameJSONin, 0, sizeof(topicnameJSONin)); memset(topicnameJSONin, 0, sizeof(topicnameJSONin));
DebugPort.println("MQTT: Initialising...");
MQTTclient.disconnect(true); MQTTclient.disconnect(true);
long escape = millis() + 10000; long escape = millis() + 10000;
while(MQTTclient.connected()) { while(MQTTclient.connected()) {
@ -274,6 +273,16 @@ void kickMQTT() {
void doMQTT() 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!!! // most MQTT is managed via callbacks!!!
if(NVstore.getMQTTinfo().enabled) { if(NVstore.getMQTTinfo().enabled) {
#ifndef USE_RTOS_MQTTTIMER #ifndef USE_RTOS_MQTTTIMER
@ -411,4 +420,9 @@ void subscribe(const char* topic)
MQTTclient.subscribe(topic, NVstore.getMQTTinfo().qos); MQTTclient.subscribe(topic, NVstore.getMQTTinfo().qos);
} }
void requestMQTTrestart()
{
MQTTrestart = (millis() + 1000) | 1;
}
#endif #endif

View File

@ -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) bool handleFileRead(String path) { // send the right file to the client (if it exists)
DebugPort.println("handleFileRead: " + path); DebugPort.println("handleFileRead: " + path);
if (path.endsWith("/")) path += "index.html"; // If a folder is requested, send the index file 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 contentType = getContentType(path); // Get the MIME type
String pathWithGz = path + ".gz"; String pathWithGz = path + ".gz";
if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) { // If the file exists as a compressed archive, or normal 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 ers;
String rename; String rename;
if(withHTMLanchors == 2) { if(withHTMLanchors == 2) {
rename = "<button class='rename' onClick=onRename('" + fn + "')>Rename</button>"; String htmlNm = fn;
ers = "<input class='del' type='button' value='X' onClick=onErase('" + fn + "')>"; htmlNm.replace(" ", "%20");
rename = "<button class='rename' onClick=onRename('" + htmlNm + "')>Rename</button>";
ers = "<input class='del' type='button' value='X' onClick=onErase('" + htmlNm + "')>";
} }
if(withHTMLanchors) { if(withHTMLanchors) {
String fn2; 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" fn2.remove(fn2.length()-3, 3); // strip trailing ".gz"
} }
if(fn2.length() != 0) { if(fn2.length() != 0) {
fn2.replace(" ", "%20");
// create hyperlink if web page file // create hyperlink if web page file
fn = "<a href=\"" + fn2 + "\">" + file.name() + "</a>"; fn = "<a href=\"" + fn2 + "\">" + file.name() + "</a>";
} }
@ -678,6 +682,7 @@ void addTableData(String& HTML, String dta)
void onErase() void onErase()
{ {
String filename = server.arg("filename"); // get request argument value by name String filename = server.arg("filename"); // get request argument value by name
filename.replace("%20", " "); // convert HTML spaces to real spaces
if(filename.length() != 0) { if(filename.length() != 0) {
DebugPort.printf("onErase: %s ", filename.c_str()); DebugPort.printf("onErase: %s ", filename.c_str());
@ -986,6 +991,8 @@ void onRename()
DebugPort.println("WEB: POST /reboot"); DebugPort.println("WEB: POST /reboot");
String oldname = server.arg("oldname"); // get request argument value by name String oldname = server.arg("oldname"); // get request argument value by name
String newname = server.arg("newname"); // 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 != "") { if(oldname != "" && newname != "") {
DebugPort.printf("Renaming %s to %s\r\n", oldname.c_str(), newname.c_str()); DebugPort.printf("Renaming %s to %s\r\n", oldname.c_str(), newname.c_str());
SPIFFS.rename(oldname.c_str(), newname.c_str()); SPIFFS.rename(oldname.c_str(), newname.c_str());

View File

@ -279,6 +279,38 @@ const char* getWifiSTAAddrStr()
else else
return ""; 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() const char* getWifiAPMACStr()
{ {

View File

@ -28,8 +28,10 @@ void doWiFiManager();
bool initWifi(const char *failedssid, const char *failedpassword); bool initWifi(const char *failedssid, const char *failedpassword);
const char* getWifiAPAddrStr(); const char* getWifiAPAddrStr();
const char* getWifiSTAAddrStr(); const char* getWifiSTAAddrStr();
const char* getWifiGatewayAddrStr();
const char* getWifiAPMACStr(); const char* getWifiAPMACStr();
const char* getWifiSTAMACStr(); const char* getWifiSTAMACStr();
int8_t getWifiRSSI();
String getSSID(); String getSSID();
bool isWifiConnected(); bool isWifiConnected();

View File

@ -63,6 +63,8 @@ sBrowserUpload::begin(String& filename, int filesize)
} }
} }
else { else {
SrcFile.name.replace("%20", " "); // convert HTML spaces to real spaces
// SPIFFS UPLOAD START // SPIFFS UPLOAD START
DebugPort.printf("Starting SPIFFS upload: %s\r\n", SrcFile.name.c_str()); DebugPort.printf("Starting SPIFFS upload: %s\r\n", SrcFile.name.c_str());