Added NV Storage for ESP32

Changed "USB" to "DebugPort"
Tidy up of blue wire data frames and passing about by reference instead of just the data buffer.
Added AppInventor application
This commit is contained in:
rljonesau 2018-10-20 18:11:23 +11:00
parent 5d112d7e64
commit 1325ae6038
14 changed files with 532 additions and 243 deletions

Binary file not shown.

View file

@ -9,6 +9,8 @@
"xhash": "cpp",
"xstring": "cpp",
"xtree": "cpp",
"algorithm": "cpp"
"algorithm": "cpp",
"initializer_list": "cpp",
"xutility": "cpp"
}
}

View file

@ -1,11 +1,12 @@
#include <Arduino.h>
class CProtocol;
void Bluetooth_Init();
void Bluetooth_Report(const char* pHdr, const unsigned char Data[24]);
void Bluetooth_SendFrame(const char* pHdr, const CProtocol& Frame);
void Bluetooth_Check();
void Bluetooth_Interpret(String line);
extern void Command_Interpret(String line); // decodes received command lines, implemented in main .ino file!

View file

@ -1,14 +1,14 @@
#include "Bluetooth.h"
#include "TxManage.h"
#include "pins.h"
#include "Protocol.h"
#include "debugport.h"
#if defined(ESP32)
#ifdef ESP32
const int LED = 2;
// ESP32
void Bluetooth_Interpret();
String BluetoothRxLine;
#ifndef ESP32_USE_BLE_RLJ
@ -25,8 +25,10 @@ BluetoothSerial SerialBT;
void Bluetooth_Init()
{
pinMode(LED, OUTPUT);
if(!SerialBT.begin("ESPHEATER")) {
Serial.println("An error occurred initialising Bluetooth");
DebugPort.println("An error occurred initialising Bluetooth");
}
}
@ -36,7 +38,7 @@ void Bluetooth_Check()
char rxVal = SerialBT.read();
if(isControl(rxVal)) { // "End of Line"
BluetoothRxLine += '\0';
Bluetooth_Interpret(BluetoothRxLine);
Command_Interpret(BluetoothRxLine);
BluetoothRxLine = "";
}
else {
@ -45,11 +47,22 @@ void Bluetooth_Check()
}
}
void Bluetooth_Report(const char* pHdr, const unsigned char Data[24])
void Bluetooth_SendFrame(const char* pHdr, const CProtocol& Frame)
{
if(SerialBT.hasClient()) {
SerialBT.print(pHdr);
SerialBT.write(Data, 24);
if(Frame.verifyCRC()) {
digitalWrite(LED, !digitalRead(LED)); // toggle LED
SerialBT.print(pHdr);
SerialBT.write(Frame.Data, 24);
}
else {
DebugPort.print("Bluetooth data not sent, CRC error ");
DebugPort.println(pHdr);
}
}
else {
digitalWrite(LED, 0);
}
}
@ -106,7 +119,7 @@ class MyCallbacks : public BLECharacteristicCallbacks {
while(rxValue.length() > 0) {
char rxVal = rxValue[0];
if(isControl(rxVal)) { // "End of Line"
Bluetooth_Interpret(BluetoothRxLine);
Command_Interpret(BluetoothRxLine);
BluetoothRxLine = "";
}
else {
@ -148,18 +161,20 @@ void Bluetooth_Init()
pService->start();
// start advertising
pServer->getAdvertising()->start();
Serial.println("Awaiting a client to notify...");
DebugPort.println("Awaiting a client to notify...");
}
void Bluetooth_Report(const char* pHdr, const unsigned char Data[24])
void Bluetooth_Report(const char* pHdr, const CProtocol& Frame)
{
if(deviceConnected) {
// BLE can only squirt 20 bytes per packet.
// build the entire message then divide and conquer
std::string txData = pHdr;
txData.append((char*)Data, 24);
if(Frame.verifyCRC()) {
// BLE can only squirt 20 bytes per packet.
// build the entire message then divide and conquer
std::string txData = pHdr;
txData.append((char*)Frame.Data, 24);
BLE_Send(txData);
BLE_Send(txData);
}
}
}
@ -169,7 +184,7 @@ void Bluetooth_Check()
if (!deviceConnected && oldDeviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("start advertising");
DebugPort.println("start advertising");
oldDeviceConnected = deviceConnected;
}
// connecting

View file

@ -1,21 +1,22 @@
#include "Bluetooth.h"
#include "TxManage.h"
#include "pins.h"
#include "Protocol.h"
#include "debugport.h"
#if defined (ESP32)
// Bluetooth access via HC-05 Module, using a UART
#ifndef ESP32
// NOTE: ESP32 uses an entirely different mechanism, please refer to BluetoothESP32.cpp/.h
#else
#if defined(__arm__)
#ifdef __arm__
// for Arduino Due
static UARTClass& Bluetooth(Serial2);
#else
// for Mega
static HardwareSerial& Bluetooth(Serial2); // TODO: make proper ESP32 BT client
static HardwareSerial& Bluetooth(Serial2); // TODO: make proper ESP32 BT client
#endif
bool Bluetooth_Command(const char* cmd);
void Bluetooth_Interpret();
bool Bluetooth_ATCommand(const char* cmd);
String BluetoothRxData;
@ -27,8 +28,7 @@ bool bHC05Available = false;
void Bluetooth_Init()
{
#ifndef __ESP32__
// search for BlueTooth adapter, trying the common baud rates, then less common
// as the device cannot be guaranteed to power up with the key pin high
// we are at the mercy of the baud rate stored in the module.
@ -36,53 +36,53 @@ void Bluetooth_Init()
digitalWrite(KeyPin, HIGH);
delay(500);
USB.println("\r\n\r\nAttempting to detect HC-05 Bluetooth module...");
DebugPort.println("\r\n\r\nAttempting to detect HC-05 Bluetooth module...");
int BTidx = 0;
int maxTries = sizeof(BTRates)/sizeof(int);
for(BTidx = 0; BTidx < maxTries; BTidx++) {
USB.print(" @ ");
USB.print(BTRates[BTidx]);
USB.print(" baud... ");
DebugPort.print(" @ ");
DebugPort.print(BTRates[BTidx]);
DebugPort.print(" baud... ");
Bluetooth.begin(BTRates[BTidx]); // open serial port at a certain baud rate
Bluetooth.print("\r\n");
Bluetooth.setTimeout(50);
if(Bluetooth_Command("AT\r\n")) {
USB.println(" OK.");
if(Bluetooth_ATCommand("AT\r\n")) {
DebugPort.println(" OK.");
break;
}
// failed, try another baud rate
USB.println("");
DebugPort.println("");
Bluetooth.flush();
}
USB.println("");
DebugPort.println("");
if(BTidx == maxTries) {
USB.println("FAILED to detect HC-05 Bluetooth module :-(");
DebugPort.println("FAILED to detect HC-05 Bluetooth module :-(");
}
else {
if(BTRates[BTidx] == 115200) {
USB.println("HC-05 found and already set to 115200 baud, skipping Init.");
DebugPort.println("HC-05 found and already set to 115200 baud, skipping Init.");
bHC05Available = true;
}
else {
do {
USB.println("HC-05 found");
DebugPort.println("HC-05 found");
USB.print(" Setting Name to \"DieselHeater\"... ");
if(!Bluetooth_Command("AT+NAME=\"DieselHeater\"\r\n")) {
USB.println("FAILED");
DebugPort.print(" Setting Name to \"DieselHeater\"... ");
if(!Bluetooth_ATCommand("AT+NAME=\"DieselHeater\"\r\n")) {
DebugPort.println("FAILED");
break;
}
USB.println("OK");
DebugPort.println("OK");
USB.print(" Setting baud rate to 115200N81...");
if(!Bluetooth_Command("AT+UART=115200,1,0\r\n")) {
USB.println("FAILED");
DebugPort.print(" Setting baud rate to 115200N81...");
if(!Bluetooth_ATCommand("AT+UART=115200,1,0\r\n")) {
DebugPort.println("FAILED");
break;
};
USB.println("OK");
DebugPort.println("OK");
Bluetooth.begin(115200);
bHC05Available = true;
@ -98,8 +98,7 @@ void Bluetooth_Init()
if(!bHC05Available)
Bluetooth.end(); // close serial port if no module found
USB.println("");
#endif
DebugPort.println("");
}
void Bluetooth_Check()
@ -110,7 +109,7 @@ void Bluetooth_Check()
char rxVal = Bluetooth.read();
if(isControl(rxVal)) { // "End of Line"
BluetoothRxData += '\0';
Bluetooth_Interpret(BluetoothRxData);
Command_Interpret(BluetoothRxData);
BluetoothRxData = "";
}
else {
@ -121,16 +120,22 @@ void Bluetooth_Check()
}
void Bluetooth_Report(const char* pHdr, const unsigned char Data[24])
void Bluetooth_SendFrame(const char* pHdr, const CProtocol& Frame)
{
if(bHC05Available) {
Bluetooth.print(pHdr);
Bluetooth.write(Data, 24);
if(Frame.verifyCRC()) {
Bluetooth.print(pHdr);
Bluetooth.write(Frame.Data, 24);
}
else {
DebugPort.print("Bluetooth data not sent, CRC error ");
DebugPort.println(pHdr);
}
}
}
// local function, typically to perform Hayes commands with HC-05
bool Bluetooth_Command(const char* cmd)
bool Bluetooth_ATCommand(const char* cmd)
{
if(bHC05Available) {
Bluetooth.print(cmd);
@ -148,36 +153,3 @@ bool Bluetooth_Command(const char* cmd)
#endif
void Bluetooth_Interpret(String line)
{
if(line.startsWith("[CMD]") ) {
USB.write("BT command Rx'd: ");
// incoming command from BT app!
line.remove(0, 5); // strip away "[CMD]" header
if(line.startsWith("ON") ) {
USB.write("ON\n");
TxManage.RequestOn();
}
else if(line.startsWith("OFF")) {
USB.write("OFF\n");
TxManage.RequestOff();
}
else if(line.startsWith("Pmin")) {
line.remove(0, 4);
USB.write("Pmin\n");
}
else if(line.startsWith("Pmax")) {
line.remove(0, 4);
USB.write("Pmax\n");
}
else if(line.startsWith("Fmin")) {
line.remove(0, 4);
USB.write("Fmin\n");
}
else if(line.startsWith("Fmax")) {
line.remove(0, 4);
USB.write("Fmax\n");
}
}
}

View file

@ -0,0 +1,118 @@
#include "Arduino.h"
#include "NVStorage.h"
CHeaterStorage::CHeaterStorage()
{
calValues.Pmin = 14;
calValues.Pmax = 40;
calValues.Fmin = 1500;
calValues.Fmax = 4500;
calValues.ThermostatMode = 1;
calValues.setTemperature = 22;
}
unsigned char
CHeaterStorage::getPmin()
{
return calValues.Pmin;
}
unsigned char
CHeaterStorage::getPmax()
{
return calValues.Pmax;
}
unsigned short
CHeaterStorage::getFmin()
{
return calValues.Fmin;
}
unsigned short
CHeaterStorage::getFmax()
{
return calValues.Fmax;
}
unsigned char
CHeaterStorage::getTemperature()
{
return calValues.setTemperature;
}
unsigned char
CHeaterStorage::getThermostatMode()
{
return calValues.ThermostatMode;
}
void
CHeaterStorage::setPmin(unsigned char val)
{
calValues.Pmin = val;
}
void
CHeaterStorage::setPmax(unsigned char val)
{
calValues.Pmax = val;
}
void
CHeaterStorage::setFmin(unsigned short val)
{
calValues.Fmin = val;
}
void
CHeaterStorage::setFmax(unsigned short val)
{
calValues.Fmax = val;
}
void
CHeaterStorage::setTemperature(unsigned char val)
{
calValues.setTemperature = val;
}
void
CHeaterStorage::setThermostatMode(unsigned char val)
{
calValues.ThermostatMode = val;
}
///////////////////////////////////////////////////////////////////////////////////////
// ESP32
//
#ifdef ESP32
CESP32HeaterStorage::CESP32HeaterStorage()
{
}
CESP32HeaterStorage::~CESP32HeaterStorage()
{
preferences.end();
}
void
CESP32HeaterStorage::init()
{
preferences.begin("dieselheater", false);
}
void CESP32HeaterStorage::load()
{
preferences.getBytes("calValues", &calValues, sizeof(sNVStore));
}
void CESP32HeaterStorage::save()
{
preferences.putBytes("calValues", &calValues, sizeof(sNVStore));
}
#endif // ESP32

View file

@ -0,0 +1,69 @@
struct sNVStore {
unsigned char Pmin;
unsigned char Pmax;
unsigned short Fmin;
unsigned short Fmax;
unsigned char ThermostatMode;
unsigned char setTemperature;
};
class CNVStorage {
public:
CNVStorage() {};
virtual ~CNVStorage() {};
virtual void init() = 0;
virtual void load() = 0;
virtual void save() = 0;
};
class CHeaterStorage : public CNVStorage {
protected:
sNVStore calValues;
public:
CHeaterStorage();
virtual ~CHeaterStorage() {};
// TODO: These are only here to allow building without fully
// fleshing out NV storage for Due, Mega etc
// these should be removed once complete (pure virtual)
void init() {};
void load() {};
void save() {};
unsigned char getPmin();
unsigned char getPmax();
unsigned short getFmin();
unsigned short getFmax();
unsigned char getTemperature();
unsigned char getThermostatMode();
void setPmin(unsigned char val);
void setPmax(unsigned char val);
void setFmin(unsigned short val);
void setFmax(unsigned short val);
void setTemperature(unsigned char val);
void setThermostatMode(unsigned char val);
};
#ifdef ESP32
#include <Preferences.h>
class CESP32HeaterStorage : public CHeaterStorage {
Preferences preferences;
public:
CESP32HeaterStorage();
virtual ~CESP32HeaterStorage();
void init();
void load();
void save();
};
#endif
extern CHeaterStorage* pNVStorage;

View file

@ -1,14 +1,15 @@
#include <Arduino.h>
#include "Protocol.h"
#include "debugport.h"
unsigned short
CProtocol::CalcCRC(int len)
CProtocol::CalcCRC(int len) const
{
// calculate a CRC-16/MODBUS checksum using the first 22 bytes of the data array
unsigned short wCRCWord = 0xFFFF;
int wLength = len;
unsigned char* pData = Data;
const unsigned char* pData = Data;
while (wLength--)
{
unsigned char nTemp = *pData++ ^ wCRCWord;
@ -34,20 +35,29 @@ CProtocol::setCRC(unsigned short CRC)
unsigned short
CProtocol::getCRC()
CProtocol::getCRC() const
{
unsigned short CRC;
CRC = Data[22]; // MSB of CRC in Data[22]
CRC <<= 8;
CRC |= Data[23]; // LSB of CRC in Data[23]
return CRC;
}
// return true for CRC match
bool
CProtocol::verifyCRC()
CProtocol::verifyCRC() const
{
unsigned short CRC = CalcCRC(22); // calculate CRC based on first 22 bytes
return (getCRC() == CRC); // does it match the stored values?
unsigned short FrameCRC = getCRC();
bool bOK = (FrameCRC == CRC);
if(!bOK) {
DebugPort.print("verifyCRC FAILED: calc:");
DebugPort.print(CRC, HEX);
DebugPort.print(" data:");
DebugPort.println(FrameCRC, HEX);
}
return bOK; // does it match the stored values?
}
CProtocol&
@ -57,21 +67,6 @@ CProtocol::operator=(const CProtocol& rhs)
return *this;
}
int
CProtocol::getCommand()
{
return Controller.Command;
}
void
CProtocol::setCommand(int cmd)
{
Controller.Command = cmd;
}
/*unsigned char getCommand();
void setCommand(int mode);*/
void
CProtocol::setFan_Min(short Speed)

View file

@ -98,16 +98,24 @@ public:
public:
CProtocol() { Init(0); };
CProtocol(int TxMode) { Init(TxMode); };
void Init(int Txmode);
// CRC handlers
unsigned short CalcCRC(int len); // calculate and set the CRC upon len bytes
void setCRC(); // calculate and set the CRC in the buffer
unsigned short CalcCRC(int len) const; // calculate the CRC upon len bytes
void setCRC(); // calculate and set the CRC in the buffer
void setCRC(unsigned short CRC); // set the CRC in the buffer
unsigned short getCRC(); // extract CRC value from buffer
bool verifyCRC(); // return true for CRC match
// command
int getCommand();
void setCommand(int mode);
unsigned short getCRC() const; // extract CRC value from buffer
bool verifyCRC() const; // return true for CRC match
void setActiveMode() { Controller.Byte0 = 0x76; }; // this allows heater to save tuning params to EEPROM
void setPassiveMode() { Controller.Byte0 = 0x78; }; // this prevents heater saving tuning params to EEPROM
// command helpers
void resetCommand() { setRawCommand(0x00); };
void onCommand() { setRawCommand(0xA0); };
void offCommand() { setRawCommand(0x05); };
// raw command
int getRawCommand() { return Controller.Command; };
void setRawCommand(int mode) { Controller.Command = mode; };
// Run state
unsigned char getRunState() { return Heater.RunState; };
void setRunState(unsigned char state) { Heater.RunState = state; };
@ -144,10 +152,13 @@ public:
unsigned char getTemperature_Desired() { return Controller.DesiredTemperature; };
unsigned char getTemperature_Min() { return Controller.MinTemperature; };
unsigned char getTemperature_Max() { return Controller.MaxTemperature; };
void setThermostatMode(unsigned on) { Controller.OperatingMode = on ? 0x32 : 0xCD; };
// glow plug
short getGlowPlug_Current(); // glow plug current
short getGlowPlug_Voltage(); // glow plug voltage
void setGlowPlug_Current(short ampsx100); // glow plug current
void setGlowPlug_Voltage(short voltsx10); // glow plug voltage
// heat exchanger
short getTemperature_HeatExchg(); // temperature of heat exchanger
void setTemperature_HeatExchg(short degC); // temperature of heat exchanger

View file

@ -2,7 +2,7 @@
Chinese Heater Half Duplex Serial Data Sending Tool
Connects to the blue wire of a Chinese heater, which is the half duplex serial link.
Sends and receives data from serial port 1.
Sends and receives data from hardware serial port 1.
Terminology: Tx is to the heater unit, Rx is from the heater unit.
@ -12,7 +12,7 @@
This software can connect to the blue wire in a normal OEM system, detecting the
OEM controller and allowing extraction of the data or injecting on/off commands.
If Pin 21 is grounded on the Due, this simple stream will be reported over USB and
If Pin 21 is grounded on the Due, this simple stream will be reported over Serial and
no control from the Arduino will be allowed.
This allows sniffing of the blue wire in a normal system.
@ -20,11 +20,11 @@
If it has been > 100ms since the last blue wire activity this indicates a new frame
sequence is starting from the OEM controller.
Synchronise as such then count off the next 24 bytes storing them in the Controller's
data array. These bytes are then reported over USB to the PC in ASCII.
data array. These bytes are then reported over Serial to the PC in ASCII.
It is then expected the heater will respond with it's 24 bytes.
Capture those bytes and store them in the Heater1 data array.
Once again these bytes are then reported over USB to the PC in ASCII.
Once again these bytes are then reported over Serial to the PC in ASCII.
If no activity is sensed in a second, it is assumed no controller is attached and we
have full control over the heater.
@ -65,22 +65,26 @@
#include "Protocol.h"
#include "TxManage.h"
#include "pins.h"
#include "NVStorage.h"
#include "debugport.h"
#define BLUETOOTH
#define DEBUG_BTRX
#ifdef BLUETOOTH
#include "Bluetooth.h"
#endif
#if defined(__arm__)
// for Arduino Due
// Required for Arduino Due, UARTclass is derived from HardwareSerial
static UARTClass& BlueWire(Serial1);
#else
// for ESP32, Mega
// HardwareSerial is it for these boards
static HardwareSerial& BlueWire(Serial1);
#endif
void SerialReport(const char* hdr, const unsigned char* pData, const char* ftr);
void DebugReportFrame(const char* hdr, const CProtocol& Frame, const char* ftr);
class CommStates {
public:
@ -109,74 +113,94 @@ private:
CommStates CommState;
CTxManage TxManage(TxEnbPin, Serial1);
CProtocol OEMController; // most recent data packet received from OEM controller found on blue wire
CProtocol Heater1; // data packet received from heater in response to OEM controller packet
CProtocol Heater2; // data packet received from heater in response to our packet
CProtocol BTCParams(CProtocol::CtrlMode); // holds our local parameters, used in case of no OEM controller
CTxManage TxManage(TxEnbPin, BlueWire);
CProtocol OEMControllerFrame; // data packet received from heater in response to OEM controller packet
CProtocol HeaterFrame1; // data packet received from heater in response to OEM controller packet
CProtocol HeaterFrame2; // data packet received from heater in response to our packet
CProtocol DefaultBTCParams(CProtocol::CtrlMode); // defines the default parameters, used in case of no OEM controller
long lastRxTime; // used to observe inter character delays
// setup Non Volatile storage
// this is very much hardware dependent, we can use the ESP32's FLASH
#ifdef ESP32
CESP32HeaterStorage NVStorage;
#else
CHeaterStorage NVStorage; // dummy, for now
#endif
CHeaterStorage* pNVStorage = NULL;
void setup()
{
// initialize listening serial port
// initialize serial port to interact with the "blue wire"
// 25000 baud, Tx and Rx channels of Chinese heater comms interface:
// Tx/Rx data to/from heater, special baud rate for Chinese heater controllers
// Tx/Rx data to/from heater,
// Note special baud rate for Chinese heater controllers
pinMode(ListenOnlyPin, INPUT_PULLUP);
pinMode(KeyPin, OUTPUT);
// pinMode(Tx2Pin, OUTPUT);
digitalWrite(KeyPin, LOW);
// digitalWrite(Tx2Pin, HIGH);
#if defined(__arm__) || defined(__AVR__)
BlueWire.begin(25000);
pinMode(Rx1Pin, INPUT_PULLUP); // required for MUX to work properly
#else if defined(__ESP32__)
#elif ESP32
// ESP32
BlueWire.begin(25000, SERIAL_8N1, Rx1Pin, Tx1Pin);
BlueWire.begin(25000, SERIAL_8N1, Rx1Pin, Tx1Pin); // need to explicitly specify pins for pin multiplexer!
pinMode(Rx1Pin, INPUT_PULLUP); // required for MUX to work properly
#endif
// initialise serial monitor on serial port 0
USB.begin(115200);
// this is the usual USB connection to a PC
DebugPort.begin(115200);
// prepare for first long delay detection
lastRxTime = millis();
TxManage.begin(); // ensure Tx enable pin setup
TxManage.begin(); // ensure Tx enable pin is setup
// define defaults should heater controller be missing
BTCParams.setTemperature_Desired(23);
BTCParams.setTemperature_Actual(22);
BTCParams.Controller.OperatingVoltage = 120;
BTCParams.setPump_Min(16);
BTCParams.setPump_Max(55);
BTCParams.setFan_Min(1680);
BTCParams.setFan_Max(4500);
DefaultBTCParams.setTemperature_Desired(23);
DefaultBTCParams.setTemperature_Actual(22);
DefaultBTCParams.Controller.OperatingVoltage = 120;
DefaultBTCParams.setPump_Min(16);
DefaultBTCParams.setPump_Max(55);
DefaultBTCParams.setFan_Min(1680);
DefaultBTCParams.setFan_Max(4500);
#ifdef BLUETOOTH
Bluetooth_Init();
#endif
// create pointer to CHeaterStorage
// via the magic of polymorphism we can use this to access whatever
// storage is required for a specifc platform in a uniform way
pNVStorage = &NVStorage;
pNVStorage->init();
pNVStorage->load();
}
// main functional loop is based about a state machine approach, waiting for data
// to appear upon the blue wire, and marshalling into an appropriate receive buffer
// according to the state.
void loop()
{
unsigned long timenow = millis();
// check for test commands received from PC Over USB
if(USB.available()) {
char rxval = USB.read();
if(DebugPort.available()) {
char rxval = DebugPort.read();
if(rxval == '+') {
TxManage.RequestOn();
TxManage.queueOnRequest();
}
if(rxval == '-') {
TxManage.RequestOff();
TxManage.queueOffRequest();
}
}
#ifdef BLUETOOTH
// check for Bluetooth activity
Bluetooth_Check();
Bluetooth_Check(); // check for Bluetooth activity
#endif
// Handle time interval where we send data to the blue wire
@ -194,27 +218,28 @@ void loop()
// check for no rx traffic => no OEM controller
if(CommState.is(CommStates::Idle) && (RxTimeElapsed >= 970)) {
// have not seen any receive data for a second.
// OEM controller probably not connected.
// Skip to BTC_Tx, sending our own settings.
// OEM controller is probably not connected.
// Skip state machine immediately to BTC_Tx, sending our own settings.
CommState.set(CommStates::BTC_Tx);
bool bOurParams = true;
TxManage.Start(BTCParams, timenow, bOurParams);
bool isBTCmaster = true;
TxManage.PrepareFrame(DefaultBTCParams, isBTCmaster); // use our parameters, and mix in NV storage values
TxManage.Start(timenow);
#ifdef BLUETOOTH
Bluetooth_Report("[BTC]", BTCParams.Data); // BTC => Bluetooth Controller :-)
Bluetooth_SendFrame("[BTC]", TxManage.getFrame()); // BTC => Bluetooth Controller :-)
#endif
}
// precautionary action if all 24 bytes were not received whilst expecting them
if(RxTimeElapsed > 50) {
if( CommState.is(CommStates::ControllerRx) ||
CommState.is(CommStates::HeaterRx1) ||
CommState.is(CommStates::HeaterRx1) ||
CommState.is(CommStates::HeaterRx2) ) {
CommState.set(CommStates::Idle);
}
}
// read from port 1, the "blue wire" (to/from heater), store according to CommState
// read data from Serial port 1, the "blue wire" (to/from heater), store according to CommState
if (BlueWire.available()) {
lastRxTime = timenow;
@ -227,19 +252,19 @@ void loop()
int inByte = BlueWire.read(); // read hex byte
if( CommState.is(CommStates::ControllerRx) ) {
if(CommState.saveData(OEMController.Data, inByte) ) {
if(CommState.saveData(OEMControllerFrame.Data, inByte) ) {
CommState.set(CommStates::ControllerReport);
}
}
else if( CommState.is(CommStates::HeaterRx1) ) {
if( CommState.saveData(Heater1.Data, inByte) ) {
if( CommState.saveData(HeaterFrame1.Data, inByte) ) {
CommState.set(CommStates::HeaterReport1);
}
}
else if( CommState.is(CommStates::HeaterRx2) ) {
if( CommState.saveData(Heater2.Data, inByte) ) {
if( CommState.saveData(HeaterFrame2.Data, inByte) ) {
CommState.set(CommStates::HeaterReport2);
}
}
@ -250,35 +275,38 @@ void loop()
if( CommState.is(CommStates::ControllerReport) ) {
// filled controller frame, report
#ifdef BLUETOOTH
Bluetooth_Report("[OEM]", OEMController.Data);
// echo received OEM controller frame over Bluetooth, using [OEM] header
Bluetooth_SendFrame("[OEM]", OEMControllerFrame);
#endif
SerialReport("Ctrl ", OEMController.Data, " ");
DebugReportFrame("OEM ", OEMControllerFrame, " ");
CommState.set(CommStates::HeaterRx1);
}
else if(CommState.is(CommStates::HeaterReport1) ) {
// received heater frame (after controller message), report
SerialReport("Htr1 ", Heater1.Data, "\r\n");
DebugReportFrame("Htr1 ", HeaterFrame1, "\r\n");
#ifdef BLUETOOTH
Bluetooth_Report("[HTR]", Heater1.Data);
// echo heater reponse data to Bluetooth client
Bluetooth_SendFrame("[HTR]", HeaterFrame1);
#endif
if(digitalRead(ListenOnlyPin)) {
bool bOurParams = false;
TxManage.Start(OEMController, timenow, bOurParams);
bool isBTCmaster = false;
TxManage.PrepareFrame(OEMControllerFrame, isBTCmaster); // parrot OEM parameters, but block NV modes
TxManage.Start(timenow);
CommState.set(CommStates::BTC_Tx);
}
else {
CommState.set(CommStates::Idle); // "Listen Only" input held low, don't send out Tx
CommState.set(CommStates::Idle); // "Listen Only" input is held low, don't send out Tx
}
}
else if( CommState.is(CommStates::HeaterReport2) ) {
// received heater frame (after our control message), report
SerialReport("Htr2 ", Heater2.Data, "\r\n");
DebugReportFrame("Htr2 ", HeaterFrame2, "\r\n");
// if(!digitalRead(ListenOnlyPin)) {
#ifdef BLUETOOTH
Bluetooth_Report("[HTR]", Heater2.Data); // pin not grounded, suppress duplicate to BT
Bluetooth_SendFrame("[HTR]", HeaterFrame2); // pin not grounded, suppress duplicate to BT
#endif
// }
CommState.set(CommStates::Idle);
@ -286,14 +314,87 @@ void loop()
} // loop
void SerialReport(const char* hdr, const unsigned char* pData, const char* ftr)
void DebugReportFrame(const char* hdr, const CProtocol& Frame, const char* ftr)
{
USB.print(hdr); // header
DebugPort.print(hdr); // header
for(int i=0; i<24; i++) {
char str[16];
sprintf(str, "%02X ", pData[i]); // build 2 dig hex values
USB.print(str); // and print
sprintf(str, "%02X ", Frame.Data[i]); // build 2 dig hex values
DebugPort.print(str); // and print
}
DebugPort.print(ftr); // footer
}
void Command_Interpret(String line)
{
unsigned char cVal;
unsigned short sVal;
#ifdef DEBUG_BTRX
DebugPort.println(line);
DebugPort.println();
#endif
if(line.startsWith("[CMD]") ) {
DebugPort.write("BT command Rx'd: ");
// incoming command from BT app!
line.remove(0, 5); // strip away "[CMD]" header
if(line.startsWith("ON") ) {
DebugPort.write("ON\n");
TxManage.queueOnRequest();
}
else if(line.startsWith("OFF")) {
DebugPort.write("OFF\n");
TxManage.queueOffRequest();
}
else if(line.startsWith("Pmin")) {
line.remove(0, 4);
DebugPort.write("Pmin=");
cVal = (line.toFloat() * 10) + 0.5;
DebugPort.println(cVal);
pNVStorage->setPmin(cVal);
}
else if(line.startsWith("Pmax")) {
line.remove(0, 4);
DebugPort.write("Pmax=");
cVal = (line.toFloat() * 10) + 0.5;
DebugPort.println(cVal);
pNVStorage->setPmax(cVal);
}
else if(line.startsWith("Fmin")) {
line.remove(0, 4);
DebugPort.print("Fmin=");
sVal = line.toInt();
DebugPort.println(sVal);
pNVStorage->setFmin(sVal);
}
else if(line.startsWith("Fmax")) {
line.remove(0, 4);
DebugPort.print("Fmax=");
sVal = line.toInt();
DebugPort.println(sVal);
pNVStorage->setFmax(sVal);
}
else if(line.startsWith("save")) {
line.remove(0, 4);
DebugPort.write("save\n");
pNVStorage->save();
}
else if(line.startsWith("degC")) {
line.remove(0, 4);
DebugPort.write("degC=");
cVal = line.toInt();
DebugPort.println(cVal);
pNVStorage->setTemperature(cVal);
}
else if(line.startsWith("Mode")) {
line.remove(0, 4);
DebugPort.write("Mode=");
cVal = !pNVStorage->getThermostatMode();
pNVStorage->setThermostatMode(cVal);
DebugPort.println(cVal);
}
}
USB.print(ftr); // footer
}

View file

@ -1,51 +1,90 @@
#include "TxManage.h"
#include "NVStorage.h"
extern void SerialReport(const char* hdr, const unsigned char* pData, const char* ftr);
extern void DebugReportFrame(const char* hdr, const CProtocol&, const char* ftr);
CTxManage::CTxManage(int TxEnbPin, HardwareSerial& serial) :
m_Serial(serial),
m_Frame(CProtocol::CtrlMode)
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_nTxEnbPin = TxEnbPin;
m_nTxGatePin = TxGatePin;
}
void CTxManage::begin()
{
pinMode(m_nTxEnbPin, OUTPUT);
digitalWrite(m_nTxEnbPin, LOW);
pinMode(m_nTxGatePin, OUTPUT);
digitalWrite(m_nTxGatePin, LOW);
}
void
CTxManage::RequestOn()
CTxManage::queueOnRequest()
{
m_bOnReq = true;
}
void
CTxManage::RequestOff()
CTxManage::queueOffRequest()
{
m_bOffReq = true;
}
void
CTxManage::Start(const CProtocol& ref, unsigned long timenow, bool self)
void
CTxManage::PrepareFrame(const CProtocol& basisFrame, bool isBTCmaster)
{
m_Frame = ref;
// 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
m_TxFrame.resetCommand(); // no command upon blue wire initially, unless a request is pending
if(m_bOnReq) {
m_bOnReq = false;
m_TxFrame.onCommand();
}
if(m_bOffReq) {
m_bOffReq = false;
m_TxFrame.offCommand();
}
// 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
m_Frame.Data[0] = self ? 0x76 : 0x78;
if(isBTCmaster) {
m_TxFrame.setActiveMode(); // this allows heater to svae the tuning params to EEPROM
m_TxFrame.setFan_Min(pNVStorage->getFmin());
m_TxFrame.setFan_Max(pNVStorage->getFmax());
m_TxFrame.setPump_Min(pNVStorage->getPmin());
m_TxFrame.setPump_Max(pNVStorage->getPmax());
m_TxFrame.setThermostatMode(pNVStorage->getThermostatMode());
m_TxFrame.setTemperature_Desired(pNVStorage->getTemperature());
}
else {
m_TxFrame.setPassiveMode(); // this prevents the tuning parameters being saved by heater
}
if(timenow == 0)
// ensure CRC valid
m_TxFrame.setCRC();
}
void
CTxManage::Start(unsigned long timenow)
{
if(timenow == 0) // avoid a black hole if millis() wraps
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)
{
@ -55,51 +94,20 @@ CTxManage::CheckTx(unsigned long timenow)
if(diff > m_nStartDelay) {
// begin front porch of Tx gating pulse
digitalWrite(m_nTxEnbPin, HIGH);
digitalWrite(m_nTxGatePin, HIGH);
}
if(m_bTxPending && (diff > (m_nStartDelay + m_nFrontPorch))) {
// begin serial transmission
m_bTxPending = false;
_send();
m_BlueWireSerial.write(m_TxFrame.Data, 24); // write native binary values
DebugReportFrame("BTC ", m_TxFrame, " "); // report frame to debug port
}
if(diff > (m_nStartDelay + m_nFrameTime)) {
// conclude Tx gating
m_nStartTime = 0;
digitalWrite(m_nTxEnbPin, LOW);
digitalWrite(m_nTxGatePin, LOW);
}
}
return m_nStartTime == 0;
}
void
CTxManage::_send()
{
// install on/off commands if required
if(m_bOnReq) {
m_bOnReq = false;
m_Frame.Controller.Command = 0xa0;
}
else if(m_bOffReq) {
m_bOffReq = false;
m_Frame.Controller.Command = 0x05;
}
else {
m_Frame.Controller.Command = 0x00;
}
// ensure CRC valid
m_Frame.setCRC();
// send to heater - using binary
for(int i=0; i<24; i++) {
m_Serial.write(m_Frame.Data[i]); // write native binary values
}
Report();
}
void
CTxManage::Report()
{
SerialReport("Self ", m_Frame.Data, " ");
}

View file

@ -8,24 +8,24 @@ class CTxManage
const int m_nFrontPorch = 2;
public:
CTxManage(int TxEnbPin, HardwareSerial& serial);
void RequestOn();
void RequestOff();
void Start(const CProtocol& ref, unsigned long timenow, bool self);
CTxManage(int TxGatePin, HardwareSerial& serial);
void queueOnRequest();
void queueOffRequest();
void PrepareFrame(const CProtocol& Frame, bool isBTCmaster);
void Start(unsigned long timenow);
bool CheckTx(unsigned long timenow);
void Report();
void begin();
const CProtocol& getFrame() const { return m_TxFrame; };
private:
CProtocol m_Frame;
CProtocol m_TxFrame;
bool m_bOnReq;
bool m_bOffReq;
bool m_bTxPending;
int m_nTxEnbPin;
int m_nTxGatePin;
unsigned long m_nStartTime;
HardwareSerial& m_Serial;
HardwareSerial& m_BlueWireSerial;
void _send();
};
extern CTxManage TxManage;

View file

@ -0,0 +1,8 @@
#if defined(__arm__)
// Typically Arduino Due
static UARTClass& DebugPort(Serial);
#else
static HardwareSerial& DebugPort(Serial); // reference Serial as DebugPort
#endif

View file

@ -1,8 +1,4 @@
#if defined(__ESP32__)
const int TxEnbPin = 22;
#else
const int TxEnbPin = 20;
#endif
const int ListenOnlyPin = 21;
const int KeyPin = 15;
const int Tx1Pin = 18;
@ -10,10 +6,3 @@ const int Rx1Pin = 19;
const int Tx2Pin = 16;
const int Rx2Pin = 17;
#if defined(__arm__)
// for Arduino Due
static UARTClass& USB(Serial); // TODO: make proper ESP32 BT client
#else
// for ESP32, Mega
static HardwareSerial& USB(Serial); // TODO: make proper ESP32 BT client
#endif