608 lines
13 KiB
C++
608 lines
13 KiB
C++
/*
|
|
* This file is part of the "bluetoothheater" distribution
|
|
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
|
|
*
|
|
* Copyright (C) 2020 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 <Arduino.h>
|
|
#include "AltControllerTask.h"
|
|
#include "AltController.h"
|
|
#include "../cfg/BTCConfig.h"
|
|
#include "../cfg/pins.h"
|
|
#include "Protocol.h"
|
|
#include "TxManage.h"
|
|
// #include "SmartError.h"
|
|
#include "../Utility/UtilClasses.h"
|
|
#include "../Utility/DataFilter.h"
|
|
#include "../Utility/FuelGauge.h"
|
|
#include "../Utility/HourMeter.h"
|
|
#include "../Utility/macros.h"
|
|
#include "../Utility/DemandManager.h"
|
|
|
|
#define MASK_FAN 0x0800
|
|
#define MASK_PUMP 0x0400
|
|
#define MASK_PLATEAU 0x0200
|
|
#define MASK_GLOW 0x0100
|
|
#define MASK_STOPPING 0x0080
|
|
#define MASK_ON 0x0040
|
|
|
|
void matchDemand(int desired);
|
|
|
|
/* known command values
|
|
0xA0 - status/ Rx code 5
|
|
0xA1 - switch to thermo mode 'h7' - get 0x5xxx messages in thermo...
|
|
0xA2 - power on/off
|
|
0xA3 - increase demand (h1-h6) (also used to leave thermo mode ('h7' -> 'h1'))
|
|
0xA4 - decrease demand (h1-h6)
|
|
0xA5 - ??
|
|
0xA6 - Primary get status
|
|
0xA7 - status (alternates status and Rx code 9... when stopped)
|
|
0xA8 - returns status
|
|
0xA9 - returns status
|
|
0xAA - enter pump edit mode ->0x3xxx returned / running minimum power (alternates status and Rx code 3...)
|
|
0xAB - get voltage
|
|
0xAC - prime
|
|
0xAD - ?
|
|
0xAE - toggle plateau
|
|
0xAF - get body temp
|
|
|
|
0x70 - maintain power (thermostat)
|
|
0x71 - increase power (thermostat)
|
|
0x72 - decrease power (thermostat)
|
|
|
|
EDIT mode:
|
|
when you send 0xAA and 0x3yxx is returned, you can step thru 'y' by using 0xAD
|
|
ie 0x30xx, 0x31xx, 0x32xx, 0x33xx, 0x34xx, 0x35xx - Those seem to be the 6 set points.
|
|
Using 0xA3 and 0xA4 you can increase or decrease 'xx' of the selected 'y' field
|
|
0xA2 then saves the new setting (but stops the heater if running!)
|
|
|
|
Have not found a fan speed anywhere...
|
|
|
|
*/
|
|
|
|
int
|
|
sAltHeaterData::errState()
|
|
{
|
|
/* convert fault codes from the ECU to classic codes used by more capable unit
|
|
A02 -> E-01 Power/voltage problem Normal range: 24V (18-32V), 12V (9-16V)
|
|
A03 -> E-04 Oil pump failure
|
|
A04 -> E-03 Ignition plug failure
|
|
A05 -> E-06 Fan failure
|
|
A06 -> E-09 Body Sensor failure
|
|
A07 -> E-10 Unsuccessful startup
|
|
A08 -> E-05 High temperature alarm (inlet air > 50 °C; chassis > 200 ° C)
|
|
A09 -> E-08 Flameout alarm
|
|
|
|
Note that classic errors must +1 of that displayed
|
|
*/
|
|
switch(Error) {
|
|
case 2: return 2; // low volts -> E=01
|
|
case 3: return 5; // pump -> E-04
|
|
case 4: return 4; // glow plug -> E-03
|
|
case 5: return 7; // fan -> E-06
|
|
case 6: return 10; // sensor -> E-09
|
|
case 7: return 11; // start fail -> E-10
|
|
case 8: return 6; // overtemp -> E-05
|
|
case 9: return 9; // flameout -> E-08
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
int
|
|
sAltHeaterData::runState()
|
|
{
|
|
if(HeaterOn == 0) {
|
|
return 0;
|
|
}
|
|
else {
|
|
if(Stopping) {
|
|
return 7;
|
|
}
|
|
else {
|
|
if(GlowOn) {
|
|
if(!PumpOn)
|
|
return 9; // heating glow plug
|
|
else
|
|
return 2; // igniting
|
|
}
|
|
else {
|
|
return 5;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
float
|
|
sAltHeaterData::getPumpRate()
|
|
{
|
|
if(PumpOn) {
|
|
if(INBOUNDS(Demand, 0, 5) && pumpRate[Demand] > 0)
|
|
return pumpRate[Demand] * 0.1;
|
|
else
|
|
return -1;
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
float
|
|
sAltHeaterData::getDesiredPumpRate(int Idx)
|
|
{
|
|
if(INBOUNDS(Idx, 0, 5) && pumpRate[Idx] > 0)
|
|
return pumpRate[Idx] * 0.1;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
sAltHeaterData::decodeThermoDemand(int rxData)
|
|
{
|
|
Demand = rxData & 0x07;
|
|
thermoMode = true;
|
|
}
|
|
|
|
void
|
|
sAltHeaterData::decodeError(int rxData)
|
|
{
|
|
Error = rxData & 0x0f;
|
|
}
|
|
|
|
void
|
|
sAltHeaterData::decodePumpRate(int rxData)
|
|
{
|
|
int index = (rxData & 0xF00) >> 8;
|
|
int rate = rxData & 0xFF;
|
|
|
|
if(INBOUNDS(index, 0, 5)) {
|
|
pumpRate[index] = rate;
|
|
}
|
|
}
|
|
|
|
void
|
|
sAltHeaterData::decodeVolts(int rxData)
|
|
{
|
|
Volts = rxData & 0x1f;
|
|
FilteredSamples.ipVolts.update(Volts);
|
|
FilteredSamples.FastipVolts.update(Volts);
|
|
}
|
|
|
|
void
|
|
sAltHeaterData::decodeBodyT(int rxData)
|
|
{
|
|
BodyT = (rxData & 0xFFF);
|
|
}
|
|
|
|
|
|
void
|
|
sAltHeaterData::decodeStatus(int rxData)
|
|
{
|
|
if(rxData & MASK_ON) {
|
|
HeaterOn = true;
|
|
FanOn = rxData & MASK_FAN ? true : false;
|
|
GlowOn = rxData & MASK_GLOW ? true : false;
|
|
PumpOn = rxData & MASK_PUMP ? true : false;
|
|
Plateau = rxData & MASK_PLATEAU ? true : false;
|
|
Stopping = rxData & MASK_STOPPING ? true : false;
|
|
int dmd = rxData & 0x07;
|
|
if(dmd != 6) {
|
|
Demand = dmd;
|
|
thermoMode = false;
|
|
}
|
|
else {
|
|
thermoMode = true;
|
|
}
|
|
}
|
|
else {
|
|
HeaterOn = false;
|
|
FanOn = false;
|
|
GlowOn = false;
|
|
PumpOn = false;
|
|
Plateau = false;
|
|
Stopping = false;
|
|
Error = 0;
|
|
Volts = rxData & 0x1f;
|
|
thermoMode = false;
|
|
|
|
FilteredSamples.ipVolts.update(Volts);
|
|
FilteredSamples.FastipVolts.update(Volts);
|
|
}
|
|
}
|
|
|
|
sAltHeaterData::sAltHeaterData()
|
|
{
|
|
init();
|
|
}
|
|
|
|
void
|
|
sAltHeaterData::init()
|
|
{
|
|
thermoMode = false;
|
|
Active = 0;
|
|
HeaterOn = false;
|
|
Stopping = false;
|
|
GlowOn = false;
|
|
FanOn = false;
|
|
PumpOn = false;
|
|
Plateau = false;
|
|
Demand = 0;
|
|
BodyT = -1;
|
|
Volts = 0;
|
|
Error = 0;
|
|
for(int i=0; i<6; i++) pumpRate[i] = -1;
|
|
}
|
|
|
|
float
|
|
sAltHeaterData::getBodyTemp()
|
|
{
|
|
return BodyT; // TODO: map to real world somehow
|
|
}
|
|
|
|
void
|
|
sAltHeaterData::report()
|
|
{
|
|
char msg[80];
|
|
sprintf(msg, "On:%d Fan:%d Glow:%d Pump:%d Plateau:%d Demand:%d Battery:%d BodyT:%d\r\n",
|
|
HeaterOn,
|
|
FanOn,
|
|
GlowOn,
|
|
PumpOn,
|
|
Plateau,
|
|
Demand,
|
|
Volts,
|
|
BodyT);
|
|
Serial.print(msg);
|
|
}
|
|
|
|
void
|
|
CAltCommsTask::_decode(int rxData)
|
|
{
|
|
unsigned int ID = rxData >> 12;
|
|
switch(ID) {
|
|
case 0x0: break;
|
|
case 0x1: break;
|
|
case 0x2: break;
|
|
case 0x3:
|
|
_htrData.decodePumpRate(rxData);
|
|
break; // 0x3yxx y = H1(0) - h6(5) xx = Hz x10
|
|
case 0x4:
|
|
_htrData.decodeBodyT(rxData);
|
|
break;
|
|
case 0x5:
|
|
_htrData.decodeThermoDemand(rxData);
|
|
break; // heat bars on OEM LCD in thermo mode 0=1 bar
|
|
case 0x6:
|
|
_htrData.decodeStatus(rxData);
|
|
break;
|
|
case 0x7: break;
|
|
case 0x8:
|
|
_htrData.decodeError(rxData);
|
|
break;
|
|
case 0x9: break; // 0x9009 in response to A7 when stopped...
|
|
case 0xa:
|
|
_htrData.decodeVolts(rxData);
|
|
break;
|
|
case 0xb: break;
|
|
case 0xc: break;
|
|
case 0xd: break;
|
|
case 0xe: break;
|
|
case 0xf: break;
|
|
}
|
|
_htrData.Active = millis() + 5000;
|
|
}
|
|
|
|
void
|
|
CAltCommsTask::reportDecode()
|
|
{
|
|
_htrData.report();
|
|
}
|
|
|
|
float
|
|
CAltCommsTask::getFanRPM()
|
|
{
|
|
return _htrData.FanOn ? -1 : 0;
|
|
}
|
|
|
|
float
|
|
CAltCommsTask::getActualPumpRate()
|
|
{
|
|
return _htrData.getPumpRate();
|
|
}
|
|
|
|
float
|
|
CAltCommsTask::getDesiredPumpRate(int idx)
|
|
{
|
|
return _htrData.getDesiredPumpRate(idx);
|
|
}
|
|
|
|
float
|
|
CAltCommsTask::getGlow()
|
|
{
|
|
return _htrData.GlowOn ? -1 : 0;
|
|
}
|
|
|
|
float
|
|
CAltCommsTask::getBodyTemp()
|
|
{
|
|
return _htrData.getBodyTemp();
|
|
}
|
|
|
|
int
|
|
CAltCommsTask::getRunState()
|
|
{
|
|
return _htrData.runState();
|
|
}
|
|
|
|
int
|
|
CAltCommsTask::getErrState()
|
|
{
|
|
return _htrData.errState();
|
|
}
|
|
|
|
void
|
|
CAltCommsTask::reqPower()
|
|
{
|
|
putTxQueue(0xA2);
|
|
}
|
|
|
|
void
|
|
CAltCommsTask::_reqStatus()
|
|
{
|
|
putTxQueue(0xA6);
|
|
}
|
|
|
|
void
|
|
CAltCommsTask::_reqVolts()
|
|
{
|
|
putTxQueue(0xAB);
|
|
}
|
|
|
|
|
|
void
|
|
CAltCommsTask::_reqBodyT()
|
|
{
|
|
putTxQueue(0xAF);
|
|
}
|
|
|
|
void
|
|
CAltCommsTask::_reqDemand(int dir)
|
|
{
|
|
if(_htrData.thermoMode)
|
|
putTxQueue(0xA1); // switch from thermo mode
|
|
|
|
if(dir == 0)
|
|
putTxQueue(0xA6);
|
|
else if(dir > 0)
|
|
putTxQueue(0xA3);
|
|
else
|
|
putTxQueue(0xA4);
|
|
}
|
|
|
|
void
|
|
CAltCommsTask::_reqThermo(int delta)
|
|
{
|
|
if(!_htrData.thermoMode)
|
|
putTxQueue(0xA1); // switch to thermo mode
|
|
|
|
if(delta == 0)
|
|
putTxQueue(0x70);
|
|
else if(delta > 0)
|
|
putTxQueue(0x71);
|
|
else
|
|
putTxQueue(0x72);
|
|
}
|
|
|
|
void checkAltTxEvents()
|
|
{
|
|
}
|
|
|
|
void
|
|
CAltCommsTask::manage()
|
|
{
|
|
static bool flipflop = false;
|
|
if(_connState == 0) {
|
|
_doStartupProbe();
|
|
}
|
|
|
|
else if(_connState == 1) {
|
|
long tDelta = xTaskGetTickCount() - _tPause;
|
|
if(tDelta >= 0) {
|
|
_tPause += 1000;
|
|
|
|
if(CDemandManager::isThermostat()) {
|
|
_doThermo();
|
|
}
|
|
else {
|
|
// _reqStatus();
|
|
// _reqDemand(0);
|
|
_matchDemand(CDemandManager::getPumpHz());
|
|
}
|
|
|
|
if(_htrData.HeaterOn) {
|
|
if((flipflop = !flipflop))
|
|
_reqVolts();
|
|
else
|
|
_reqBodyT();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CAltCommsTask::_doStartupProbe()
|
|
{
|
|
// interrogate the ehater and extract the fixed pump rates for each heat demand setting H1->H6
|
|
if(_startup.state == 0) {
|
|
// erase pump rate record
|
|
_htrData.init();
|
|
_startup.pumpIdx = 0;
|
|
putTxQueue(0xAA); // request initial pump rate
|
|
_tPause = xTaskGetTickCount() + 600;
|
|
_startup.state++;
|
|
}
|
|
|
|
else if(_startup.state == 1) {
|
|
if(_htrData.pumpRate[_startup.pumpIdx] > 0) {
|
|
_startup.state++;
|
|
}
|
|
else {
|
|
long tDelta = xTaskGetTickCount() - _tPause;
|
|
if(tDelta >= 0) {
|
|
_startup.state = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
else if(_startup.state == 2) {
|
|
if(_htrData.pumpRate[5] < 0) {
|
|
_startup.pumpIdx++;
|
|
if(_startup.pumpIdx == 6) {
|
|
_startup.state = 0;
|
|
}
|
|
else {
|
|
putTxQueue(0xAD); // req next pump rate
|
|
_tPause = xTaskGetTickCount() + 600;
|
|
_startup.state++;
|
|
}
|
|
}
|
|
else {
|
|
putTxQueue(0xAA); // finished - leave
|
|
_tPause = xTaskGetTickCount() + 600;
|
|
_connState = 1;
|
|
}
|
|
}
|
|
|
|
else if(_startup.state == 3) {
|
|
if(_htrData.pumpRate[_startup.pumpIdx] > 0) {
|
|
_startup.state = 2;
|
|
}
|
|
else {
|
|
long tDelta = xTaskGetTickCount() - _tPause;
|
|
if(tDelta >= 0) {
|
|
_startup.state = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CAltCommsTask::checkEvents()
|
|
{
|
|
int rxHeater;
|
|
if(AltCommsTask.getRxQueue(rxHeater)) {
|
|
_decode(rxHeater);
|
|
}
|
|
isActive();
|
|
|
|
manage();
|
|
}
|
|
|
|
|
|
bool
|
|
CAltCommsTask::isActive() {
|
|
if(_htrData.Active) {
|
|
long tDelta = millis() - _htrData.Active;
|
|
if(tDelta > 0) {
|
|
_htrData.Active = 0;
|
|
}
|
|
}
|
|
return _htrData.Active != 0;
|
|
}
|
|
|
|
|
|
void
|
|
CAltCommsTask::_doThermo()
|
|
{
|
|
static int demandMemory = 5;
|
|
uint8_t ThermostatMode = NVstore.getUserSettings().ThermostatMethod; // get the METHOD of thermostat control
|
|
float Window = NVstore.getUserSettings().ThermostatWindow;
|
|
float tCurrent = getTemperatureSensor();
|
|
float tDesired = float(CDemandManager::getDegC());
|
|
float tDelta = tCurrent - tDesired;
|
|
Window /= 2;
|
|
switch(ThermostatMode) {
|
|
|
|
case 3: // GPIO controlled thermostat mode
|
|
if(CDemandManager::isExtThermostatMode()) {
|
|
if(CDemandManager::isExtThermostatOn()) {
|
|
_matchDemand(5);
|
|
}
|
|
else {
|
|
_matchDemand(0);
|
|
}
|
|
break;
|
|
}
|
|
// deliberately fall through if not enabled for GPIO control to standard thermostat
|
|
// |
|
|
// V
|
|
case 0: // conventional heater controlled thermostat mode
|
|
Window = 1.0;
|
|
// deliberately fall through with +-1C window
|
|
// |
|
|
// V
|
|
case 1: // heater controlled thermostat mode - BUT actual temp is tweaked via a changed window
|
|
// if(fabs(tDelta) < Window) {
|
|
// _reqThermo(0); // hold at desired if inside window
|
|
// }
|
|
// else if(tDelta < -Window) {
|
|
// _reqThermo(+1);
|
|
// }
|
|
// else {
|
|
// _reqThermo(-1);
|
|
// }
|
|
if(tDelta >= Window) {
|
|
demandMemory = 0; // flip flop to minimum power when over +ve threshold
|
|
}
|
|
else if(tDelta <= -Window){
|
|
demandMemory = 5; // flip flop to maximum power when under -ve threshold
|
|
}
|
|
_matchDemand(demandMemory);
|
|
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
|
|
if(fabs(tDelta) < Window/2) {
|
|
_matchDemand(3);
|
|
}
|
|
else if(fabs(tDelta) < Window) {
|
|
_matchDemand((tDelta > 0) ? 2 : 4);
|
|
}
|
|
else {
|
|
_matchDemand((tDelta > 0) ? 0 : 5);
|
|
}
|
|
break;
|
|
|
|
case 4:
|
|
_matchDemand(5);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
CAltCommsTask::_matchDemand(int desired)
|
|
{
|
|
if(_htrData.Demand == desired)
|
|
_reqDemand(0);
|
|
else if(_htrData.Demand > desired)
|
|
_reqDemand(-1);
|
|
else
|
|
_reqDemand(+1);
|
|
} |