2018-09-22 23:31:47 +00:00
|
|
|
/*
|
2018-09-23 08:59:19 +00:00
|
|
|
Chinese Heater Half Duplex Serial Data Sending Tool
|
2018-09-22 23:31:47 +00:00
|
|
|
|
|
|
|
Connects to the blue wire of a Chinese heater, which is the half duplex serial link.
|
2018-09-23 08:59:19 +00:00
|
|
|
Sends and receives data from serial port 1.
|
|
|
|
|
2018-09-22 23:31:47 +00:00
|
|
|
Terminology: Tx is to the heater unit, Rx is from the heater unit.
|
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
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.
|
2018-09-24 10:56:37 +00:00
|
|
|
|
|
|
|
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.
|
2018-09-23 08:59:19 +00:00
|
|
|
|
2018-09-22 23:31:47 +00:00
|
|
|
The binary data is received from the line.
|
2018-09-23 08:59:19 +00:00
|
|
|
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.
|
2018-09-22 23:31:47 +00:00
|
|
|
|
2018-09-24 10:56:37 +00:00
|
|
|
It is then expected the heater will respond with it's 24 bytes.
|
2018-09-23 08:59:19 +00:00
|
|
|
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.
|
2018-09-22 23:31:47 +00:00
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
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_________
|
2018-09-22 23:31:47 +00:00
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
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**
|
2018-09-25 10:56:32 +00:00
|
|
|
|
|
|
|
A HC-05 Bluetooth module is attached to Serial2:
|
|
|
|
TXD -> Rx2 (pin 17)
|
|
|
|
RXD -> Tx2 (pin 16)
|
|
|
|
EN(key) -> pin 15
|
2018-09-22 23:31:47 +00:00
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
|
|
|
|
This code only works with boards that have more than one hardware serial port like Arduino
|
2018-09-22 23:31:47 +00:00
|
|
|
Mega, Due, Zero etc.
|
|
|
|
|
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
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
|
2018-09-22 23:31:47 +00:00
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
created 23 Sep 2018 by Ray Jones
|
2018-09-22 23:31:47 +00:00
|
|
|
|
|
|
|
This example code is in the public domain.
|
|
|
|
*/
|
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
#include "Protocol.h"
|
2018-09-23 06:31:09 +00:00
|
|
|
#include "TxManage.h"
|
2018-10-18 09:49:14 +00:00
|
|
|
#include "pins.h"
|
|
|
|
|
2018-10-18 10:09:18 +00:00
|
|
|
#define BLUETOOTH
|
|
|
|
|
|
|
|
#ifdef BLUETOOTH
|
|
|
|
#include "Bluetooth.h"
|
|
|
|
#endif
|
|
|
|
|
2018-10-18 09:49:14 +00:00
|
|
|
#if defined(__arm__)
|
|
|
|
// for Arduino Due
|
|
|
|
static UARTClass& BlueWire(Serial1);
|
|
|
|
#else
|
|
|
|
// for ESP32, Mega
|
|
|
|
static HardwareSerial& BlueWire(Serial1);
|
|
|
|
#endif
|
2018-09-22 23:31:47 +00:00
|
|
|
|
2018-09-23 06:31:09 +00:00
|
|
|
void SerialReport(const char* hdr, const unsigned char* pData, const char* ftr);
|
|
|
|
|
|
|
|
class CommStates {
|
|
|
|
public:
|
2018-09-23 08:59:19 +00:00
|
|
|
// comms states
|
2018-09-23 06:31:09 +00:00
|
|
|
enum eCS {
|
2018-10-18 09:49:14 +00:00
|
|
|
Idle, ControllerRx, ControllerReport, HeaterRx1, HeaterReport1, BTC_Tx, HeaterRx2, HeaterReport2
|
2018-09-23 06:31:09 +00:00
|
|
|
};
|
|
|
|
CommStates() {
|
|
|
|
set(Idle);
|
|
|
|
}
|
|
|
|
void set(eCS eState) {
|
|
|
|
m_State = eState;
|
|
|
|
m_Count = 0;
|
|
|
|
}
|
|
|
|
bool is(eCS eState) {
|
|
|
|
return m_State == eState;
|
|
|
|
}
|
2018-09-23 08:59:19 +00:00
|
|
|
bool saveData(unsigned char* pData, unsigned char val, int limit = 24) { // returns true when buffer filled
|
2018-09-23 06:31:09 +00:00
|
|
|
pData[m_Count++] = val;
|
|
|
|
return m_Count == limit;
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
int m_State;
|
|
|
|
int m_Count;
|
|
|
|
};
|
|
|
|
|
2018-09-25 10:56:32 +00:00
|
|
|
|
2018-09-23 06:31:09 +00:00
|
|
|
CommStates CommState;
|
|
|
|
CTxManage TxManage(TxEnbPin, Serial1);
|
2018-10-18 09:49:14 +00:00
|
|
|
CProtocol OEMController; // most recent data packet received from OEM controller found on blue wire
|
2018-09-23 09:15:49 +00:00
|
|
|
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
|
2018-10-18 09:49:14 +00:00
|
|
|
CProtocol BTCParams(CProtocol::CtrlMode); // holds our local parameters, used in case of no OEM controller
|
2018-09-23 08:59:19 +00:00
|
|
|
long lastRxTime; // used to observe inter character delays
|
2018-09-22 23:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
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
|
2018-09-24 10:56:37 +00:00
|
|
|
pinMode(ListenOnlyPin, INPUT_PULLUP);
|
2018-09-25 10:56:32 +00:00
|
|
|
pinMode(KeyPin, OUTPUT);
|
|
|
|
// pinMode(Tx2Pin, OUTPUT);
|
|
|
|
digitalWrite(KeyPin, LOW);
|
|
|
|
// digitalWrite(Tx2Pin, HIGH);
|
2018-09-24 10:56:37 +00:00
|
|
|
|
2018-10-15 07:14:12 +00:00
|
|
|
#if defined(__arm__) || defined(__AVR__)
|
2018-09-23 08:59:19 +00:00
|
|
|
BlueWire.begin(25000);
|
2018-10-18 10:09:18 +00:00
|
|
|
pinMode(Rx1Pin, INPUT_PULLUP); // required for MUX to work properly
|
2018-10-15 07:14:12 +00:00
|
|
|
#else if defined(__ESP32__)
|
|
|
|
// ESP32
|
|
|
|
BlueWire.begin(25000, SERIAL_8N1, Rx1Pin, Tx1Pin);
|
2018-10-18 10:09:18 +00:00
|
|
|
pinMode(Rx1Pin, INPUT_PULLUP); // required for MUX to work properly
|
2018-10-15 07:14:12 +00:00
|
|
|
#endif
|
2018-09-22 23:31:47 +00:00
|
|
|
|
|
|
|
// initialise serial monitor on serial port 0
|
2018-09-23 08:59:19 +00:00
|
|
|
USB.begin(115200);
|
2018-09-22 23:31:47 +00:00
|
|
|
|
|
|
|
// prepare for first long delay detection
|
|
|
|
lastRxTime = millis();
|
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
TxManage.begin(); // ensure Tx enable pin setup
|
|
|
|
|
|
|
|
// define defaults should heater controller be missing
|
2018-10-18 09:49:14 +00:00
|
|
|
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);
|
|
|
|
|
2018-10-18 10:09:18 +00:00
|
|
|
#ifdef BLUETOOTH
|
2018-10-18 09:49:14 +00:00
|
|
|
Bluetooth_Init();
|
2018-10-18 10:09:18 +00:00
|
|
|
#endif
|
2018-09-22 23:31:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void loop()
|
|
|
|
{
|
|
|
|
unsigned long timenow = millis();
|
|
|
|
|
2018-09-23 06:31:09 +00:00
|
|
|
// check for test commands received from PC Over USB
|
2018-09-23 08:59:19 +00:00
|
|
|
if(USB.available()) {
|
|
|
|
char rxval = USB.read();
|
2018-09-22 23:31:47 +00:00
|
|
|
if(rxval == '+') {
|
2018-09-23 06:31:09 +00:00
|
|
|
TxManage.RequestOn();
|
2018-09-22 23:31:47 +00:00
|
|
|
}
|
|
|
|
if(rxval == '-') {
|
2018-09-23 06:31:09 +00:00
|
|
|
TxManage.RequestOff();
|
2018-09-22 23:31:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-18 10:09:18 +00:00
|
|
|
#ifdef BLUETOOTH
|
2018-10-18 09:49:14 +00:00
|
|
|
// check for Bluetooth activity
|
|
|
|
Bluetooth_Check();
|
2018-10-18 10:09:18 +00:00
|
|
|
#endif
|
2018-09-24 10:56:37 +00:00
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
// Handle time interval where we send data to the blue wire
|
2018-10-18 09:49:14 +00:00
|
|
|
if(CommState.is(CommStates::BTC_Tx)) {
|
2018-09-23 08:59:19 +00:00
|
|
|
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
|
2018-09-23 06:31:09 +00:00
|
|
|
}
|
2018-09-22 23:31:47 +00:00
|
|
|
}
|
|
|
|
|
2018-09-23 06:31:09 +00:00
|
|
|
|
|
|
|
// 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.
|
2018-10-18 09:49:14 +00:00
|
|
|
// Skip to BTC_Tx, sending our own settings.
|
|
|
|
CommState.set(CommStates::BTC_Tx);
|
2018-09-23 08:59:19 +00:00
|
|
|
bool bOurParams = true;
|
2018-10-18 09:49:14 +00:00
|
|
|
TxManage.Start(BTCParams, timenow, bOurParams);
|
2018-10-18 10:09:18 +00:00
|
|
|
#ifdef BLUETOOTH
|
2018-10-18 09:49:14 +00:00
|
|
|
Bluetooth_Report("[BTC]", BTCParams.Data); // BTC => Bluetooth Controller :-)
|
2018-10-18 10:09:18 +00:00
|
|
|
#endif
|
2018-09-22 23:31:47 +00:00
|
|
|
}
|
2018-09-23 06:31:09 +00:00
|
|
|
|
2018-09-24 10:56:37 +00:00
|
|
|
// precautionary action if all 24 bytes were not received whilst expecting them
|
|
|
|
if(RxTimeElapsed > 50) {
|
2018-09-23 08:59:19 +00:00
|
|
|
if( CommState.is(CommStates::ControllerRx) ||
|
|
|
|
CommState.is(CommStates::HeaterRx1) ||
|
|
|
|
CommState.is(CommStates::HeaterRx2) ) {
|
2018-09-23 06:31:09 +00:00
|
|
|
|
|
|
|
CommState.set(CommStates::Idle);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// read from port 1, the "blue wire" (to/from heater), store according to CommState
|
2018-09-23 08:59:19 +00:00
|
|
|
if (BlueWire.available()) {
|
2018-09-22 23:31:47 +00:00
|
|
|
|
|
|
|
lastRxTime = timenow;
|
2018-09-23 06:31:09 +00:00
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
// detect start of a new frame sequence from OEM controller
|
|
|
|
if( CommState.is(CommStates::Idle) && (RxTimeElapsed > 100)) {
|
|
|
|
CommState.set(CommStates::ControllerRx);
|
2018-09-22 23:31:47 +00:00
|
|
|
}
|
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
int inByte = BlueWire.read(); // read hex byte
|
2018-09-22 23:31:47 +00:00
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
if( CommState.is(CommStates::ControllerRx) ) {
|
2018-10-18 09:49:14 +00:00
|
|
|
if(CommState.saveData(OEMController.Data, inByte) ) {
|
2018-09-23 08:59:19 +00:00
|
|
|
CommState.set(CommStates::ControllerReport);
|
2018-09-22 23:31:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
else if( CommState.is(CommStates::HeaterRx1) ) {
|
|
|
|
if( CommState.saveData(Heater1.Data, inByte) ) {
|
|
|
|
CommState.set(CommStates::HeaterReport1);
|
2018-09-22 23:31:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
else if( CommState.is(CommStates::HeaterRx2) ) {
|
|
|
|
if( CommState.saveData(Heater2.Data, inByte) ) {
|
|
|
|
CommState.set(CommStates::HeaterReport2);
|
2018-09-22 23:31:47 +00:00
|
|
|
}
|
2018-09-23 06:31:09 +00:00
|
|
|
}
|
2018-09-22 23:31:47 +00:00
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
} // BlueWire.available
|
2018-09-22 23:31:47 +00:00
|
|
|
|
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
if( CommState.is(CommStates::ControllerReport) ) {
|
2018-09-23 06:31:09 +00:00
|
|
|
// filled controller frame, report
|
2018-10-18 10:09:18 +00:00
|
|
|
#ifdef BLUETOOTH
|
2018-10-18 09:49:14 +00:00
|
|
|
Bluetooth_Report("[OEM]", OEMController.Data);
|
2018-10-18 10:09:18 +00:00
|
|
|
#endif
|
2018-10-18 09:49:14 +00:00
|
|
|
SerialReport("Ctrl ", OEMController.Data, " ");
|
2018-09-23 08:59:19 +00:00
|
|
|
CommState.set(CommStates::HeaterRx1);
|
2018-09-23 06:31:09 +00:00
|
|
|
}
|
2018-09-22 23:31:47 +00:00
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
else if(CommState.is(CommStates::HeaterReport1) ) {
|
2018-09-23 06:31:09 +00:00
|
|
|
// received heater frame (after controller message), report
|
|
|
|
SerialReport("Htr1 ", Heater1.Data, "\r\n");
|
2018-10-18 10:09:18 +00:00
|
|
|
#ifdef BLUETOOTH
|
2018-10-18 09:49:14 +00:00
|
|
|
Bluetooth_Report("[HTR]", Heater1.Data);
|
2018-10-18 10:09:18 +00:00
|
|
|
#endif
|
2018-09-22 23:31:47 +00:00
|
|
|
|
2018-09-24 10:56:37 +00:00
|
|
|
if(digitalRead(ListenOnlyPin)) {
|
|
|
|
bool bOurParams = false;
|
2018-10-18 09:49:14 +00:00
|
|
|
TxManage.Start(OEMController, timenow, bOurParams);
|
|
|
|
CommState.set(CommStates::BTC_Tx);
|
2018-09-24 10:56:37 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
CommState.set(CommStates::Idle); // "Listen Only" input held low, don't send out Tx
|
|
|
|
}
|
2018-09-23 06:31:09 +00:00
|
|
|
}
|
2018-09-22 23:31:47 +00:00
|
|
|
|
2018-09-23 08:59:19 +00:00
|
|
|
else if( CommState.is(CommStates::HeaterReport2) ) {
|
2018-09-23 06:31:09 +00:00
|
|
|
// received heater frame (after our control message), report
|
|
|
|
SerialReport("Htr2 ", Heater2.Data, "\r\n");
|
2018-10-15 07:14:12 +00:00
|
|
|
// if(!digitalRead(ListenOnlyPin)) {
|
2018-10-18 10:09:18 +00:00
|
|
|
#ifdef BLUETOOTH
|
2018-10-18 09:49:14 +00:00
|
|
|
Bluetooth_Report("[HTR]", Heater2.Data); // pin not grounded, suppress duplicate to BT
|
2018-10-18 10:09:18 +00:00
|
|
|
#endif
|
2018-10-15 07:14:12 +00:00
|
|
|
// }
|
2018-09-23 06:31:09 +00:00
|
|
|
CommState.set(CommStates::Idle);
|
2018-09-22 23:31:47 +00:00
|
|
|
}
|
|
|
|
|
2018-09-23 06:31:09 +00:00
|
|
|
} // loop
|
2018-09-22 23:31:47 +00:00
|
|
|
|
2018-09-23 06:31:09 +00:00
|
|
|
void SerialReport(const char* hdr, const unsigned char* pData, const char* ftr)
|
|
|
|
{
|
2018-09-23 08:59:19 +00:00
|
|
|
USB.print(hdr); // header
|
2018-09-23 06:31:09 +00:00
|
|
|
for(int i=0; i<24; i++) {
|
|
|
|
char str[16];
|
2018-09-23 08:59:19 +00:00
|
|
|
sprintf(str, "%02X ", pData[i]); // build 2 dig hex values
|
|
|
|
USB.print(str); // and print
|
2018-09-22 23:31:47 +00:00
|
|
|
}
|
2018-09-23 08:59:19 +00:00
|
|
|
USB.print(ftr); // footer
|
2018-09-25 10:56:32 +00:00
|
|
|
}
|
|
|
|
|