Implemented mechanism for thermostat hysteresis. Need to build UI.

This commit is contained in:
rljonesau 2019-03-14 21:52:59 +11:00
parent 220657956f
commit 2fc020ae6c
13 changed files with 165 additions and 42 deletions

View file

@ -770,7 +770,6 @@ void loop()
DS18B20holdoff = 2;
fFilteredTemperature = -100;
}
DefaultBTCParams.setTemperature_Actual((unsigned char)(fFilteredTemperature + 0.5)); // update [BTC] frame to send
// Added DISABLE INTERRUPTS to test for parasitic fix.
// portDISABLE_INTERRUPTS();
TempSensor.requestTemperatures(); // prep sensor for future reading
@ -891,7 +890,7 @@ int getSetTemp()
bool reqThermoToggle()
{
return setThermostatMode(getThermostatMode() ? 0 : 1);
return setThermostatMode(getThermostatModeActive() ? 0 : 1);
}
bool setThermostatMode(unsigned char val)
@ -904,9 +903,14 @@ bool setThermostatMode(unsigned char val)
}
bool getThermostatMode()
bool getThermostatModeActive()
{
return NVstore.getThermostatMode() != 0;
if(bHasOEMController) {
return getHeaterInfo().isThermostat();
}
else {
return NVstore.getThermostatMode() != 0;
}
}
void checkDisplayUpdate()
@ -932,7 +936,17 @@ void reqPumpPrime(bool on)
DefaultBTCParams.setPump_Prime(on);
}
float getActualTemperature()
float getTemperatureDesired()
{
if(bHasOEMController) {
return getHeaterInfo().getTemperature_Desired();
}
else {
return NVstore.getDesiredTemperature();
}
}
float getTemperatureSensor()
{
return fFilteredTemperature;
}

View file

@ -54,7 +54,7 @@ CBasicScreen::show()
char msg[20];
int xPos, yPos;
float fTemp = getActualTemperature();
float fTemp = getTemperatureSensor();
if(fTemp > -80) {
if(NVstore.getDegFMode()) {
fTemp = fTemp * 9 / 5 + 32;
@ -110,8 +110,8 @@ CBasicScreen::show()
long tDelta = millis() - _showSetMode;
if(tDelta < 0) {
// Show current heat demand setting
if(getHeaterInfo().isThermostat()) {
float fTemp = getHeaterInfo().getTemperature_Desired();
if(getThermostatModeActive()) {
float fTemp = getTemperatureDesired();
if(NVstore.getDegFMode()) {
fTemp = fTemp * 9 / 5 + 32;
sprintf(msg, "Setpoint = %.0f`F", fTemp);
@ -191,7 +191,7 @@ CBasicScreen::keyHandler(uint8_t event)
if(repeatCount > 2) {
repeatCount = -1; // prevent double handling
_showMode = millis() + 5000;
_nModeSel = getHeaterInfo().isThermostat() ? 0 : 1;
_nModeSel = getThermostatModeActive() ? 0 : 1;
}
}
// hold UP to toggle degC/degF mode selection

View file

@ -79,7 +79,7 @@ CDetailedScreen::show()
{
CScreenHeader::show();
const char* c = String(getActualTemperature()).c_str();
const char* c = String(getTemperatureSensor()).c_str();
int runstate = getHeaterInfo().getRunState();//HtrFrame.getRunState();
int errstate = getHeaterInfo().getErrState(); //HtrFrame.getErrState();
@ -92,13 +92,13 @@ CDetailedScreen::show()
float desiredT = 0;
if((runstate && (runstate <= 5)) || _showTarget) {
if(getHeaterInfo().isThermostat())
if(getThermostatModeActive())
desiredT = getHeaterInfo().getTemperature_Desired();
else
desiredT = -getHeaterInfo().getPump_Fixed();
}
float fTemp = getActualTemperature();
float fTemp = getTemperatureSensor();
showThermometer(desiredT, // read values from most recently sent [BTC] frame
fTemp);

View file

@ -310,11 +310,9 @@ CProtocol::DebugReport(const char* hdr, const char* ftr)
}
void
CProtocol::setThermostatMode(unsigned on)
CProtocol::setThermostatModeProtocol(unsigned on)
{
Controller.OperatingMode = on ? 0x32 : 0xCD;
if(!on)
setTemperature_Actual(0); // if using fixed mode, actual must be reported as 0
};
void

View file

@ -183,7 +183,7 @@ public:
unsigned char getTemperature_Min() const { return Controller.MinTemperature; };
unsigned char getTemperature_Max() const { return Controller.MaxTemperature; };
unsigned char getTemperature_Actual() const { return Controller.ActualTemperature; };
void setThermostatMode(unsigned on);
void setThermostatModeProtocol(unsigned on);
bool isThermostat() const { return Controller.OperatingMode == 0x32; };
// glow plug
float getGlowPlug_Current() const; // glow plug current

View file

@ -21,6 +21,7 @@
#include "TxManage.h"
#include "../Utility/NVStorage.h"
#include "../Protocol/helpers.h"
extern void DebugReportFrame(const char* hdr, const CProtocol&, const char* ftr);
@ -117,8 +118,77 @@ CTxManage::PrepareFrame(const CProtocol& basisFrame, bool isBTCmaster)
m_TxFrame.setFan_Max(NVstore.getFmax());
m_TxFrame.setPump_Min(NVstore.getPmin());
m_TxFrame.setPump_Max(NVstore.getPmax());
m_TxFrame.setThermostatMode(NVstore.getThermostatMode());
float tActual = getTemperatureSensor();
uint8_t u8Temp = (uint8_t)(tActual);
m_TxFrame.setTemperature_Actual(u8Temp); // use current temp, for now
m_TxFrame.setTemperature_Desired(NVstore.getDesiredTemperature());
if(NVstore.getThermostatMode()) {
uint8_t ThermoMode = NVstore.getThermostatMethodMode(); // get the METHOD of thermostat control
float Hysteresis = NVstore.getThermostatMethodHysteresis();
float tCurrent = getTemperatureSensor();
float tDesired = float(NVstore.getDesiredTemperature());
float tDelta = tCurrent - tDesired;
#ifdef DEBUG_THERMOSTAT
DebugPort.print("Hysteresis = "); DebugPort.print(Hysteresis); DebugPort.print(" tCurrent = "); DebugPort.print(tCurrent); DebugPort.print(" tDesired = "); DebugPort.print(tDesired); DebugPort.print(" tDelta = "); DebugPort.println(tDelta);
#endif
switch(ThermoMode) {
case 0: // conventional heater controlled thermostat mode
m_TxFrame.setThermostatModeProtocol(1); // using heater thermostat control
u8Temp = (uint8_t)(tActual + 0.5);
m_TxFrame.setTemperature_Actual(u8Temp);
#ifdef DEBUG_THERMOSTAT
DebugPort.print("Conventional thermostat mode: tActual = "); DebugPort.println(u8Temp);
#endif
break;
case 1: // heater controlled thermostat mode - BUT actual temp is tweaked via a changed hysteresis
m_TxFrame.setThermostatModeProtocol(1); // using heater thermostat control
u8Temp = (uint8_t)(tActual + 0.5); // use rounded actual unless within hysteresis window
if(fabs(tDelta) < Hysteresis) {
// hold at desired if inside hysteresis
u8Temp = NVstore.getDesiredTemperature();
}
else if(fabs(tDelta) <= 1.0) {
// force outside if delta is <= 1 but greater than hysteresis
u8Temp = NVstore.getDesiredTemperature() + ((tDelta > 0) ? 1 : -1);
}
m_TxFrame.setTemperature_Actual(u8Temp);
#ifdef DEBUG_THERMOSTAT
DebugPort.print("Heater hysteresis thermostat mode: tActual = "); DebugPort.println(u8Temp);
#endif
break;
case 2: // BTC controlled thermostat mode
// map Hysteresis to a Hz value,
// Hz mode however uses the desired temperature field, somewhere between 8 - 35 for min/max
// so create a desired "temp" according the the current hystersis
tDelta /= Hysteresis; // convert tDelta to fraction of hysteresis (CAUTION - may be > +-1 !)
#ifdef DEBUG_THERMOSTAT
DebugPort.print("Controller hysteresis thermostat mode: Fraction = "); DebugPort.print(tDelta);
#endif
Hysteresis = (m_TxFrame.getTemperature_Max() + m_TxFrame.getTemperature_Min()) * 0.5; // midpoint - tDelta = 0 hinges here
tDelta *= (m_TxFrame.getTemperature_Max() - Hysteresis); // linear offset from setpoint
Hysteresis -= tDelta; // lower Hz when over temp, higher Hz when under!
// bounds limit - recall original tDelta was NOT managed prior!
LOWERLIMIT(Hysteresis, m_TxFrame.getTemperature_Min());
UPPERLIMIT(Hysteresis, m_TxFrame.getTemperature_Max());
// apply modifed desired temperature (works in conjunction with thermostatmode = 0!)
u8Temp = (uint8_t)(Hysteresis + 0.5);
m_TxFrame.setTemperature_Desired(u8Temp);
m_TxFrame.setThermostatModeProtocol(0); // direct heater to use Hz Mode
m_TxFrame.setTemperature_Actual(0); // must force actual to 0 for Hz mode
#ifdef DEBUG_THERMOSTAT
DebugPort.print(" tDesired (pseudo Hz demand) = "); DebugPort.println(u8Temp);
#endif
break;
}
}
else {
m_TxFrame.setThermostatModeProtocol(0); // not using any form of thermostat control
m_TxFrame.setTemperature_Actual(0); // must force actual to 0 for Hz mode
}
// m_TxFrame.setThermostatMode(NVstore.getThermostatMode());
m_TxFrame.Controller.OperatingVoltage = NVstore.getSysVoltage();
m_TxFrame.Controller.FanSensor = NVstore.getFanSensor();
m_TxFrame.Controller.GlowDrive = NVstore.getGlowDrive();

View file

@ -30,8 +30,10 @@ extern bool reqTempDelta(int delta);
extern bool reqTemp(unsigned char newTemp);
extern bool reqThermoToggle();
extern bool setThermostatMode(unsigned char);
extern bool getThermostatModeActive(); // OEM: actual mode from blue wire, BTC: or our NV
extern void reqPumpPrime(bool on);
extern float getActualTemperature();
float getTemperatureDesired(); // OEM: the advertised value, BTC our setpoint
extern float getTemperatureSensor();
extern int getSetTemp();
extern void setPumpMin(float);
extern void setPumpMax(float);

View file

@ -25,7 +25,7 @@
#include "BTCDateTime.h"
void decodeTimerDays(const char* ipStr)
void decodeJSONTimerDays(const char* ipStr)
{
char dayInfo[32];
int timerIdx;
@ -53,7 +53,7 @@ void decodeTimerDays(const char* ipStr)
}
void decodeTimerTime(int stop, const char* ipStr)
void decodeJSONTimerTime(int stop, const char* ipStr)
{
int hour, min;
int timerIdx;
@ -75,7 +75,7 @@ void decodeTimerTime(int stop, const char* ipStr)
}
}
void decodeTimerNumeric(int valID, const char* ipStr)
void decodeJSONTimerNumeric(int valID, const char* ipStr)
{
int value;
int timerIdx;

View file

@ -81,8 +81,8 @@ struct sTimer {
};
const char* getTimerJSONStr(int timer, int param);
void decodeTimerDays(const char* str);
void decodeTimerTime(int stop, const char*);
void decodeTimerNumeric(int repeat, const char*);
void decodeJSONTimerDays(const char* str);
void decodeJSONTimerTime(int stop, const char*);
void decodeJSONTimerNumeric(int repeat, const char*);
#endif

View file

@ -115,22 +115,22 @@ void interpretJsonCommand(char* pLine)
}
else if(strcmp("TimerDays", it->key) == 0) {
// value encoded as "ID Days,Days"
decodeTimerDays(it->value.as<const char*>());
decodeJSONTimerDays(it->value.as<const char*>());
}
else if(strcmp("TimerStart", it->key) == 0) {
// value encoded as "ID HH:MM"
decodeTimerTime(0, it->value.as<const char*>());
decodeJSONTimerTime(0, it->value.as<const char*>());
}
else if(strcmp("TimerStop", it->key) == 0) {
// value encoded as "ID HH:MM"
decodeTimerTime(1, it->value.as<const char*>());
decodeJSONTimerTime(1, it->value.as<const char*>());
}
else if(strcmp("TimerRepeat", it->key) == 0) {
// value encoded as "ID val"
decodeTimerNumeric(0, it->value.as<const char*>());
decodeJSONTimerNumeric(0, it->value.as<const char*>());
}
else if(strcmp("TimerTemp", it->key) == 0) {
decodeTimerNumeric(1, it->value.as<const char*>());
decodeJSONTimerNumeric(1, it->value.as<const char*>());
}
else if(strcmp("TimerConflict", it->key) == 0) {
validateTimer(it->value.as<int>());
@ -152,22 +152,22 @@ void validateTimer(int ID)
timerConflict = CTimerManager::conflictTest(ID); // check targeted timer against other timers
TimerModerator.reset(ID); // ensure we fully update client with our understanding of selected timer
TimerModerator.reset(ID); // ensure we update client with our (real) version of the selected timer
}
bool makeJsonString(CModerator& moderator, char* opStr, int len)
bool makeJSONString(CModerator& moderator, char* opStr, int len)
{
StaticJsonBuffer<800> jsonBuffer; // create a JSON buffer on the stack
JsonObject& root = jsonBuffer.createObject(); // create object to add JSON commands to
bool bSend = false; // reset should send flag
float tidyTemp = getActualTemperature();
float tidyTemp = getTemperatureSensor();
tidyTemp = int(tidyTemp * 10) * 0.1f; // round to 0.1 resolution
if(tidyTemp > -80) {
bSend |= moderator.addJson("TempCurrent", tidyTemp, root);
}
bSend |= moderator.addJson("TempDesired", getHeaterInfo().getTemperature_Desired(), root);
bSend |= moderator.addJson("TempDesired", getTemperatureDesired(), root);
bSend |= moderator.addJson("TempMin", getHeaterInfo().getTemperature_Min(), root);
bSend |= moderator.addJson("TempMax", getHeaterInfo().getTemperature_Max(), root);
bSend |= moderator.addJson("TempBody", getHeaterInfo().getTemperature_HeatExchg(), root);
@ -175,7 +175,7 @@ bool makeJsonString(CModerator& moderator, char* opStr, int len)
bSend |= moderator.addJson("RunString", getHeaterInfo().getRunStateStr(), root); // verbose it up!
bSend |= moderator.addJson("ErrorState", getHeaterInfo().getErrState(), root );
bSend |= moderator.addJson("ErrorString", getHeaterInfo().getErrStateStrEx(), root); // verbose it up!
bSend |= moderator.addJson("Thermostat", getHeaterInfo().isThermostat(), root );
bSend |= moderator.addJson("Thermostat", getThermostatModeActive(), root );
bSend |= moderator.addJson("PumpFixed", getHeaterInfo().getPump_Fixed(), root );
bSend |= moderator.addJson("PumpMin", getHeaterInfo().getPump_Min(), root );
bSend |= moderator.addJson("PumpMax", getHeaterInfo().getPump_Max(), root );
@ -204,7 +204,7 @@ bool makeJsonString(CModerator& moderator, char* opStr, int len)
// timer
// Only timer parameters that have changed will be sent, after reset the typical string will be
// {"TimerStart":XX:XX,"TimerStop":XX:XX,"TimerDays":XX,"TimerRepeat":X}
bool makeJsonTimerString(int channel, char* opStr, int len)
bool makeJSONTimerString(int channel, char* opStr, int len)
{
StaticJsonBuffer<800> jsonBuffer; // create a JSON buffer on the stack
JsonObject& root = jsonBuffer.createObject(); // create object to add JSON commands to
@ -229,7 +229,7 @@ void updateJSONclients(bool report)
// update general parameters
char jsonStr[800];
{
if(makeJsonString(JSONmoderator, jsonStr, sizeof(jsonStr))) {
if(makeJSONString(JSONmoderator, jsonStr, sizeof(jsonStr))) {
if (report) {
DebugPort.print("JSON send: "); DebugPort.println(jsonStr);
}
@ -241,7 +241,7 @@ void updateJSONclients(bool report)
bool bNewTimerInfo = false;
for(int tmr=0; tmr<14; tmr++)
{
if(makeJsonTimerString(tmr, jsonStr, sizeof(jsonStr))) {
if(makeJSONTimerString(tmr, jsonStr, sizeof(jsonStr))) {
if (report) {
DebugPort.print("JSON send: "); DebugPort.println(jsonStr);
}

View file

@ -27,8 +27,8 @@
extern char defaultJSONstr[64];
bool makeJsonString(CModerator& moderator, char* opStr, int len);
bool makeJsonTimerString(int channel, char* opStr, int len);
bool makeJSONString(CModerator& moderator, char* opStr, int len);
bool makeJSONTimerString(int channel, char* opStr, int len);
void updateJSONclients(bool report);
template<class T>

View file

@ -28,12 +28,14 @@ bool s8inBounds(int8_t test, int8_t minLim, int8_t maxLim);
bool u8Match2(uint8_t test, uint8_t test1, uint8_t test2);
bool u16inBounds(uint16_t test, uint16_t minLim, uint16_t maxLim);
bool s32inBounds(long test, long minLim, long maxLim);
bool thermoMethodinBounds(uint8_t test, uint8_t minLim, uint8_t maxLim);
bool
sNVStore::valid()
{
bool retval = true;
retval &= (DimTime >= 0) && (DimTime < 300000); // 5 mins
retval &= (ThermostatMethod & 0x03) < 3; // only modes 0, 1 or 2
for(int i=0; i<2; i++) {
retval &= timer[i].valid();
}
@ -48,6 +50,7 @@ sNVStore::init()
timer[i].init();
}
DimTime = 60000; // 1 minute
ThermostatMethod = 10 << 2; // 1 degree hysteresis, normal thermostat
Heater.init();
}
@ -92,6 +95,18 @@ CHeaterStorage::getThermostatMode()
return _calValues.Heater.ThermostatMode;
}
unsigned char
CHeaterStorage::getThermostatMethodMode()
{
return _calValues.ThermostatMethod & 0x03;
}
float
CHeaterStorage::getThermostatMethodHysteresis()
{
return float((_calValues.ThermostatMethod >> 2) & 0x3f) * 0.05f; // top 5 bits / 10, then / 2
}
void
CHeaterStorage::setPmin(float val)
{
@ -130,6 +145,22 @@ CHeaterStorage::setThermostatMode(unsigned char val)
_calValues.Heater.ThermostatMode = val;
}
void
CHeaterStorage::setThermostatMethodMode(unsigned char val)
{
_calValues.ThermostatMethod &= 0xF3;
_calValues.ThermostatMethod |= (val & 0x03);
}
void
CHeaterStorage::setThermostatMethodHysteresis(float val)
{
_calValues.ThermostatMethod &= 0x03;
int nVal = int(val * 10 + 0.5);
_calValues.ThermostatMethod |= ((nVal & 0x3F) << 2);
}
void
CHeaterStorage::setSystemVoltage(float fVal)
{
@ -331,6 +362,8 @@ CESP32HeaterStorage::loadUI()
preferences.begin("user", false);
validatedLoad("dimTime", _calValues.DimTime, 60000, s32inBounds, 0, 600000);
validatedLoad("degF", _calValues.degF, 0, u8inBounds, 0, 1);
validatedLoad("thermoMethod", _calValues.ThermostatMethod, (10 << 2), u8inBounds, 0, 2, 0x03);
// validatedLoad("thermoMethod", _calValues.ThermostatMethod, (10 << 2) + 0, u8inBounds, 0, 2); // TESTO!!!!
preferences.end();
}
@ -340,14 +373,15 @@ CESP32HeaterStorage::saveUI()
preferences.begin("user", false);
preferences.putULong("dimTime", _calValues.DimTime);
preferences.putUChar("degF", _calValues.degF);
preferences.putUChar("thermoMethod", _calValues.ThermostatMethod);
preferences.end();
}
bool
CESP32HeaterStorage::validatedLoad(const char* key, uint8_t& val, int defVal, std::function<bool(uint8_t, uint8_t, uint8_t)> validator, int min, int max)
CESP32HeaterStorage::validatedLoad(const char* key, uint8_t& val, int defVal, std::function<bool(uint8_t, uint8_t, uint8_t)> validator, int min, int max, uint8_t mask)
{
val = preferences.getUChar(key, defVal);
if(!validator(val, min, max)) {
if(!validator(val & mask, min, max)) {
DebugPort.print("CESP32HeaterStorage::validatedLoad<uint8_t> invalid read ");
DebugPort.print(key); DebugPort.print("="); DebugPort.print(val);

View file

@ -67,6 +67,7 @@ struct sNVStore {
sHeater Heater;
long DimTime;
uint8_t degF;
uint8_t ThermostatMethod; // 0: standard heater, 1: Narrow Hysterisis, 2:Managed Hz mode
sTimer timer[14];
bool valid();
void init();
@ -103,6 +104,8 @@ public:
unsigned short getFmax();
unsigned char getDesiredTemperature();
unsigned char getThermostatMode();
unsigned char getThermostatMethodMode();
float getThermostatMethodHysteresis();
unsigned char getSysVoltage();
unsigned char getFanSensor();
unsigned char getGlowDrive();
@ -115,6 +118,8 @@ public:
void setFmax(unsigned short val);
void setDesiredTemperature(unsigned char val);
void setThermostatMode(unsigned char val);
void setThermostatMethodMode(unsigned char val);
void setThermostatMethodHysteresis(float val);
void setSystemVoltage(float fVal);
void setFanSensor(unsigned char val);
void setGlowDrive(unsigned char val);
@ -146,7 +151,7 @@ public:
void loadUI();
void saveUI();
bool validatedLoad(const char* key, int8_t& val, int defVal, std::function<bool(int8_t, int8_t, int8_t)> validator, int min, int max);
bool validatedLoad(const char* key, uint8_t& val, int defVal, std::function<bool(uint8_t, uint8_t, uint8_t)> validator, int min, int max);
bool validatedLoad(const char* key, uint8_t& val, int defVal, std::function<bool(uint8_t, uint8_t, uint8_t)> validator, int min, int max, uint8_t mask=0xff);
bool validatedLoad(const char* key, uint16_t& val, int defVal, std::function<bool(uint16_t, uint16_t, uint16_t)> validator, int min, int max);
bool validatedLoad(const char* key, long& val, long defVal, std::function<bool(long, long, long)> validator, long min, long max);
};