924a079fb2
Persistent variables now used for temperature, pump and cyclic mode enabled settings (not NV) NV save is now staged, so it can be performed at an appropriate time, after reading DS18B20! JSONpack, instead of lame JSONloose to allow single line JSON output
256 lines
9.6 KiB
C++
256 lines
9.6 KiB
C++
/*
|
|
* 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 "TxManage.h"
|
|
#include "../Utility/NVStorage.h"
|
|
#include "../Utility/helpers.h"
|
|
|
|
//#define DEBUG_THERMOSTAT
|
|
|
|
|
|
extern void DebugReportFrame(const char* hdr, const CProtocol&, const char* ftr);
|
|
|
|
// CTxManage is used to send a data frame to the blue wire
|
|
//
|
|
// As the blue wire is bidirectional, we need to only allow our transmit data
|
|
// to reach the blue wire when we actually want to send data.
|
|
// At all other times we are listening to the blue wire, receiving any async data
|
|
//
|
|
// This requires external circuitry to toggle the Tx/Rx modes.
|
|
// A "Tx Gating" signal is used.
|
|
// when high, transmit data is sent to the blue wire
|
|
// when low, transmit data is blocked (Hi-Z)
|
|
//
|
|
// Ideally the circuit also prevents feeding back our own Tx data into the Rx pin
|
|
// but the main software loop handles this situation by only accepting Rx data when expected.
|
|
//
|
|
// Timing diagram
|
|
// ____________________
|
|
// Tx Gate ____________________| |___________________________
|
|
// _____________________________________________________________________
|
|
// Tx Data |||||||||||||||
|
|
|
|
CTxManage::CTxManage(int TxGatePin, HardwareSerial& serial) :
|
|
m_BlueWireSerial(serial),
|
|
m_TxFrame(CProtocol::CtrlMode)
|
|
{
|
|
m_bOnReq = false;
|
|
m_bOffReq = false;
|
|
m_bTxPending = false;
|
|
m_nStartTime = 0;
|
|
m_nTxGatePin = TxGatePin;
|
|
_rawCommand = 0;
|
|
}
|
|
|
|
void CTxManage::begin()
|
|
{
|
|
pinMode(m_nTxGatePin, OUTPUT);
|
|
digitalWrite(m_nTxGatePin, LOW); // default to receive mode
|
|
}
|
|
|
|
void
|
|
CTxManage::queueOnRequest(bool set)
|
|
{
|
|
m_bOnReq = set; // allow cancellation via heater response frame decode
|
|
m_bOffReq = false;
|
|
}
|
|
|
|
void
|
|
CTxManage::queueOffRequest(bool set)
|
|
{
|
|
m_bOffReq = set; // allow cancellation via heater response frame decode
|
|
m_bOnReq = false;
|
|
}
|
|
|
|
void
|
|
CTxManage::queueRawCommand(unsigned char val)
|
|
{
|
|
_rawCommand = val;
|
|
}
|
|
|
|
void
|
|
CTxManage::PrepareFrame(const CProtocol& basisFrame, bool isBTCmaster)
|
|
{
|
|
// copy supplied frame, typically this will be the values an OEM controller delivered
|
|
// which means we parrot that data by default.
|
|
// When parroting, we must especially avoid ping ponging "set temperature"!
|
|
// Otherwise we are supplied with the default params for standalone mode, which we
|
|
// then instil the NV parameters
|
|
m_TxFrame = basisFrame;
|
|
|
|
// ALWAYS install on/off commands if required
|
|
#ifndef PROTOCOL_INVESTIGATION
|
|
m_TxFrame.resetCommand(); // no command upon blue wire initially, unless a request is pending
|
|
if(_rawCommand) {
|
|
m_TxFrame.setRawCommand(_rawCommand);
|
|
_rawCommand = 0;
|
|
}
|
|
else {
|
|
if(m_bOnReq) {
|
|
// m_bOnReq = false; // requires cancel via queueOnRequest(false)
|
|
m_TxFrame.onCommand();
|
|
}
|
|
if(m_bOffReq) {
|
|
// m_bOffReq = false; // requires cancel via queueOffRequest(false)
|
|
m_TxFrame.offCommand();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// 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
|
|
if(isBTCmaster) {
|
|
m_TxFrame.setActiveMode(); // this allows heater to save the tuning params to EEPROM
|
|
m_TxFrame.setFan_Min(NVstore.getHeaterTuning().Fmin);
|
|
m_TxFrame.setFan_Max(NVstore.getHeaterTuning().Fmax);
|
|
m_TxFrame.setPump_Min(NVstore.getHeaterTuning().getPmin());
|
|
m_TxFrame.setPump_Max(NVstore.getHeaterTuning().getPmax());
|
|
|
|
float tActual = getTemperatureSensor();
|
|
uint8_t u8Temp = (uint8_t)(tActual + 0.5);
|
|
m_TxFrame.setTemperature_Actual(u8Temp); // use current temp, for now
|
|
m_TxFrame.setHeaterDemand(getDemandDegC());
|
|
m_TxFrame.setThermostatModeProtocol(1); // assume using thermostat control for now
|
|
|
|
if(!getThermostatModeActive()) {
|
|
m_TxFrame.setThermostatModeProtocol(0); // not using any form of thermostat control
|
|
m_TxFrame.setHeaterDemand(getDemandPump()); // set fixed Hz demand instead
|
|
m_TxFrame.setTemperature_Actual(0); // must force actual to 0 for Hz mode
|
|
}
|
|
else if(NVstore.getUserSettings().ThermostatMethod) {
|
|
uint8_t ThermoMode = NVstore.getUserSettings().ThermostatMethod; // get the METHOD of thermostat control
|
|
float Window = NVstore.getUserSettings().ThermostatWindow;
|
|
float tCurrent = getTemperatureSensor();
|
|
float tDesired = float(getDemandDegC());
|
|
float tDelta = tCurrent - tDesired;
|
|
float fTemp;
|
|
#ifdef DEBUG_THERMOSTAT
|
|
DebugPort.printf("Window=%.1f tCurrent=%.1f tDesired=%.1f tDelta=%.1f\r\n", Window, tCurrent, tDesired, tDelta);
|
|
#endif
|
|
Window /= 2;
|
|
switch(ThermoMode) {
|
|
|
|
case 0: // conventional heater controlled thermostat mode
|
|
case 3: // 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.printf("Conventional thermostat mode: tActual = %d\r\n", u8Temp);
|
|
#endif
|
|
break;
|
|
|
|
case 1: // heater controlled thermostat mode - BUT actual temp is tweaked via a changed window
|
|
m_TxFrame.setThermostatModeProtocol(1); // using heater thermostat control
|
|
u8Temp = (uint8_t)(tActual + 0.5); // use rounded actual unless within window
|
|
if(fabs(tDelta) < Window) {
|
|
// hold at desired if inside window
|
|
u8Temp = getDemandDegC();
|
|
}
|
|
else if(fabs(tDelta) <= 1.0) {
|
|
// force outside if delta is <= 1 but greater than window
|
|
u8Temp = getDemandDegC() + ((tDelta > 0) ? 1 : -1);
|
|
}
|
|
m_TxFrame.setTemperature_Actual(u8Temp);
|
|
#ifdef DEBUG_THERMOSTAT
|
|
DebugPort.printf("Heater controlled windowed thermostat mode: tActual=%d\r\n", u8Temp);
|
|
#endif
|
|
break;
|
|
|
|
case 2: // BTC controlled thermostat mode
|
|
// map linear deviation within thermostat window 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 /= Window; // convert tDelta to fraction of window (CAUTION - may be > +-1 !)
|
|
#ifdef DEBUG_THERMOSTAT
|
|
DebugPort.printf("Linear window thermostat mode: Fraction=%f", tDelta);
|
|
#endif
|
|
fTemp = (m_TxFrame.getTemperature_Max() + m_TxFrame.getTemperature_Min()) * 0.5; // midpoint - tDelta = 0 hinges here
|
|
tDelta *= (m_TxFrame.getTemperature_Max() - fTemp); // linear offset from setpoint
|
|
fTemp -= tDelta; // lower Hz when over temp, higher Hz when under!
|
|
// bounds limit - recall original tDelta was NOT managed prior!
|
|
LOWERLIMIT(fTemp, m_TxFrame.getTemperature_Min());
|
|
UPPERLIMIT(fTemp, m_TxFrame.getTemperature_Max());
|
|
// apply modifed desired temperature (works in conjunction with thermostatmode = 0!)
|
|
u8Temp = (uint8_t)(fTemp + 0.5);
|
|
m_TxFrame.setHeaterDemand(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.printf(" tDesired (pseudo Hz demand) = %d\r\n", u8Temp);
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
// m_TxFrame.setThermostatMode(NVstore.getThermostatMode());
|
|
|
|
m_TxFrame.Controller.OperatingVoltage = NVstore.getHeaterTuning().sysVoltage;
|
|
m_TxFrame.Controller.FanSensor = NVstore.getHeaterTuning().fanSensor;
|
|
m_TxFrame.Controller.GlowDrive = NVstore.getHeaterTuning().glowDrive;
|
|
}
|
|
else {
|
|
m_TxFrame.setPassiveMode(); // this prevents the tuning parameters being saved by heater
|
|
}
|
|
|
|
// ensure CRC valid
|
|
m_TxFrame.setCRC();
|
|
}
|
|
|
|
void
|
|
CTxManage::Start(unsigned long timenow)
|
|
{
|
|
if(timenow == 0) // avoid a black hole if millis() has wrapped to zero
|
|
timenow++;
|
|
|
|
m_nStartTime = timenow;
|
|
m_bTxPending = true;
|
|
}
|
|
|
|
// generate a Tx Gate, then send the TxFrame to the Blue wire
|
|
// Note the serial data is ISR driven, we need to hold off
|
|
// for a while to let teh buffewred dat clear before closing the Tx Gate.
|
|
bool
|
|
CTxManage::CheckTx(unsigned long timenow)
|
|
{
|
|
if(m_nStartTime) {
|
|
|
|
long diff = timenow - m_nStartTime;
|
|
|
|
if(diff > m_nStartDelay) {
|
|
// begin front porch of Tx gating pulse
|
|
digitalWrite(m_nTxGatePin, HIGH);
|
|
}
|
|
if(m_bTxPending && (diff > (m_nStartDelay + m_nFrontPorch))) {
|
|
// front porch expired, perform serial transmission
|
|
// Tx gate remains held high
|
|
m_bTxPending = false;
|
|
m_BlueWireSerial.write(m_TxFrame.Data, 24); // write native binary values
|
|
}
|
|
if(diff > (m_nStartDelay + m_nFrameTime)) {
|
|
// conclude Tx gating after (emperical) delay
|
|
digitalWrite(m_nTxGatePin, LOW);
|
|
m_nStartTime = 0; // cancel, we are DONE
|
|
}
|
|
}
|
|
return m_nStartTime == 0; // returns true when done
|
|
}
|
|
|