ESP32_ChinaDieselHeater_Con.../src/Protocol/TxManage.cpp
Ray Jones 7e1a4940ac Added BME280 humidity and altitude information
Can start heater if high humidity
Altitude reported to heater per BMP180 in OEM controllers.
lowered min Hz limit to 500
2020-01-13 19:48:32 +11:00

295 lines
12 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 |||||||||||||||
unsigned long CTxManage::m_nStartTime = 0;
int CTxManage::m_nTxGatePin = 0;
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;
m_HWTimer = NULL;
}
// static function used for the tx gate termination
void CTxManage::GateTerminate()
{
digitalWrite(m_nTxGatePin, LOW); // default to receive mode
m_nStartTime = 0; // cancel, we are DONE
}
void CTxManage::begin()
{
pinMode(m_nTxGatePin, OUTPUT);
digitalWrite(m_nTxGatePin, LOW); // default to receive mode
// 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);
timerAlarmDisable(m_HWTimer); //disable interrupt for now
timerSetAutoReload(m_HWTimer, false);
}
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(uint8_t 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 altitude;
if(getTempSensor().getAltitude(altitude)) { // if a BME280 is fitted
m_TxFrame.setAltitude(altitude);
}
else {
m_TxFrame.setAltitude(3500); // default height - yes it is weird, but that's what the simple controllers send!
}
float tActual = getTemperatureSensor();
int8_t s8Temp = (int8_t)(tActual + 0.5);
m_TxFrame.setTemperature_Actual(s8Temp); // 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 3: // GPIO controlled thermostat mode
if(getExternalThermostatModeActive()) {
if(getExternalThermostatOn()) {
s8Temp = m_TxFrame.getTemperature_Max(); // input active (contact closure) - max burn
}
else {
s8Temp = m_TxFrame.getTemperature_Min(); // input not active (contact open) - min burn
}
m_TxFrame.setHeaterDemand(s8Temp);
m_TxFrame.setThermostatModeProtocol(0); // direct heater to use Hz Mode
m_TxFrame.setTemperature_Actual(0); // must force actual to 0 for Hz mode
break;
}
// deliberately fall through if not enabled for GPIO control to standard thermostat
// |
// V
case 0: // conventional heater controlled thermostat mode
m_TxFrame.setThermostatModeProtocol(1); // using heater thermostat control
s8Temp = (int8_t)(tActual + 0.5);
m_TxFrame.setTemperature_Actual(s8Temp);
#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
s8Temp = (int8_t)(tActual + 0.5); // use rounded actual unless within window
if(fabs(tDelta) < Window) {
// hold at desired if inside window
s8Temp = getDemandDegC();
}
else if(fabs(tDelta) <= 1.0) {
// force outside if delta is <= 1 but greater than window
s8Temp = getDemandDegC() + ((tDelta > 0) ? 1 : -1);
}
m_TxFrame.setTemperature_Actual(s8Temp);
#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!)
s8Temp = (int8_t)(fTemp + 0.5);
m_TxFrame.setHeaterDemand(s8Temp);
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.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)
{
m_nStartTime = timenow + m_nStartDelay; // create a dwell period if an OEM controller is present after it's data exchange
m_nStartTime |= 1; // avoid a black hole if millis() has wrapped to zero
m_bTxPending = true;
}
// generate a Tx Gate, then send the TxFrame to the Blue wire
// Note the serial data is ISR driven, we supply all 24 bytes to the Tx buffer which is then drained automatically
// the Tx Gate is closed shortly after the last byte is completed.
bool
CTxManage::CheckTx(unsigned long timenow)
{
if(m_nStartTime && m_bTxPending) {
long diff = timenow - m_nStartTime;
// dwell since OEM exchange has expired ?
if(diff >= 0) {
// begin front porch of Tx gating pulse
digitalWrite(m_nTxGatePin, HIGH);
}
if(diff >= m_nFrontPorch) {
// front porch expired, start actual serial transmission
// Tx gate remains held high
// it is then brought low by the timer alarm callback, which also cancels m_nStartTime
m_bTxPending = false;
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
}
}
return m_nStartTime == 0; // returns true when done
}