diff --git a/Arduino/SenderTrial2/CFrame.cpp b/Arduino/SenderTrial2/CFrame.cpp index 6e3151b..97b2e6e 100644 --- a/Arduino/SenderTrial2/CFrame.cpp +++ b/Arduino/SenderTrial2/CFrame.cpp @@ -2,12 +2,12 @@ #include "CFrame.h" unsigned short -CFrame::CalcCRC() +CFrame::CalcCRC(int len) { // calculate a CRC-16/MODBUS checksum using the first 22 bytes of the data array unsigned short wCRCWord = 0xFFFF; - int wLength = 22; + int wLength = len; unsigned char* pData = Data; while (wLength--) { @@ -22,7 +22,7 @@ CFrame::CalcCRC() void CFrame::setCRC() { - setCRC(CalcCRC()); + setCRC(CalcCRC(22)); } void @@ -32,6 +32,14 @@ CFrame::setCRC(unsigned short CRC) Data[23] = (CRC >> 0) & 0xff; // LSB of CRC in Data[23] } +CFrame& +CFrame::operator=(CFrame& rhs) +{ + memcpy(Data, rhs.Data, 24); + return *this; +} + + unsigned short CFrame::getCRC() { @@ -45,7 +53,7 @@ CFrame::getCRC() bool CFrame::verifyCRC() { - unsigned short CRC = CalcCRC(); // calculate CRC based on first 22 bytes + unsigned short CRC = CalcCRC(22); // calculate CRC based on first 22 bytes return (getCRC() == CRC); // does it match the stored values? } diff --git a/Arduino/SenderTrial2/CFrame.h b/Arduino/SenderTrial2/CFrame.h index 94afbe7..b077f7e 100644 --- a/Arduino/SenderTrial2/CFrame.h +++ b/Arduino/SenderTrial2/CFrame.h @@ -1,33 +1,35 @@ +#ifndef _CFRAME_H_ +#define _CFRAME_H_ class CFrame { public: union { unsigned char Data[24]; struct { - unsigned char Byte0; // always 0x76 - unsigned char Len; // always 0x16 == 22 - unsigned char Command; // transient commands: 00: NOP, 0xa0 START, 0x05: STOP - unsigned char ActualTemperature; // 1degC / digit - unsigned char DesiredTemperature; // 1degC / digit - unsigned char MinPumpFreq; // 0.1Hz/digit - unsigned char MaxPumpFreq; // 0.1Hz/digit - unsigned char MinFanRPM_MSB; // 16 bit - big endian MSB - unsigned char MinFanRPM_LSB; // 16 bit - big endian LSB : 1 RPM / digit - unsigned char MaxFanRPM_MSB; // 16 bit - big endian MSB - unsigned char MaxFanRPM_LSB; // 16 bit - big endian LSB : 1 RPM / digit - unsigned char OperatingVoltage; // 120, 240 : 0.1V/digit - unsigned char FanSensor; // SN-1 or SN-2 - unsigned char OperatingMode; // 0x32:Thermostat, 0xCD:Fixed - unsigned char MinTemperature; // Minimum settable temperature - unsigned char MaxTemperature; // Maximum settable temperature - unsigned char MinTempRise; // temp rise to sense running OK - unsigned char Prime; // 00: normal, 0x5A: fuel prime - unsigned char Unknown1_MSB; // always 0x01 - unsigned char Unknown1_LSB; // always 0x2c "300 secs = max run without burn detected"? - unsigned char Unknown2_MSB; // always 0x0d - unsigned char Unknown2_LSB; // always 0xac "3500 ?" - unsigned char CRC_MSB; - unsigned char CRC_LSB; + unsigned char Byte0; // [0] always 0x76 + unsigned char Len; // [1] always 0x16 == 22 + unsigned char Command; // [2] transient commands: 00: NOP, 0xa0 START, 0x05: STOP + unsigned char ActualTemperature; // [3] 1degC / digit + unsigned char DesiredTemperature; // [4] 1degC / digit + unsigned char MinPumpFreq; // [5] 0.1Hz/digit + unsigned char MaxPumpFreq; // [6] 0.1Hz/digit + unsigned char MinFanRPM_MSB; // [7] 16 bit - big endian MSB + unsigned char MinFanRPM_LSB; // [8] 16 bit - big endian LSB : 1 RPM / digit + unsigned char MaxFanRPM_MSB; // [9] 16 bit - big endian MSB + unsigned char MaxFanRPM_LSB; // [10] 16 bit - big endian LSB : 1 RPM / digit + unsigned char OperatingVoltage; // [11] 120, 240 : 0.1V/digit + unsigned char FanSensor; // [12] SN-1 or SN-2 + unsigned char OperatingMode; // [13] 0x32:Thermostat, 0xCD:Fixed + unsigned char MinTemperature; // [14] Minimum settable temperature + unsigned char MaxTemperature; // [15] Maximum settable temperature + unsigned char MinTempRise; // [16] temp rise to sense running OK + unsigned char Prime; // [17] 00: normal, 0x5A: fuel prime + unsigned char Unknown1_MSB; // [18] always 0x01 + unsigned char Unknown1_LSB; // [19] always 0x2c "300 secs = max run without burn detected"? + unsigned char Unknown2_MSB; // [20] always 0x0d + unsigned char Unknown2_LSB; // [21] always 0xac "3500 ?" + unsigned char CRC_MSB; // [22] + unsigned char CRC_LSB; // [23] } Tx; struct { unsigned char Byte0; // always 0x76 @@ -97,7 +99,7 @@ public: CFrame(int TxMode) { Init(TxMode); }; void Init(int Txmode); // CRC handlers - unsigned short CalcCRC(); // calculate and set the CRC upon first 22 bytes + unsigned short CalcCRC(int len); // calculate and set the CRC upon first 22 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 @@ -124,5 +126,8 @@ public: short getTemperature_GlowPin(); // temperature of glow pin short getTemperature_HeatExchg(); // temperature of heat exchanger short getTemperature_Inlet(); // temperature near inlet + + CFrame& operator=(CFrame& rhs); }; +#endif \ No newline at end of file diff --git a/Arduino/SenderTrial2/SenderTrial2.ino b/Arduino/SenderTrial2/SenderTrial2.ino index 3d18199..6deb22a 100644 --- a/Arduino/SenderTrial2/SenderTrial2.ino +++ b/Arduino/SenderTrial2/SenderTrial2.ino @@ -42,24 +42,49 @@ */ #include "CFrame.h" +#include "TxManage.h" +void SerialReport(const char* hdr, const unsigned char* pData, const char* ftr); + +class CommStates { + public: + enum eCS { + Idle, CtrlRx, CtrlRpt, HtrRx1, HtrRpt1, SelfTx, HtrRx2, HtrRpt2 + }; + CommStates() { + set(Idle); + } + void set(eCS eState) { + m_State = eState; + m_Count = 0; + } + bool is(eCS eState) { + return m_State == eState; + } + bool rxData(unsigned char* pData, unsigned char val, int limit = 24) { // return true if buffer filled + pData[m_Count++] = val; + return m_Count == limit; + } +private: + int m_State; + int m_Count; +}; + +const int TxEnbPin = 17; +CommStates CommState; CFrame Controller(CFrame::TxMode); CFrame TxFrame(CFrame::TxMode); +CTxManage TxManage(TxEnbPin, Serial1); CFrame Heater1; CFrame Heater2; -const int TxEnbPin = 17; long lastRxTime; // used to calculate inter character delay long TxEnbTime; // used to reset TxEnb low bool bOnEvent = false; bool bOffEvent = false; -void CheckTx(); - void setup() { - pinMode(TxEnbPin, OUTPUT); - digitalWrite(TxEnbPin, LOW); // 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 @@ -72,196 +97,122 @@ void setup() // prepare for first long delay detection lastRxTime = millis(); + TxManage.begin(); + } void loop() { - static int count = 0; - static unsigned long lastTx = 0; - static bool bAllowTxSlot = false; - static int Stage = -1; - - char str[16]; - unsigned long timenow = millis(); + // check for test commands received from PC Over USB if(Serial.available()) { char rxval = Serial.read(); if(rxval == '+') { - bOnEvent = true; + TxManage.RequestOn(); } if(rxval == '-') { - bOffEvent = true; + TxManage.RequestOff(); } } - if((Stage == 4) && (timenow - lastTx) > 10) { - TxEnbTime = timenow; - if(TxEnbTime == 0) - TxEnbTime++; - digitalWrite(TxEnbPin, HIGH); + if(CommState.is(CommStates::SelfTx)) { + // Interval where we should send data to the blue wire + lastRxTime = timenow; // not expecting rx data, but we are pumping onto blue wire! + TxManage.Tick(timenow); // keep trying to send our data + if(!TxManage.isBusy()) { // until completed + CommState.set(CommStates::HtrRx2); // then await heater repsonse + } } - CheckTx(); - // check serial data has gone quite for a while so we can trample in... - // calc elapsed time since last rxd byte to detect start of frame sequence - /* unsigned long TxSlot = timenow - lastRxTime; - -// if(Slot > 50 && TxEnbTime == 0 && (bOnEvent || bOffEvent)) { - if(bAllowTxSlot && (TxSlot > 50) && (TxEnbTime == 0)) { - TxEnbTime = timenow; - if(TxEnbTime == 0) - TxEnbTime++; - digitalWrite(TxEnbPin, HIGH); + + // 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 bSelfParams = true; + TxManage.Send(timenow, bSelfParams); } -*/ - // read from port 1, the "Tx Data" (to heater), send to the serial monitor: + + // precaution if 24 bytes were not received whilst expecting them + if(RxTimeElapsed > 50) { + if( CommState.is(CommStates::CtrlRx) || + CommState.is(CommStates::HtrRx1) || + CommState.is(CommStates::HtrRx2) ) { + + CommState.set(CommStates::Idle); + } + } + + // read from port 1, the "blue wire" (to/from heater), store according to CommState if (Serial1.available()) { - // calc elapsed time since last rxd byte to detect start of frame sequence - unsigned long diff = timenow - lastRxTime; lastRxTime = timenow; - - if((Stage == -1) && (diff > 100)) { // this indicates the start of a new frame sequence from the controller - Stage = 0; - count = 0; + + if( CommState.is(CommStates::Idle) && (RxTimeElapsed > 100)) { // this indicates the start of a new frame sequence from another controller + CommState.set(CommStates::CtrlRx); } int inByte = Serial1.read(); // read hex byte - if(Stage == 0) { - Controller.Data[count++] = inByte; - if(count == 24) { - Stage = 1; + if( CommState.is(CommStates::CtrlRx) ) { + if(CommState.rxData(Controller.Data, inByte) ) { + CommState.set(CommStates::CtrlRpt); } } - if(Stage == 2) { - Heater1.Data[count++] = inByte; - if(count == 24) { - Stage = 3; + else if( CommState.is(CommStates::HtrRx1) ) { + if( CommState.rxData(Heater1.Data, inByte) ) { + CommState.set(CommStates::HtrRpt1); } } - if(Stage == 6) { - Heater2.Data[count++] = inByte; - if(count == 24) { - Stage = 7; + else if( CommState.is(CommStates::HtrRx2) ) { + if( CommState.rxData(Heater2.Data, inByte) ) { + CommState.set(CommStates::HtrRpt2); } - } + } + } // Serial1.available + + + if( CommState.is(CommStates::CtrlRpt) ) { + // filled controller frame, report + SerialReport("Ctrl ", Controller.Data, " "); + CommState.set(CommStates::HtrRx1); } + + else if(CommState.is(CommStates::HtrRpt1) ) { + // received heater frame (after controller message), report + SerialReport("Htr1 ", Heater1.Data, "\r\n"); - // dump to PC after capturing all 24 Rx bytes in a frame session - if(Stage == 1) { // filled controller frame, dump + TxManage.Copy(Controller); // replicate last obtained controller data + TxManage.Send(timenow, false); + CommState.set(CommStates::SelfTx); + } + + else if( CommState.is(CommStates::HtrRpt2) ) { + // received heater frame (after our control message), report - char str[16]; - sprintf(str, "Ctrl ", lastRxTime); - Serial.print(str); // print timestamp - for(int i=0; i<24; i++) { - - sprintf(str, "%02X ", Controller.Data[i]); // make 2 dig hex values - Serial.print(str); // and print - - } -// Serial.println(); // newline and done + SerialReport("Htr2 ", Heater2.Data, "\r\n"); - Stage = 2; - count = 0; + CommState.set(CommStates::Idle); + } - } // Stage == 1 - - if(Stage == 3) { // filled heater frame, dump - - char str[16]; - sprintf(str, " Htr1 ", lastRxTime); - Serial.print(str); // print timestamp - for(int i=0; i<24; i++) { - - sprintf(str, "%02X ", Heater1.Data[i]); // make 2 dig hex values - Serial.print(str); // and print - - } - Serial.println(); // newline and done - - Stage = 4; - count = 0; - lastTx = timenow; - - } // Stage == 1 - - if(Stage == 7) { // filled heater frame, dump - - char str[16]; - sprintf(str, " Htr2 ", lastRxTime); - Serial.print(str); // print timestamp - for(int i=0; i<24; i++) { - - sprintf(str, "%02X ", Heater2.Data[i]); // make 2 dig hex values - Serial.print(str); // and print - - } - Serial.println(); // newline and done - - Stage = -1; - count = 0; - - } // Stage == 1 - } // loop - -void CheckTx() + +void SerialReport(const char* hdr, const unsigned char* pData, const char* ftr) { - char str[16]; - - if(TxEnbTime) { - long diff = timenow - TxEnbTime; - if(diff >= 12) { - TxEnbTime = 0; - digitalWrite(TxEnbPin, LOW); - Stage = 6; - } + Serial.print(hdr); // header + for(int i=0; i<24; i++) { + char str[16]; + sprintf(str, "%02X ", pData[i]); // build 2 dig hex values + Serial.print(str); // and print } - - if((Stage == 4) && (timenow - lastTx) > 10) { - - Stage = 5; - - TxFrame.Tx.Byte0 = 0x78; - TxFrame.setTemperature_Desired(35); - TxFrame.setTemperature_Actual(22); - TxFrame.Tx.OperatingVoltage = 240; - TxFrame.setPump_Min(16); - TxFrame.setPump_Max(55); - TxFrame.setFan_Min(1680); - TxFrame.setFan_Max(4500); - - if(bOnEvent) { - bOnEvent = false; - TxFrame.Tx.Command = 0xa0; - } - else if(bOffEvent) { - bOffEvent = false; - TxFrame.Tx.Command = 0x05; - } - else { - TxFrame.Tx.Command = 0x00; - } - - TxFrame.setCRC(); - - // send to serial monitor using ASCII - Serial.print("Us "); // and print ASCII data - for(int i=0; i<24; i++) { - sprintf(str, "%02X ", TxFrame.Data[i]); // make 2 dig hex ASCII values - Serial.print(str); // and print ASCII data - } - - // send to heater - using binary - digitalWrite(TxEnbPin, HIGH); - for(int i=0; i<24; i++) { - Serial1.write(TxFrame.Data[i]); // write native binary values - } - } - + Serial.print(ftr); // footer } \ No newline at end of file diff --git a/Arduino/SenderTrial2/TxManage.cpp b/Arduino/SenderTrial2/TxManage.cpp new file mode 100644 index 0000000..aec134d --- /dev/null +++ b/Arduino/SenderTrial2/TxManage.cpp @@ -0,0 +1,127 @@ +#include +#include "TxManage.h" + +extern void SerialReport(const char* hdr, const unsigned char* pData, const char* ftr); + +CTxManage::CTxManage(int TxEnbPin, USARTClass& serial) : + m_Serial(serial), + m_Frame(CFrame::TxMode) +{ + m_bOnReq = false; + m_bOffReq = false; + m_bTxPending = false; + m_bSelf = true; + m_nStartTime = 0; + m_nTxEnbPin = TxEnbPin; +} + +void CTxManage::begin() +{ + pinMode(m_nTxEnbPin, OUTPUT); + digitalWrite(m_nTxEnbPin, LOW); +} + +void +CTxManage::RequestOn() +{ + m_bOnReq = true; +} + +void +CTxManage::RequestOff() +{ + m_bOffReq = true; +} + +void +CTxManage::Copy(CFrame& ref) +{ + m_Frame = ref; +} + +bool +CTxManage::isBusy() +{ + return m_nStartTime != 0; +} + +void +CTxManage::Send(unsigned long timenow, bool self) +{ + if(timenow == 0) + timenow++; + + m_nStartTime = timenow; + m_bSelf = self; + m_bTxPending = true; +} + +void +CTxManage::Tick(unsigned long timenow) +{ + if(m_nStartTime) { + + long diff = timenow - m_nStartTime; + + if(diff > m_nStartDelay) { + // begin front porch of Tx gating pulse + digitalWrite(m_nTxEnbPin, HIGH); + } + if(m_bTxPending && (diff > (m_nStartDelay + m_nFrontPorch))) { + // begin serial transmission + m_bTxPending = false; + _send(); + } + if(diff > (m_nStartDelay + m_nFrameTime)) { + // conclude Tx gating + m_nStartTime = 0; + digitalWrite(m_nTxEnbPin, LOW); + } + } +} + +void +CTxManage::_send() +{ + if(m_bSelf) { + m_Frame.Data[0] = 0x76; // required for heater to use the max min information + m_Frame.Data[1] = 0x16; + m_Frame.setTemperature_Desired(35); + m_Frame.setTemperature_Actual(22); + m_Frame.Tx.OperatingVoltage = 120; + m_Frame.setPump_Min(16); + m_Frame.setPump_Max(55); + m_Frame.setFan_Min(1680); + m_Frame.setFan_Max(4500); + } + else { + m_Frame.Data[0] = 0x78; // this prevents the controller trying to show bum information, heater uses controller max/min settings + } + + if(m_bOnReq) { + m_bOnReq = false; + m_Frame.Tx.Command = 0xa0; + } + else if(m_bOffReq) { + m_bOffReq = false; + m_Frame.Tx.Command = 0x05; + } + else { + m_Frame.Tx.Command = 0x00; + } + + 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, " "); +} diff --git a/Arduino/SenderTrial2/TxManage.h b/Arduino/SenderTrial2/TxManage.h new file mode 100644 index 0000000..8939c3a --- /dev/null +++ b/Arduino/SenderTrial2/TxManage.h @@ -0,0 +1,33 @@ +#include +#include "CFrame.h" + +class CTxManage +{ +public: + CTxManage(int TxEnbPin, USARTClass& serial); + void RequestOn(); + void RequestOff(); + void Copy(CFrame& ref); + bool isBusy(); + void Tick(unsigned long timenow); + void Send(unsigned long timenow, bool self); + void Report(); + void begin(); + +private: + CFrame m_Frame; + bool m_bOnReq; + bool m_bOffReq; + bool m_bTxPending; + bool m_bSelf; + int m_nTxEnbPin; + unsigned long m_nStartTime; + USARTClass& m_Serial; + + const int m_nStartDelay = 20; + const int m_nFrameTime = 14; + const int m_nFrontPorch = 2; + + void _send(); +}; +