ESP32_ChinaDieselHeater_Con.../Arduino/SenderTrial2/SenderTrial2.ino
rljonesau baed4b3079 Beefed up Bluetooth handling to include tuning parameters.
Changed Tx headers to [OEM] and [BTC] for the OEM controller and this Bluetooth controller.
Added #ifdefs to accomodate Mega and ESP32.
Built and tested on ESP32, Serial1 talks to heater OK using standard pin numbering :-)
2018-10-15 18:14:12 +11:00

452 lines
No EOL
13 KiB
C++

/*
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.
Terminology: Tx is to the heater unit, Rx is from the heater unit.
Typical data frame timing on the blue wire is:
__Tx_Rx____________________________Tx_Rx____________________________Tx_Rx___________
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
no control from the Arduino will be allowed.
This allows sniffing of the blue wire in a normal system.
The binary data is received from the line.
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.
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.
If no activity is sensed in a second, it is assumed no controller is attached and we
have full control over the heater.
Either way we can now inject a message onto the blue wire allowing our custom
on/off control.
We must remain synchronous with the OEM controller if it exists otherwise E-07
faults will be caused.
Typical data frame timing on the blue wire is then:
__OEMTx_HtrRx__OurTx_HtrRx____________OEMTx_HtrRx__OurTx_HtrRx____________OEMTx_HtrRx__OurTx_HtrRx_________
The second HtrRx to the next OEMTx delay is always > 100ms and is paced by the OEM controller.
The delay before seeing Heater Rx data after any Tx is usually much less than 10ms.
But this does rise if new max/min or voltage settings are sent.
**The heater only ever sends Rx data in response to a data frame from a controller**
A HC-05 Bluetooth module is attached to Serial2:
TXD -> Rx2 (pin 17)
RXD -> Tx2 (pin 16)
EN(key) -> pin 15
This code only works with boards that have more than one hardware serial port like Arduino
Mega, Due, Zero etc.
The circuit:
- a Tx Rx multiplexer is required to combine the Arduino's Tx1 And Rx1 pins onto the blue wire.
- a Tx Enable signal from pin 20 controls the multiplexer
- Serial logging software on Serial0 via USB link
created 23 Sep 2018 by Ray Jones
This example code is in the public domain.
*/
#include "Protocol.h"
#include "TxManage.h"
void SerialReport(const char* hdr, const unsigned char* pData, const char* ftr);
void BluetoothDetect();
bool BlueToothCommand(const char* cmd);
void BlueToothReport(const char* pHdr, const unsigned char Data[24]);
void BluetoothInterpret();
class CommStates {
public:
// comms states
enum eCS {
Idle, ControllerRx, ControllerReport, HeaterRx1, HeaterReport1, SelfTx, HeaterRx2, HeaterReport2
};
CommStates() {
set(Idle);
}
void set(eCS eState) {
m_State = eState;
m_Count = 0;
}
bool is(eCS eState) {
return m_State == eState;
}
bool saveData(unsigned char* pData, unsigned char val, int limit = 24) { // returns true when buffer filled
pData[m_Count++] = val;
return m_Count == limit;
}
private:
int m_State;
int m_Count;
};
#if defined(__arm__)
// for Arduino Due
UARTClass& USB(Serial);
UARTClass& BlueWire(Serial1);
UARTClass& BlueTooth(Serial2);
#else
// for ESP32, Mega
HardwareSerial& USB(Serial);
HardwareSerial& BlueWire(Serial1);
#if defined(__ESP32__)
// ESP32
HardwareSerial& BlueTooth(Serial2); // TODO: make proper ESP32 BT client
#else
// Mega
HardwareSerial& BlueTooth(Serial2);
#endif
#endif
#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;
const int Rx1Pin = 19;
const int Tx2Pin = 16;
const int Rx2Pin = 17;
const int BTRates[] = {
9600, 38400, 115200, 19200, 57600, 2400, 4800
};
CommStates CommState;
CTxManage TxManage(TxEnbPin, Serial1);
CProtocol Controller; // 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 SelfParams(CProtocol::CtrlMode); // holds our local parameters, used in case of no OEM controller
long lastRxTime; // used to observe inter character delays
bool bBlueToothAvailable = false;
String BluetoothRxData;
void setup()
{
// initialize listening serial port
// 25000 baud, Tx and Rx channels of Chinese heater comms interface:
// Tx/Rx data to/from heater, 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(19, INPUT_PULLUP); // required for MUX to work properly
#else if defined(__ESP32__)
// ESP32
#define RXD2 16
#define TXD2 17
BlueWire.begin(25000, SERIAL_8N1, Rx1Pin, Tx1Pin);
#endif
// initialise serial monitor on serial port 0
USB.begin(115200);
// prepare for first long delay detection
lastRxTime = millis();
TxManage.begin(); // ensure Tx enable pin setup
// define defaults should heater controller be missing
SelfParams.setTemperature_Desired(23);
SelfParams.setTemperature_Actual(22);
SelfParams.Controller.OperatingVoltage = 120;
SelfParams.setPump_Min(16);
SelfParams.setPump_Max(55);
SelfParams.setFan_Min(1680);
SelfParams.setFan_Max(4500);
BluetoothDetect();
}
void loop()
{
unsigned long timenow = millis();
// check for test commands received from PC Over USB
if(USB.available()) {
char rxval = USB.read();
if(rxval == '+') {
TxManage.RequestOn();
}
if(rxval == '-') {
TxManage.RequestOff();
}
}
// check for data coming back over Bluetooth
if(BlueTooth.available()) {
char rxVal = BlueTooth.read();
if(isControl(rxVal)) { // "End of Line"
BluetoothRxData += '\0';
BluetoothInterpret();
}
else {
BluetoothRxData += rxVal; // append new char to our Rx buffer
}
}
// Handle time interval where we send data to the blue wire
if(CommState.is(CommStates::SelfTx)) {
lastRxTime = timenow; // we are pumping onto blue wire, track this activity!
if(TxManage.CheckTx(timenow) ) { // monitor our data delivery
CommState.set(CommStates::HeaterRx2); // then await heater repsonse
}
}
// calc elapsed time since last rxd byte to detect no other controller, or start of frame sequence
unsigned long RxTimeElapsed = timenow - lastRxTime;
// 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 SelfTx, sending our own settings.
CommState.set(CommStates::SelfTx);
bool bOurParams = true;
TxManage.Start(SelfParams, timenow, bOurParams);
BlueToothReport("[BTC]", SelfParams.Data); // BTC => Bluetooth Controller :-)
}
// 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::HeaterRx2) ) {
CommState.set(CommStates::Idle);
}
}
// read from port 1, the "blue wire" (to/from heater), store according to CommState
if (BlueWire.available()) {
lastRxTime = timenow;
// detect start of a new frame sequence from OEM controller
if( CommState.is(CommStates::Idle) && (RxTimeElapsed > 100)) {
CommState.set(CommStates::ControllerRx);
}
int inByte = BlueWire.read(); // read hex byte
if( CommState.is(CommStates::ControllerRx) ) {
if(CommState.saveData(Controller.Data, inByte) ) {
CommState.set(CommStates::ControllerReport);
}
}
else if( CommState.is(CommStates::HeaterRx1) ) {
if( CommState.saveData(Heater1.Data, inByte) ) {
CommState.set(CommStates::HeaterReport1);
}
}
else if( CommState.is(CommStates::HeaterRx2) ) {
if( CommState.saveData(Heater2.Data, inByte) ) {
CommState.set(CommStates::HeaterReport2);
}
}
} // BlueWire.available
if( CommState.is(CommStates::ControllerReport) ) {
// filled controller frame, report
BlueToothReport("[OEM]", Controller.Data);
SerialReport("Ctrl ", Controller.Data, " ");
CommState.set(CommStates::HeaterRx1);
}
else if(CommState.is(CommStates::HeaterReport1) ) {
// received heater frame (after controller message), report
SerialReport("Htr1 ", Heater1.Data, "\r\n");
BlueToothReport("[HTR]", Heater1.Data);
if(digitalRead(ListenOnlyPin)) {
bool bOurParams = false;
TxManage.Start(Controller, timenow, bOurParams);
CommState.set(CommStates::SelfTx);
}
else {
CommState.set(CommStates::Idle); // "Listen Only" input 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");
// if(!digitalRead(ListenOnlyPin)) {
BlueToothReport("[HTR]", Heater2.Data); // pin not grounded, suppress duplicate to BT
// }
CommState.set(CommStates::Idle);
}
} // loop
void SerialReport(const char* hdr, const unsigned char* pData, const char* ftr)
{
USB.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
}
USB.print(ftr); // footer
}
void BluetoothDetect()
{
#if defined(__ESP32__)
#else
// 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.
BlueTooth.begin(9600);
digitalWrite(KeyPin, HIGH);
delay(500);
USB.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... ");
BlueTooth.begin(BTRates[BTidx]); // open serial port at a certain baud rate
BlueTooth.print("\r\n");
BlueTooth.setTimeout(50);
if(BlueToothCommand("AT\r\n")) {
USB.println(" OK.");
break;
}
// failed, try another baud rate
USB.println("");
BlueTooth.flush();
}
USB.println("");
if(BTidx == maxTries) {
USB.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.");
bBlueToothAvailable = true;
}
else {
do {
USB.println("HC-05 found");
USB.print(" Setting Name to \"DieselHeater\"... ");
if(!BlueToothCommand("AT+NAME=\"DieselHeater\"\r\n")) {
USB.println("FAILED");
break;
}
USB.println("OK");
USB.print(" Setting baud rate to 115200N81...");
if(!BlueToothCommand("AT+UART=115200,1,0\r\n")) {
USB.println("FAILED");
break;
};
USB.println("OK");
BlueTooth.begin(115200);
bBlueToothAvailable = true;
} while(0);
}
}
digitalWrite(KeyPin, LOW); // leave HC-05 command mode
delay(500);
if(!bBlueToothAvailable)
BlueTooth.end(); // close serial port if no module found
USB.println("");
#endif
}
bool BlueToothCommand(const char* cmd)
{
if(bBlueToothAvailable) {
BlueTooth.print(cmd);
char RxBuffer[16];
memset(RxBuffer, 0, 16);
int read = BlueTooth.readBytesUntil('\n', RxBuffer, 16); // \n is not included in returned string!
if((read == 3) && (0 == strcmp(RxBuffer, "OK\r")) ) {
return true;
}
return false;
}
return false;
}
void BlueToothReport(const char* pHdr, const unsigned char Data[24])
{
if(bBlueToothAvailable) {
BlueTooth.print(pHdr);
BlueTooth.write(Data, 24);
}
}
void BluetoothInterpret()
{
if(BluetoothRxData.startsWith("[CMD]") ) {
USB.write("BT command Rx'd: ");
// incoming command from BT app!
BluetoothRxData.remove(0, 5); // strip away "[CMD]" header
if(BluetoothRxData.startsWith("ON")) {
USB.write("ON\n");
TxManage.RequestOn();
}
else if(BluetoothRxData.startsWith("OFF")) {
USB.write("OFF\n");
TxManage.RequestOff();
}
else if(BluetoothRxData.startsWith("Pmin")) {
USB.write("Pmin\n");
}
else if(BluetoothRxData.startsWith("Pmax")) {
USB.write("Pmax\n");
}
else if(BluetoothRxData.startsWith("Fmin")) {
USB.write("Fmin\n");
}
else if(BluetoothRxData.startsWith("Fmax")) {
USB.write("Fmax\n");
}
}
BluetoothRxData = ""; //flush string, ready for new data
}