diff --git a/Arduino/SenderTrial2/.vscode/arduino.json b/Arduino/SenderTrial2/.vscode/arduino.json index 684964e..684e9bd 100644 --- a/Arduino/SenderTrial2/.vscode/arduino.json +++ b/Arduino/SenderTrial2/.vscode/arduino.json @@ -3,5 +3,5 @@ "port": "COM9", "sketch": "SenderTrial2.ino", "output": "..\\build", - "configuration": "PSRAM=disabled,PartitionScheme=default,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=warn" + "configuration": "PSRAM=disabled,PartitionScheme=default,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none" } \ No newline at end of file diff --git a/Arduino/SenderTrial2/Bluetooth.h b/Arduino/SenderTrial2/Bluetooth.h index 283581c..a43daa6 100644 --- a/Arduino/SenderTrial2/Bluetooth.h +++ b/Arduino/SenderTrial2/Bluetooth.h @@ -5,6 +5,7 @@ class CProtocol; void Bluetooth_Init(); void Bluetooth_SendFrame(const char* pHdr, const CProtocol& Frame, bool lineterm=true); void Bluetooth_Check(); +void Bluetooth_SendACK(); extern void Command_Interpret(const char* pLine); // decodes received command lines, implemented in main .ino file! diff --git a/Arduino/SenderTrial2/BluetoothESP32.cpp b/Arduino/SenderTrial2/BluetoothESP32.cpp index f0d8bed..8452215 100644 --- a/Arduino/SenderTrial2/BluetoothESP32.cpp +++ b/Arduino/SenderTrial2/BluetoothESP32.cpp @@ -5,12 +5,171 @@ #ifdef ESP32 +#define ESP32_USE_HC05 + const int LED = 2; // ESP32 sRxLine RxLine; +#ifdef ESP32_USE_HC05 + +//static HardwareSerial& Bluetooth(Serial2); // TODO: make proper ESP32 BT client + +bool Bluetooth_ATCommand(const char* cmd); + +const int BTRates[] = { + 9600, 38400, 115200, 19200, 57600, 2400, 4800, 1200 +}; + +bool bHC05Available = false; + +void Bluetooth_Init() +{ + RxLine.clear(); + + // 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. + digitalWrite(KeyPin, HIGH); + delay(5000); + Serial2.begin(9600, SERIAL_8N1, Rx2Pin, Tx2Pin); // need to explicitly specify pins for pin multiplexer!); + + 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++) { + DebugPort.print(" @ "); + DebugPort.print(BTRates[BTidx]); + DebugPort.print(" baud... "); + Serial2.begin(BTRates[BTidx], SERIAL_8N1, Rx2Pin, Tx2Pin); // open serial port at a certain baud rate + delay(100); + Serial2.print("\r\n"); + Serial2.setTimeout(100); + + if(Bluetooth_ATCommand("AT\r\n")) { + DebugPort.println(" OK."); + break; + } + if(Bluetooth_ATCommand("AT\r\n")) { + DebugPort.println(" OK."); + break; + } + // failed, try another baud rate + DebugPort.println(""); + Serial2.flush(); + Serial2.end(); + delay(1000); + } + + DebugPort.println(""); + if(BTidx == maxTries) { + DebugPort.println("FAILED to detect HC-05 Bluetooth module :-("); + } + else { +/*// if(BTRates[BTidx] == 115200) { + if(BTRates[BTidx] == 9600) { +// DebugPort.println("HC-05 found and already set to 115200 baud, skipping Init."); + DebugPort.println("HC-05 found and already set to 9600 baud, skipping Init."); + bHC05Available = true; + } + else*/ { + do { + DebugPort.println("HC-05 found"); + + DebugPort.print(" Setting Name to \"Diesel Heater\"... "); + if(!Bluetooth_ATCommand("AT+NAME=\"Diesel Heater\"\r\n")) { + DebugPort.println("FAILED"); + break; + } + DebugPort.println("OK"); + +// DebugPort.print(" Setting baud rate to 115200N81..."); +// if(!Bluetooth_ATCommand("AT+UART=115200,1,0\r\n")) { + DebugPort.print(" Setting baud rate to 9600N81..."); + if(!Bluetooth_ATCommand("AT+UART=9600,1,0\r\n")) { + DebugPort.println("FAILED"); + break; + }; + DebugPort.println("OK"); + +// Serial2.begin(115200, SERIAL_8N1, Rx2Pin, Tx2Pin); + Serial2.begin(9600, SERIAL_8N1, Rx2Pin, Tx2Pin); + bHC05Available = true; + + } while(0); + + } + } + digitalWrite(KeyPin, LOW); // leave HC-05 command mode + + delay(500); + + if(!bHC05Available) + Serial2.end(); // close serial port if no module found + + DebugPort.println(""); +} + +void Bluetooth_Check() +{ + // check for data coming back over Bluetooth + if(bHC05Available) { + if(Serial2.available()) { + char rxVal = Serial2.read(); + if(isControl(rxVal)) { // "End of Line" + Command_Interpret(RxLine.Line); + RxLine.clear(); + } + else { + RxLine.append(rxVal); // append new char to our Rx buffer + } + } + } +} + + +void Bluetooth_SendFrame(const char* pHdr, const CProtocol& Frame, bool lineterm) +{ + DebugPort.print(millis()); + DebugReportFrame(pHdr, Frame, lineterm ? "\r\n" : " "); + + if(bHC05Available) { + if(Frame.verifyCRC()) { + digitalWrite(LED, !digitalRead(LED)); // toggle LED + Serial2.print(pHdr); + Serial2.write(Frame.Data, 24); + } + else { + DebugPort.print("Bluetooth data not sent, CRC error "); + DebugPort.println(pHdr); + } + } + else { + DebugPort.println("No Bluetooth client"); + digitalWrite(LED, 0); + } +} + +// local function, typically to perform Hayes commands with HC-05 +bool Bluetooth_ATCommand(const char* cmd) +{ +// if(bHC05Available) { + Serial2.print(cmd); + char RxBuffer[16]; + memset(RxBuffer, 0, 16); + int read = Serial2.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; +} + +#else // ESP32_USE_HC05 #ifndef ESP32_USE_BLE_RLJ ///////////////////////////////////////////////////////////////////////////////////////// @@ -49,13 +208,25 @@ void Bluetooth_Check() void Bluetooth_SendFrame(const char* pHdr, const CProtocol& Frame, bool lineterm) { + char fullMsg[32]; + + DebugPort.print(millis()); DebugReportFrame(pHdr, Frame, lineterm ? "\r\n" : " "); + delay(40); if(SerialBT.hasClient()) { if(Frame.verifyCRC()) { digitalWrite(LED, !digitalRead(LED)); // toggle LED - SerialBT.print(pHdr); - SerialBT.write(Frame.Data, 24); + int len = strlen(pHdr); + if(len < 8) { + strcpy(fullMsg, pHdr); + memcpy(&fullMsg[len], Frame.Data, 24); + SerialBT.write((uint8_t*)fullMsg, 24+len); + } +/* SerialBT.print(pHdr); + delay(1); + SerialBT.write(Frame.Data, 24);*/ + delay(10); } else { DebugPort.println("Data not sent to Bluetooth, CRC error!"); @@ -67,6 +238,14 @@ void Bluetooth_SendFrame(const char* pHdr, const CProtocol& Frame, bool lineterm } } +void Bluetooth_SendACK() +{ + /* if(SerialBT.hasClient()) { + SerialBT.print("[ACK]"); + }*/ +} + + // ^ // | // CLASSIC BLUETOOTH @@ -216,4 +395,6 @@ void BLE_Send(std::string Data) #endif // ESP32_USE_BLE_RLJ -#endif // __ESP32__ \ No newline at end of file +#endif // ESP32_USE_HC05 + +#endif // __ESP32__ diff --git a/Arduino/SenderTrial2/BluetoothHC05.cpp b/Arduino/SenderTrial2/BluetoothHC05.cpp index 6d89092..1ada4e5 100644 --- a/Arduino/SenderTrial2/BluetoothHC05.cpp +++ b/Arduino/SenderTrial2/BluetoothHC05.cpp @@ -45,6 +45,7 @@ void Bluetooth_Init() DebugPort.print(" @ "); DebugPort.print(BTRates[BTidx]); DebugPort.print(" baud... "); + Bluetooth.end(); Bluetooth.begin(BTRates[BTidx]); // open serial port at a certain baud rate Bluetooth.print("\r\n"); Bluetooth.setTimeout(50); @@ -85,6 +86,7 @@ void Bluetooth_Init() }; DebugPort.println("OK"); + Bluetooth.end(); Bluetooth.begin(115200); bHC05Available = true; diff --git a/Arduino/SenderTrial2/Protocol.cpp b/Arduino/SenderTrial2/Protocol.cpp index 34ef4d7..5715f15 100644 --- a/Arduino/SenderTrial2/Protocol.cpp +++ b/Arduino/SenderTrial2/Protocol.cpp @@ -228,12 +228,12 @@ CProtocol::Init(int FrameMode) Controller.OperatingMode = 0x32; // 0x32:Thermostat, 0xCD:Fixed setTemperature_Min(8); // Minimum settable temperature setTemperature_Max(35); // Maximum settable temperature - Controller.MinTempRise = 5; // temp rise to sense fuel ignition + Controller.MinTempRise = 5; // temp rise to sense fuel ignition - GLOW PLUG POWER? Controller.Prime = 0; // 00: normal, 0x5A: fuel prime Controller.Unknown1_MSB = 0x01; // always 0x01 Controller.Unknown1_LSB = 0x2c; // always 0x2c 16bit: "300 secs = max run without burn detected" ?? - Controller.Unknown2_MSB = 0x0d; // always 0x0d - Controller.Unknown2_LSB = 0xac; // always 0xac 16bit: "3500" ?? + Controller.Unknown2_MSB = 0x07; // always 0x0d + Controller.Unknown2_LSB = 0xac; // always 0xac 16bit: "3500" ?? Ignition fan max RPM???? setCRC(); } else if(FrameMode == HeatMode){ diff --git a/Arduino/SenderTrial2/SenderTrial2.ino b/Arduino/SenderTrial2/SenderTrial2.ino index 89f29ec..6522a40 100644 --- a/Arduino/SenderTrial2/SenderTrial2.ino +++ b/Arduino/SenderTrial2/SenderTrial2.ino @@ -88,7 +88,7 @@ class CommStates { public: // comms states enum eCS { - Idle, ControllerRx, ControllerReport, HeaterRx1, HeaterReport1, BTC_Tx, HeaterRx2, HeaterReport2 + Idle, OEMCtrlRx, OEMCtrlReport, HeaterRx1, HeaterReport1, BTC_Tx, HeaterRx2, HeaterReport2 }; CommStates() { set(Idle); @@ -100,8 +100,8 @@ class CommStates { 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; + bool collectData(CProtocol& Frame, unsigned char val, int limit = 24) { // returns true when buffer filled + Frame.Data[m_Count++] = val; return m_Count == limit; } private: @@ -112,11 +112,12 @@ private: CommStates CommState; CTxManage TxManage(TxEnbPin, BlueWire); -CProtocol OEMControllerFrame; // data packet received from heater in response to OEM controller packet +CProtocol OEMCtrlFrame; // 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 +int hasController = 0; // setup Non Volatile storage // this is very much hardware dependent, we can use the ESP32's FLASH @@ -127,12 +128,17 @@ CHeaterStorage NVStorage; // dummy, for now #endif CHeaterStorage* pNVStorage = NULL; +void PrepareTxFrame(const CProtocol& basisFrame, CProtocol& TxFrame, bool isBTCmaster); + void setup() { // 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, // Note special baud rate for Chinese heater controllers + pinMode(Tx2Pin, OUTPUT); + digitalWrite(Tx2Pin, HIGH); + pinMode(Rx2Pin, INPUT_PULLUP); pinMode(ListenOnlyPin, INPUT_PULLUP); pinMode(KeyPin, OUTPUT); digitalWrite(KeyPin, LOW); @@ -217,18 +223,22 @@ void loop() // have not seen any receive data for a second. // OEM controller is probably not connected. // Skip state machine immediately to BTC_Tx, sending our own settings. + hasController = 0; CommState.set(CommStates::BTC_Tx); bool isBTCmaster = true; TxManage.PrepareFrame(DefaultBTCParams, isBTCmaster); // use our parameters, and mix in NV storage values TxManage.Start(timenow); #ifdef BLUETOOTH - Bluetooth_SendFrame("[BTC]", TxManage.getFrame(), false); // BTC => Bluetooth Controller :-) +// Bluetooth_SendFrame("[BTC]", TxManage.getFrame(), false); // BTC => Bluetooth Controller :-) +// Bluetooth_SendFrame("[BTC]", TxManage.getFrame(), true); // BTC => Bluetooth Controller :-) +#else + DebugReportFrame("BTC ", OEMCtrlFrame, " "); #endif } // precautionary action if all 24 bytes were not received whilst expecting them if(RxTimeElapsed > 50) { - if( CommState.is(CommStates::ControllerRx) || + if( CommState.is(CommStates::OEMCtrlRx) || CommState.is(CommStates::HeaterRx1) || CommState.is(CommStates::HeaterRx2) ) { @@ -236,32 +246,47 @@ void loop() } } - // read data from Serial port 1, the "blue wire" (to/from heater), store according to CommState + ////////////////////////////////////////////////////////////////////////////////////// + // Blue wire data reception + // Reads data from the "blue wire" Serial port, (to/from heater) + // If an OEM controller exists we will also see it's data frames + // if (BlueWire.available()) { + + // Data is avaialable, read and store according to CommState + // if not in a recognised data frame state, the data is deliberately lost lastRxTime = timenow; - // detect start of a new frame sequence from OEM controller - if( CommState.is(CommStates::Idle) && (RxTimeElapsed > 100)) { - CommState.set(CommStates::ControllerRx); + // Detect the start of a new frame sequence from an OEM controller + // This when there has been no activity for a while on the blue wire + // the heater always responds to a controller frame, but not otherwise + if( CommState.is(CommStates::Idle) && (RxTimeElapsed > 100)) { + DebugPort.print(RxTimeElapsed); + DebugPort.println(" OEM Controller re-sync"); + hasController = 1; + CommState.set(CommStates::OEMCtrlRx); } int inByte = BlueWire.read(); // read hex byte - if( CommState.is(CommStates::ControllerRx) ) { - if(CommState.saveData(OEMControllerFrame.Data, inByte) ) { - CommState.set(CommStates::ControllerReport); + // collect OEM controller frame + if( CommState.is(CommStates::OEMCtrlRx) ) { + if(CommState.collectData(OEMCtrlFrame, inByte) ) { + CommState.set(CommStates::OEMCtrlReport); } } + // collect heater frame, in response to an OEM controller else if( CommState.is(CommStates::HeaterRx1) ) { - if( CommState.saveData(HeaterFrame1.Data, inByte) ) { + if( CommState.collectData(HeaterFrame1, inByte) ) { CommState.set(CommStates::HeaterReport1); } } + // collect heater frame, in response to our control frame else if( CommState.is(CommStates::HeaterRx2) ) { - if( CommState.saveData(HeaterFrame2.Data, inByte) ) { + if( CommState.collectData(HeaterFrame2, inByte) ) { CommState.set(CommStates::HeaterReport2); } } @@ -269,13 +294,13 @@ void loop() } // BlueWire.available - if( CommState.is(CommStates::ControllerReport) ) { + if( CommState.is(CommStates::OEMCtrlReport) ) { // filled controller frame, report #ifdef BLUETOOTH // echo received OEM controller frame over Bluetooth, using [OEM] header - Bluetooth_SendFrame("[OEM]", OEMControllerFrame, false); + Bluetooth_SendFrame("[OEM]", OEMCtrlFrame, true); #else - DebugReportFrame("OEM ", OEMControllerFrame, " "); + DebugReportFrame("OEM ", OEMCtrlFrame, " "); #endif CommState.set(CommStates::HeaterRx1); } @@ -291,7 +316,7 @@ void loop() if(digitalRead(ListenOnlyPin)) { bool isBTCmaster = false; - TxManage.PrepareFrame(OEMControllerFrame, isBTCmaster); // parrot OEM parameters, but block NV modes + TxManage.PrepareFrame(OEMCtrlFrame, isBTCmaster); // parrot OEM parameters, but block NV modes TxManage.Start(timenow); CommState.set(CommStates::BTC_Tx); } @@ -302,14 +327,21 @@ void loop() else if( CommState.is(CommStates::HeaterReport2) ) { // received heater frame (after our control message), report + delay(5); #ifdef BLUETOOTH - Bluetooth_SendFrame("[HTR]", HeaterFrame2); // pin not grounded, suppress duplicate to BT + if(!hasController) { + Bluetooth_SendFrame("[BTC]", TxManage.getFrame(), true); // BTC => Bluetooth Controller :-) + Bluetooth_SendFrame("[HTR]", HeaterFrame2); // pin not grounded, suppress duplicate to BT + } #else DebugReportFrame("Htr2 ", HeaterFrame2, "\r\n"); #endif // if(!digitalRead(ListenOnlyPin)) { // } CommState.set(CommStates::Idle); + + Serial.print("Free heap "); + Serial.println(ESP.getFreeHeap()); } } // loop @@ -325,6 +357,7 @@ void DebugReportFrame(const char* hdr, const CProtocol& Frame, const char* ftr) DebugPort.print(ftr); // footer } + void Command_Interpret(const char* pLine) { unsigned char cVal; @@ -393,7 +426,7 @@ void Command_Interpret(const char* pLine) pLine += 4; cVal = !pNVStorage->getThermostatMode(); pNVStorage->setThermostatMode(cVal); - DebugPort.print("Mode = "); + DebugPort.print("Mode now "); DebugPort.println(cVal ? "Thermostat" : "Fixed Hz"); } else { diff --git a/Arduino/SenderTrial2/TxManage.cpp b/Arduino/SenderTrial2/TxManage.cpp index 702f2bf..07a1008 100644 --- a/Arduino/SenderTrial2/TxManage.cpp +++ b/Arduino/SenderTrial2/TxManage.cpp @@ -3,6 +3,26 @@ extern void DebugReportFrame(const char* hdr, const CProtocol&, const char* ftr); +// CTxManage is used to send a data frame to the blue wire +// +// As the blue wire is bidirectional, we need to only allow our transmit data +// to reach the blue wire when we actually want to send data. +// At all other times we are listening to the blue wire, receiving any async data +// +// This requires external circuitry to toggle the Tx/Rx modes. +// A "Tx Gating" signal is used. +// when high, transmit data is sent to the blue wire +// when low, transmit data is blocked (Hi-Z) +// +// Ideally the circuit also prevents feeding back our own Tx data into the Rx pin +// but the main software loop handles this situation by only accepting Rx data when expected. +// +// Timing diagram +// ____________________ +// Tx Gate ____________________| |___________________________ +// _____________________________________________________________________ +// Tx Data ||||||||||||||| + CTxManage::CTxManage(int TxGatePin, HardwareSerial& serial) : m_BlueWireSerial(serial), m_TxFrame(CProtocol::CtrlMode) @@ -75,7 +95,7 @@ CTxManage::PrepareFrame(const CProtocol& basisFrame, bool isBTCmaster) void CTxManage::Start(unsigned long timenow) { - if(timenow == 0) // avoid a black hole if millis() wraps + if(timenow == 0) // avoid a black hole if millis() has wrapped to zero timenow++; m_nStartTime = timenow; @@ -97,17 +117,17 @@ CTxManage::CheckTx(unsigned long timenow) digitalWrite(m_nTxGatePin, HIGH); } if(m_bTxPending && (diff > (m_nStartDelay + m_nFrontPorch))) { - // begin serial transmission + // front porch expired, perform serial transmission + // Tx gate remains held high m_bTxPending = false; 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; + // conclude Tx gating after (emperical) delay digitalWrite(m_nTxGatePin, LOW); + m_nStartTime = 0; // cancel, we are DONE } } - return m_nStartTime == 0; + return m_nStartTime == 0; // returns true when done }