Added support for injecting Fixed Hz demands if a timer start has a defined temperature.

Uses normal range for fixed Hz.
This commit is contained in:
Ray Jones 2020-04-11 07:51:25 +10:00
parent 4625f98cf3
commit 9839571893
11 changed files with 1697 additions and 87 deletions

View File

@ -128,8 +128,8 @@
const int FirmwareRevision = 32;
const int FirmwareSubRevision = 0;
const int FirmwareMinorRevision = 3;
const char* FirmwareDate = "9 Apr 2020";
const int FirmwareMinorRevision = 4;
const char* FirmwareDate = "11 Apr 2020";
#ifdef ESP32
@ -155,7 +155,7 @@ void checkDebugCommands();
void manageCyclicMode();
void manageFrostMode();
void manageHumidity();
bool preemptCyclicMode();
int checkStartTemp();
void doStreaming();
void heaterOn();
void heaterOff();
@ -512,8 +512,6 @@ void setup() {
RTC_Store.begin();
FuelGauge.init(RTC_Store.getFuelGauge());
// demandDegC = RTC_Store.getDesiredTemp();
// demandPump = RTC_Store.getDesiredPump();
// bCyclicEngaged = RTC_Store.getCyclicEngaged();
DebugPort.printf("Previous cyclic active = %d\r\n", RTC_Store.getCyclicEngaged()); // state flag required for cyclic mode to persist properly after a WD reboot :-)
@ -521,7 +519,10 @@ void setup() {
pHourMeter->init(bESP32PowerUpInit || RTC_Store.getBootInit()); // ensure persistent memory variable are reset after powerup, or OTA update
RTC_Store.setBootInit(false);
reqDemand(RTC_Store.getDesiredTemp()); // bug fix: was not applying saved set point!
// bug fix: was not applying saved set points!
// reqDemand(RTC_Store.getDesiredTemp());
CTimerManager::setWorkingTemperature(RTC_Store.getDesiredTemp());
CTimerManager::setWorkingPumpHz(RTC_Store.getDesiredPump());
// Check for solo DS18B20
// store it's serial number as the primary sensor
@ -1023,24 +1024,32 @@ void manageHumidity()
}
}
bool preemptCyclicMode()
int checkStartTemp()
{
const sCyclicThermostat& cyclic = NVstore.getUserSettings().cyclic;
if(cyclic.Stop) { // cyclic mode enabled, and user has started heater
int stopDeltaT = cyclic.Stop + 1; // bump up by 1 degree - no point invoking at 1 deg over!
float deltaT = getTemperatureSensor() - getDemandDegC();
int stopDeltaT = 0;
int cyclicstop = NVstore.getUserSettings().cyclic.Stop;
if(cyclicstop) { // cyclic mode enabled
stopDeltaT = cyclicstop + 1; // bump up by 1 degree - no point invoking at 1 deg over!
}
// check if over temp, skip straight to suspend
if(deltaT > stopDeltaT) {
float deltaT = getTemperatureSensor() - getDemandDegC();
if(deltaT > stopDeltaT) {
if(cyclicstop) {
DebugPort.printf("CYCLIC MODE: Skipping directly to suspend, deltaT > +%d\r\n", stopDeltaT);
heaterOff(); // over temp - request heater stop
return true;
return -2;
}
else {
// too warm - deny start
return -1;
}
}
return false;
return 0;
}
void initBlueWireSerial()
{
// initialize serial port to interact with the "blue wire"
@ -1071,6 +1080,12 @@ bool validateFrame(const CProtocol& frame, const char* name)
}
// return values:
// 0: OK
// -1: too warm
// -2: suspended
// -3: Low Voltage Cutout
// -4: Insufficent fuel
int requestOn(bool checkTemp)
{
DebugPort.println("Start Request!");
@ -1081,18 +1096,15 @@ int requestOn(bool checkTemp)
bool LVCOK = 2 != SmartError.checkVolts(FilteredSamples.FastipVolts.getValue(), FilteredSamples.FastGlowAmps.getValue());
if(bHasHtrData && LVCOK) {
RTC_Store.setCyclicEngaged(true); // for cyclic mode
RTC_Store.setFrostOn(false); // cancel frost mode
if(!preemptCyclicMode()) { // only start if below cyclic threshold when enabled
if(!checkTemp || getTemperatureSensor() < getDemandDegC()) { // skip start if warmer than desired
heaterOn();
return 0;
}
else {
return -1; // too warm
}
RTC_Store.setFrostOn(false); // cancel frost mode
// only start if below appropriate temperature threshold, raised for cyclic mode
int denied = checkStartTemp();
if(!checkTemp || !denied) {
heaterOn();
return 0;
}
else {
return -2; // immediate cyclic suspend
return denied;
}
}
else {
@ -1139,7 +1151,7 @@ bool reqDemand(uint8_t newDemand, bool save)
CTimerManager::setWorkingTemperature(newDemand);
}
else {
RTC_Store.setDesiredPump(newDemand);
CTimerManager::setWorkingPumpHz(newDemand);
}
ScreenManager.reqUpdate();
@ -1153,7 +1165,7 @@ bool reqDemandDelta(int delta)
newDemand = CTimerManager::getWorkingTemperature() + delta;
}
else {
newDemand = RTC_Store.getDesiredPump() + delta;
newDemand = CTimerManager::getWorkingPumpHz() + delta;
}
return reqDemand(newDemand);
@ -1268,7 +1280,7 @@ void setDemandDegC(uint8_t val)
uint8_t getDemandPump()
{
return RTC_Store.getDesiredPump();
return CTimerManager::getWorkingPumpHz();
}
@ -1278,10 +1290,12 @@ float getTemperatureDesired()
return getHeaterInfo().getHeaterDemand();
}
else {
if(getThermostatModeActive())
if(getThermostatModeActive()) {
return CTimerManager::getWorkingTemperature();
else
return RTC_Store.getDesiredPump();
}
else {
return CTimerManager::getWorkingPumpHz(); // timer manager will return pump Hz, as demand value, not real Hz
}
}
}

View File

@ -34,9 +34,19 @@
#include "../../lib/RTClib/RTClib.h"
#include "../RTC/TimerManager.h"
#include "fonts/Arial.h"
#include "../Utility/NVStorage.h"
const char* briefDOW[] = { "S", "M", "T", "W", "T", "F", "S" };
float calcPumpHz(int desired) {
const sHeaterTuning& tuning = NVstore.getHeaterTuning();
float ratio = float(desired - tuning.Tmin) / float(tuning.Tmax - tuning.Tmin);
float offset = ratio * float(tuning.Pmax - tuning.Pmin);
float PumpHz = tuning.Pmin + offset;
return PumpHz / 10.f; // tuning is saved as Hz x10
}
CSetTimerScreen::CSetTimerScreen(C128x64_OLED& display, CScreenManager& mgr, int instance) : CUIEditScreen(display, mgr)
{
_initUI();
@ -83,7 +93,7 @@ CSetTimerScreen::show()
else {
// start
xPos = 18;
yPos = 16;
yPos = 15;
_printMenuText(xPos, yPos, "On", false, eRightJustify);
_printMenuText(xPos+17, yPos, ":");
xPos += 6;
@ -95,7 +105,7 @@ CSetTimerScreen::show()
// stop
xPos = 82;
yPos = 16;
yPos = 15;
_printMenuText(xPos, yPos, "Off", false, eRightJustify);
_printMenuText(xPos+17, yPos, ":");
xPos += 6;
@ -105,6 +115,13 @@ CSetTimerScreen::show()
sprintf(str, "%02d", _timerInfo.stop.min);
_printMenuText(xPos, yPos, str, _rowSel==1 && _colSel==3);
yPos = 39;
{
CTransientFont AF(_display, &arialItalic_7ptFontInfo);
sprintf(str, "( %.1fHz )", calcPumpHz(_timerInfo.temperature));
_printMenuText(_display.xCentre()+5, yPos, str, false, eLeftJustify);
}
// control
const char* msg;
_printEnabledTimers();
@ -118,23 +135,23 @@ CSetTimerScreen::show()
_printMenuText(xPos, yPos, msg, _rowSel==1 && _colSel==5, eRightJustify);
xPos = 18;
yPos = 40;
yPos = 41;
float fTemp = _timerInfo.temperature;
if(fTemp == 0) {
strcpy(str, "Current set ");
strcat(str, NVstore.getUserSettings().degF ? "`F" : "`C");
}
else {
if(NVstore.getUserSettings().degF) {
fTemp = fTemp * 9 / 5 + 32;
sprintf(str, "%.0f", fTemp);
sprintf(str, "%.0f`F", fTemp);
}
else {
sprintf(str, "%.0f", fTemp);
sprintf(str, "%.0f`C", fTemp);
}
}
bool degF = NVstore.getUserSettings().degF;
strcat(str, degF ? "`F" : "`C");
_printMenuText(_display.xCentre(), yPos, str, _rowSel==1 && _colSel==6, eCentreJustify);
_printMenuText(_display.xCentre(), yPos, str, _rowSel==1 && _colSel==6, eRightJustify);
// navigation line
@ -427,4 +444,5 @@ CSetTimerScreen::_showConflict(const char* str)
_printInverted(_display.xCentre(), 39, str, true, eCentreJustify);
_printInverted(_display.xCentre(), 28, "Conflicts", true, eCentreJustify);
}

File diff suppressed because it is too large Load Diff

View File

@ -36,6 +36,16 @@ extern const uint8_t PROGMEM arial_8ptBoldBitmaps [];
extern const FONT_INFO arial_8ptBoldFontInfo;
extern const FONT_CHAR_INFO PROGMEM arial_8ptBoldDescriptors[];
// Font data for Arial 7pt Italic
extern const uint8_t PROGMEM arialItalic_7ptBitmaps [];
extern const FONT_INFO arialItalic_7ptFontInfo;
extern const FONT_CHAR_INFO PROGMEM arialItalic_7ptDescriptors[];
// Font data for Arial 8pt Italic
extern const uint8_t PROGMEM arialItalic_8ptBitmaps [];
extern const FONT_INFO arialItalic_8ptFontInfo;
extern const FONT_CHAR_INFO PROGMEM arialItalic_8ptDescriptors[];
// Font data for Arial 12pt
extern const uint8_t PROGMEM arial_12ptBitmaps [];
extern const FONT_INFO arial_12ptFontInfo;

View File

@ -57,25 +57,6 @@ int CTxManage::m_nTxGatePin = 0;
// Sadly digitalWrite falls into this category, so use a FreeRTOS queue
// to push the end event handling into a non ISRL task
static QueueHandle_t txGate_queue = NULL;
static int lclTxGatePin = 0;
// static function used for the tx gate termination
static void IRAM_ATTR GateTerminateCallback()
{
uint32_t gpio_num = lclTxGatePin;
xQueueSendFromISR(txGate_queue, &gpio_num, NULL);
}
static void TxGateConclude(void* arg)
{
uint32_t io_num;
for(;;) {
if(xQueueReceive(txGate_queue, &io_num, portMAX_DELAY)) {
digitalWrite(io_num, LOW); // terminate Tx Gate
}
}
}
CTxManage::CTxManage(int TxGatePin, HardwareSerial& serial) :
m_BlueWireSerial(serial),
@ -87,39 +68,32 @@ CTxManage::CTxManage(int TxGatePin, HardwareSerial& serial) :
m_bTxPending = false;
m_nStartTime = 0;
m_nTxGatePin = TxGatePin;
lclTxGatePin = TxGatePin;
_rawCommand = 0;
m_HWTimer = NULL;
}
// static function used for the tx gate termination
void CTxManage::GateTerminate()
// must use IRAM_ATTR being a call back called at ISRL
void IRAM_ATTR
CTxManage::callbackGateTerminate()
{
uint32_t gpio_num = m_nTxGatePin;
xQueueSendFromISR(txGate_queue, &gpio_num, NULL);
// digitalWrite(m_nTxGatePin, LOW); // default to receive mode
digitalWrite(m_nTxGatePin, LOW); // cancel Tx Gate
m_nStartTime = 0; // cancel, we are DONE
}
void CTxManage::begin()
void
CTxManage::begin()
{
pinMode(m_nTxGatePin, OUTPUT);
digitalWrite(m_nTxGatePin, LOW); // default to receive mode
//create a queue to handle gpio event from isr
txGate_queue = xQueueCreate(10, sizeof(uint32_t));
//start gpio task
xTaskCreate(TxGateConclude, "Tx Gate Conclude", 2048, NULL, configMAX_PRIORITIES-2, NULL);
// use a hardware timer to terminate the Tx gate shortly after the completion of the 24 byte transmit packet
m_HWTimer = timerBegin(2, 80, true);
//set time in uS of Tx gate from when actual tx data bytes are loaded
// 240 bits @ 25000bps is 9.6ms, we'll use 9.7ms for a bit of tolerance
timerAlarmWrite(m_HWTimer, 10000-300, false);
// timerAttachInterrupt(m_HWTimer, &GateTerminate, true);
timerAttachInterrupt(m_HWTimer, &GateTerminateCallback, true);
timerAttachInterrupt(m_HWTimer, &callbackGateTerminate, true);
timerAlarmDisable(m_HWTimer); //disable interrupt for now
timerSetAutoReload(m_HWTimer, false);
}
@ -193,6 +167,8 @@ CTxManage::PrepareFrame(const CProtocol& basisFrame, bool isBTCmaster)
m_TxFrame.setFan_Max(NVstore.getHeaterTuning().Fmax);
m_TxFrame.setPump_Min(NVstore.getHeaterTuning().getPmin());
m_TxFrame.setPump_Max(NVstore.getHeaterTuning().getPmax());
m_TxFrame.setTemperature_Min(NVstore.getHeaterTuning().Tmin); // Minimum settable temperature
m_TxFrame.setTemperature_Max(NVstore.getHeaterTuning().Tmax); // Maximum settable temperature
float altitude;
if(getTempSensor().getAltitude(altitude)) { // if a BME280 is fitted
@ -200,7 +176,6 @@ CTxManage::PrepareFrame(const CProtocol& basisFrame, bool isBTCmaster)
}
else {
m_TxFrame.setAltitude(3500); // default height - yes it is weird, but that's what the simple controllers send!
// m_TxFrame.setAltitude(0); // default height - yes it is weird, but that's what the simple controllers send!
}
float tActual = getTemperatureSensor();
@ -335,7 +310,6 @@ CTxManage::CheckTx(unsigned long timenow)
// Tx gate remains held high
// it is then brought low by the timer alarm callback, which also cancels m_nStartTime
m_bTxPending = false;
m_nStartTime = 0;
m_BlueWireSerial.write(m_TxFrame.Data, 24); // write native binary values
timerWrite(m_HWTimer, 0); //reset tx gate timeout
timerAlarmEnable(m_HWTimer); // timeout will cause cessation of the Tx gate

View File

@ -38,7 +38,7 @@ public:
bool CheckTx(unsigned long timenow);
void begin();
const CProtocol& getFrame() const { return m_TxFrame; };
static void GateTerminate();
static void IRAM_ATTR callbackGateTerminate();
void queueSysUpdate(); // use to implant NV settings into heater
private:

View File

@ -45,6 +45,7 @@ int CTimerManager::_activeDow = 0;
int CTimerManager::_nextTimer = 0;
int CTimerManager::_nextStart = 0;
uint8_t CTimerManager::_workingTemperature = 22;
uint8_t CTimerManager::_workingPumpHz = 22;
bool CTimerManager::_timerChanged = false;
#define SET_MAPS() { \
@ -270,8 +271,10 @@ CTimerManager::manageTime(int _hour, int _minute, int _dow)
// get timer settings
int ID = (newID & 0xf) - 1;
NVstore.getTimerInfo(ID, timer);
if(timer.temperature)
if (timer.temperature) {
_workingTemperature = timer.temperature;
_workingPumpHz = timer.temperature;
}
DebugPort.printf("Start of timer interval, starting heater @ %dC\r\n", _workingTemperature);
requestOn();
_activeDow = dow; // dow when timer interval start was detected
@ -283,6 +286,7 @@ CTimerManager::manageTime(int _hour, int _minute, int _dow)
requestOff();
retval = 2;
_workingTemperature = RTC_Store.getDesiredTemp();
_workingPumpHz = RTC_Store.getDesiredPump();
DebugPort.printf("End of timer interval, stopping heater & %dC\r\n", _workingTemperature);
}
_activeTimer = newID;
@ -423,7 +427,7 @@ CTimerManager::createOneShotMap(sTimer& timer, uint16_t* pTimerMap, uint16_t* pT
}
// Concept of timer working temperature is that when a timer runs, it installs
// the programmed temeprature for that timer as the new set point.
// the programmed temperature for that timer as the new set point.
// When the timer stops, it reverts to the usual user set temperature.
//
// BUT, if a timer is running, the working temperature is updated with the new demand
@ -438,6 +442,33 @@ CTimerManager::getWorkingTemperature()
void
CTimerManager::setWorkingTemperature(uint8_t newDegC)
{
_workingTemperature = newDegC;
RTC_Store.setDesiredTemp(newDegC);
if(getThermostatModeActive()) {
_workingTemperature = newDegC;
RTC_Store.setDesiredTemp(newDegC);
}
else {
_workingPumpHz = newDegC;
RTC_Store.setDesiredPump(newDegC);
}
}
// Concept of timer working pump Hz is that when a timer runs, it installs
// the programmed temperature for that timer as the new set point.
// That is then converted by the heater into Hz
// When the timer stops, it reverts to the usual user set temperature.
//
// BUT, if a timer is running, the working temperature is updated with the new demand
// and the user temperature is also changed accordingly.
// The programmed timer temeprature is not altered and wil lrecur in the future when that time runs.
uint8_t
CTimerManager::getWorkingPumpHz()
{
return _workingPumpHz;
}
void
CTimerManager::setWorkingPumpHz(uint8_t newDemand)
{
_workingPumpHz = newDemand;
RTC_Store.setDesiredPump(newDemand);
}

View File

@ -53,9 +53,12 @@ public:
static int setTimer(sTimer& timerInfo);
static bool hasTimerChanged() { return _timerChanged; };
static uint8_t getWorkingTemperature();
static uint8_t getWorkingPumpHz();
static void setWorkingTemperature(uint8_t newDegC);
static void setWorkingPumpHz(uint8_t newDemand);
private:
static uint8_t _workingTemperature;
static uint8_t _workingPumpHz;
static int _activeTimer;
static int _activeDow;
static int _prevState;

View File

@ -43,6 +43,8 @@ struct sBM280tuning {
};
struct sHeaterTuning : public CESP32_NVStorage {
const uint8_t Tmin = 8;
const uint8_t Tmax = 35;
uint8_t Pmin;
uint8_t Pmax;
uint16_t Fmin;

View File

@ -38,6 +38,7 @@
#include "../Protocol/Protocol.h"
#include "../Utility/BTC_JSON.h"
#include "../Utility/TempSense.h"
#include "freertos/queue.h"
extern void DecodeCmd(const char* cmd, String& payload);
@ -66,6 +67,7 @@ char statusTopic[128];
#ifdef USE_RTOS_MQTTTIMER
TimerHandle_t mqttReconnectTimer = NULL;
QueueHandle_t mqttQueue = NULL;
#else
unsigned long mqttReconnect = 0;
#endif
@ -84,6 +86,16 @@ void connectToMqtt() {
}
}
#ifdef USE_RTOS_MQTTTIMER
// timer callbacks are called from ISRL
// this code MUST run from IRAM, and then safest to pass the event down thru a queue to user code
void IRAM_ATTR callbackMQTTreconnect() {
BaseType_t awoken;
int flag = 1;
xQueueSendFromISR(mqttQueue, &flag, &awoken);
}
#endif
void onMqttConnect(bool sessionPresent)
{
#ifdef USE_RTOS_MQTTTIMER
@ -192,7 +204,10 @@ bool mqttInit()
#ifdef USE_RTOS_MQTTTIMER
#ifndef BLOCK_MQTT_RECON
if(mqttReconnectTimer==NULL)
mqttReconnectTimer = xTimerCreate("mqttTimer", pdMS_TO_TICKS(20000), pdFALSE, (void*)0, reinterpret_cast<TimerCallbackFunction_t>(connectToMqtt));
// mqttReconnectTimer = xTimerCreate("mqttTimer", pdMS_TO_TICKS(20000), pdFALSE, (void*)0, reinterpret_cast<TimerCallbackFunction_t>(connectToMqtt));
mqttReconnectTimer = xTimerCreate("mqttTimer", pdMS_TO_TICKS(20000), pdFALSE, (void*)0, reinterpret_cast<TimerCallbackFunction_t>(callbackMQTTreconnect));
if(mqttQueue == NULL)
mqttQueue = xQueueCreate(10, 4);
#endif
#else
mqttReconnect = 0;
@ -242,7 +257,7 @@ bool mqttInit()
MQTTclient.onSubscribe(onMqttSubscribe);
setCallbacks = true;
}
// connection takes pplace via delayed start method
// connection takes place via delayed start method
return true;
}
return false;
@ -260,7 +275,7 @@ bool mqttPublishJSON(const char* str)
return false;
}
void kickMQTT() {
/*void kickMQTT() {
if (WiFi.isConnected()) {
if(NVstore.getMQTTinfo().enabled) {
#ifdef USE_RTOS_MQTTTIMER
@ -270,7 +285,7 @@ void kickMQTT() {
#endif
}
}
}
}*/
void doMQTT()
{
@ -280,13 +295,18 @@ void doMQTT()
if(tDelta > 0) {
MQTTrestart = 0;
mqttInit();
// connectToMqtt();
}
}
// most MQTT is managed via callbacks!!!
if(NVstore.getMQTTinfo().enabled) {
#ifndef USE_RTOS_MQTTTIMER
#ifdef USE_RTOS_MQTTTIMER
int flag;
if(xQueueReceive(mqttQueue, &flag, 0)) {
DebugPort.println("MQTT connect request via queue");
connectToMqtt();
}
#else
if(mqttReconnect) {
long tDelta = millis() - mqttReconnect;
if(tDelta > 0) {
@ -299,6 +319,7 @@ void doMQTT()
#ifdef USE_RTOS_MQTTTIMER
#ifndef BLOCK_MQTT_RECON
if (!MQTTclient.connected() && WiFi.isConnected() && !xTimerIsTimerActive(mqttReconnectTimer)) {
DebugPort.println("Starting MQTT timer");
xTimerStart(mqttReconnectTimer, 0);
}
#endif

View File

@ -29,7 +29,7 @@ bool mqttInit();
void doMQTT();
bool mqttPublishJSON(const char* str);
void connectToMqtt();
void kickMQTT();
//void kickMQTT();
bool isMQTTconnected();
const char* getTopicPrefix();