Switched to HC-05.

quick push for James
This commit is contained in:
rljonesau 2018-10-27 17:35:17 +11:00
parent a156062a94
commit 7be3424359
7 changed files with 271 additions and 34 deletions

View file

@ -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"
}

View file

@ -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!

View file

@ -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__
#endif // ESP32_USE_HC05
#endif // __ESP32__

View file

@ -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;

View file

@ -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){

View file

@ -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 {

View file

@ -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
}