Merge branch 'ESP32Bluetooth' into 'master'

Esp32 bluetooth

See merge request mrjones.id.au/bluetoothheater!2
This commit is contained in:
Ray Jones 2018-10-20 07:12:57 +00:00
commit 8fd8a314dd
15 changed files with 882 additions and 319 deletions

Binary file not shown.

View file

@ -3,6 +3,8 @@
{
"name": "Win32",
"includePath": [
"C:\\Program Files (x86)\\Arduino\\tools\\**",
"C:\\Program Files (x86)\\Arduino\\hardware\\arduino\\avr\\**",
"C:\\Users\\ray\\AppData\\Local\\Arduino15\\packages\\esp32\\tools\\**",
"C:\\Users\\ray\\AppData\\Local\\Arduino15\\packages\\esp32\\hardware\\esp32\\1.0.0\\**",
"C:\\Users\\ray\\AppData\\Local\\Arduino15\\packages\\arduino\\tools\\**",

View file

@ -2,5 +2,15 @@
"sketch": "SenderTrial2.ino",
"port": "COM4",
"board": "arduino:sam:arduino_due_x_dbg",
"output": "c:\\Users\\ray\\AppData\\Local\\Arduino\\"
"output": "c:\\Users\\ray\\AppData\\Local\\Arduino\\",
"files.associations": {
"list": "cpp",
"vector": "cpp",
"xhash": "cpp",
"xstring": "cpp",
"xtree": "cpp",
"algorithm": "cpp",
"initializer_list": "cpp",
"xutility": "cpp"
}
}

View file

@ -0,0 +1,12 @@
#include <Arduino.h>
class CProtocol;
void Bluetooth_Init();
void Bluetooth_SendFrame(const char* pHdr, const CProtocol& Frame);
void Bluetooth_Check();
extern void Command_Interpret(String line); // decodes received command lines, implemented in main .ino file!

View file

@ -0,0 +1,218 @@
#include "Bluetooth.h"
#include "pins.h"
#include "Protocol.h"
#include "debugport.h"
#ifdef ESP32
const int LED = 2;
// ESP32
String BluetoothRxLine;
#ifndef ESP32_USE_BLE_RLJ
/////////////////////////////////////////////////////////////////////////////////////////
// CLASSIC BLUETOOTH
// |
// V
#include "BluetoothSerial.h"
BluetoothSerial SerialBT;
void Bluetooth_Init()
{
pinMode(LED, OUTPUT);
if(!SerialBT.begin("ESPHEATER")) {
DebugPort.println("An error occurred initialising Bluetooth");
}
}
void Bluetooth_Check()
{
if(SerialBT.available()) {
char rxVal = SerialBT.read();
if(isControl(rxVal)) { // "End of Line"
BluetoothRxLine += '\0';
Command_Interpret(BluetoothRxLine);
BluetoothRxLine = "";
}
else {
BluetoothRxLine += rxVal; // append new char to our Rx buffer
}
}
}
void Bluetooth_SendFrame(const char* pHdr, const CProtocol& Frame)
{
if(SerialBT.hasClient()) {
if(Frame.verifyCRC()) {
digitalWrite(LED, !digitalRead(LED)); // toggle LED
SerialBT.print(pHdr);
SerialBT.write(Frame.Data, 24);
}
else {
DebugPort.print("Bluetooth data not sent, CRC error ");
DebugPort.println(pHdr);
}
}
else {
digitalWrite(LED, 0);
}
}
// ^
// |
// CLASSIC BLUETOOTH
/////////////////////////////////////////////////////////////////////////////////////////
#else // ESP32_USE_BLE_RLJ
/////////////////////////////////////////////////////////////////////////////////////////
// BLE
// |
// V
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
void BLE_Send(std::string Data);
BLEServer *pServer = NULL;
BLECharacteristic* pTxCharacteristic = NULL;
volatile bool deviceConnected = false;
bool oldDeviceConnected = false;
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
}
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
}
};
class MyCallbacks : public BLECharacteristicCallbacks {
// this callback is called when the ESP WRITE characteristic has been written to by a client
// We need to *read* the new information!
void onWrite(BLECharacteristic* pCharacteristic) {
std::string rxValue = pCharacteristic->getValue();
while(rxValue.length() > 0) {
char rxVal = rxValue[0];
if(isControl(rxVal)) { // "End of Line"
Command_Interpret(BluetoothRxLine);
BluetoothRxLine = "";
}
else {
BluetoothRxLine += rxVal; // append new char to our Rx buffer
}
rxValue.erase(0, 1);
}
}
};
void Bluetooth_Init()
{
// create the BLE device
BLEDevice::init("DieselHeater");
// create the BLE server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks);
// create the BLE service
BLEService *pService = pServer->createService(SERVICE_UUID);
// create a BLE characteristic
pTxCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID_TX,
BLECharacteristic::PROPERTY_NOTIFY
);
pTxCharacteristic->addDescriptor(new BLE2902());
BLECharacteristic* pRxCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID_RX,
BLECharacteristic::PROPERTY_WRITE
);
pRxCharacteristic->setCallbacks(new MyCallbacks/*()*/);
// start the service
pService->start();
// start advertising
pServer->getAdvertising()->start();
DebugPort.println("Awaiting a client to notify...");
}
void Bluetooth_Report(const char* pHdr, const CProtocol& Frame)
{
if(deviceConnected) {
if(Frame.verifyCRC()) {
// BLE can only squirt 20 bytes per packet.
// build the entire message then divide and conquer
std::string txData = pHdr;
txData.append((char*)Frame.Data, 24);
BLE_Send(txData);
}
}
}
void Bluetooth_Check()
{
// disconnecting
if (!deviceConnected && oldDeviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
DebugPort.println("start advertising");
oldDeviceConnected = deviceConnected;
}
// connecting
if (deviceConnected && !oldDeviceConnected) {
// do stuff here on connecting
oldDeviceConnected = deviceConnected;
}
}
// break down supplied string into 20 byte chunks (or less)
// BLE can only handle 20 bytes per packet!
void BLE_Send(std::string Data)
{
while(!Data.empty()) {
std::string substr = Data.substr(0, 20);
int len = substr.length();
pTxCharacteristic->setValue((uint8_t*)Data.data(), len);
pTxCharacteristic->notify();
Data.erase(0, len);
}
}
// ^
// |
// BLE
/////////////////////////////////////////////////////////////////////////////////////////
#endif // ESP32_USE_BLE_RLJ
#endif // __ESP32__

View file

@ -0,0 +1,155 @@
#include "Bluetooth.h"
#include "pins.h"
#include "Protocol.h"
#include "debugport.h"
// Bluetooth access via HC-05 Module, using a UART
#ifndef ESP32
// NOTE: ESP32 uses an entirely different mechanism, please refer to BluetoothESP32.cpp/.h
#ifdef __arm__
// for Arduino Due
static UARTClass& Bluetooth(Serial2);
#else
// for Mega
static HardwareSerial& Bluetooth(Serial2); // TODO: make proper ESP32 BT client
#endif
bool Bluetooth_ATCommand(const char* cmd);
String BluetoothRxData;
const int BTRates[] = {
9600, 38400, 115200, 19200, 57600, 2400, 4800
};
bool bHC05Available = false;
void Bluetooth_Init()
{
// 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);
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... ");
Bluetooth.begin(BTRates[BTidx]); // open serial port at a certain baud rate
Bluetooth.print("\r\n");
Bluetooth.setTimeout(50);
if(Bluetooth_ATCommand("AT\r\n")) {
DebugPort.println(" OK.");
break;
}
// failed, try another baud rate
DebugPort.println("");
Bluetooth.flush();
}
DebugPort.println("");
if(BTidx == maxTries) {
DebugPort.println("FAILED to detect HC-05 Bluetooth module :-(");
}
else {
if(BTRates[BTidx] == 115200) {
DebugPort.println("HC-05 found and already set to 115200 baud, skipping Init.");
bHC05Available = true;
}
else {
do {
DebugPort.println("HC-05 found");
DebugPort.print(" Setting Name to \"DieselHeater\"... ");
if(!Bluetooth_ATCommand("AT+NAME=\"DieselHeater\"\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.println("FAILED");
break;
};
DebugPort.println("OK");
Bluetooth.begin(115200);
bHC05Available = true;
} while(0);
}
}
digitalWrite(KeyPin, LOW); // leave HC-05 command mode
delay(500);
if(!bHC05Available)
Bluetooth.end(); // close serial port if no module found
DebugPort.println("");
}
void Bluetooth_Check()
{
// check for data coming back over Bluetooth
if(bHC05Available) {
if(Bluetooth.available()) {
char rxVal = Bluetooth.read();
if(isControl(rxVal)) { // "End of Line"
BluetoothRxData += '\0';
Command_Interpret(BluetoothRxData);
BluetoothRxData = "";
}
else {
BluetoothRxData += rxVal; // append new char to our Rx buffer
}
}
}
}
void Bluetooth_SendFrame(const char* pHdr, const CProtocol& Frame)
{
if(bHC05Available) {
if(Frame.verifyCRC()) {
Bluetooth.print(pHdr);
Bluetooth.write(Frame.Data, 24);
}
else {
DebugPort.print("Bluetooth data not sent, CRC error ");
DebugPort.println(pHdr);
}
}
}
// local function, typically to perform Hayes commands with HC-05
bool Bluetooth_ATCommand(const char* cmd)
{
if(bHC05Available) {
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;
}
#endif

View file

@ -0,0 +1,118 @@
#include "Arduino.h"
#include "NVStorage.h"
CHeaterStorage::CHeaterStorage()
{
calValues.Pmin = 14;
calValues.Pmax = 40;
calValues.Fmin = 1500;
calValues.Fmax = 4500;
calValues.ThermostatMode = 1;
calValues.setTemperature = 22;
}
unsigned char
CHeaterStorage::getPmin()
{
return calValues.Pmin;
}
unsigned char
CHeaterStorage::getPmax()
{
return calValues.Pmax;
}
unsigned short
CHeaterStorage::getFmin()
{
return calValues.Fmin;
}
unsigned short
CHeaterStorage::getFmax()
{
return calValues.Fmax;
}
unsigned char
CHeaterStorage::getTemperature()
{
return calValues.setTemperature;
}
unsigned char
CHeaterStorage::getThermostatMode()
{
return calValues.ThermostatMode;
}
void
CHeaterStorage::setPmin(unsigned char val)
{
calValues.Pmin = val;
}
void
CHeaterStorage::setPmax(unsigned char val)
{
calValues.Pmax = val;
}
void
CHeaterStorage::setFmin(unsigned short val)
{
calValues.Fmin = val;
}
void
CHeaterStorage::setFmax(unsigned short val)
{
calValues.Fmax = val;
}
void
CHeaterStorage::setTemperature(unsigned char val)
{
calValues.setTemperature = val;
}
void
CHeaterStorage::setThermostatMode(unsigned char val)
{
calValues.ThermostatMode = val;
}
///////////////////////////////////////////////////////////////////////////////////////
// ESP32
//
#ifdef ESP32
CESP32HeaterStorage::CESP32HeaterStorage()
{
}
CESP32HeaterStorage::~CESP32HeaterStorage()
{
preferences.end();
}
void
CESP32HeaterStorage::init()
{
preferences.begin("dieselheater", false);
}
void CESP32HeaterStorage::load()
{
preferences.getBytes("calValues", &calValues, sizeof(sNVStore));
}
void CESP32HeaterStorage::save()
{
preferences.putBytes("calValues", &calValues, sizeof(sNVStore));
}
#endif // ESP32

View file

@ -0,0 +1,69 @@
struct sNVStore {
unsigned char Pmin;
unsigned char Pmax;
unsigned short Fmin;
unsigned short Fmax;
unsigned char ThermostatMode;
unsigned char setTemperature;
};
class CNVStorage {
public:
CNVStorage() {};
virtual ~CNVStorage() {};
virtual void init() = 0;
virtual void load() = 0;
virtual void save() = 0;
};
class CHeaterStorage : public CNVStorage {
protected:
sNVStore calValues;
public:
CHeaterStorage();
virtual ~CHeaterStorage() {};
// TODO: These are only here to allow building without fully
// fleshing out NV storage for Due, Mega etc
// these should be removed once complete (pure virtual)
void init() {};
void load() {};
void save() {};
unsigned char getPmin();
unsigned char getPmax();
unsigned short getFmin();
unsigned short getFmax();
unsigned char getTemperature();
unsigned char getThermostatMode();
void setPmin(unsigned char val);
void setPmax(unsigned char val);
void setFmin(unsigned short val);
void setFmax(unsigned short val);
void setTemperature(unsigned char val);
void setThermostatMode(unsigned char val);
};
#ifdef ESP32
#include <Preferences.h>
class CESP32HeaterStorage : public CHeaterStorage {
Preferences preferences;
public:
CESP32HeaterStorage();
virtual ~CESP32HeaterStorage();
void init();
void load();
void save();
};
#endif
extern CHeaterStorage* pNVStorage;

View file

@ -1,14 +1,15 @@
#include <Arduino.h>
#include "Protocol.h"
#include "debugport.h"
unsigned short
CProtocol::CalcCRC(int len)
CProtocol::CalcCRC(int len) const
{
// calculate a CRC-16/MODBUS checksum using the first 22 bytes of the data array
unsigned short wCRCWord = 0xFFFF;
int wLength = len;
unsigned char* pData = Data;
const unsigned char* pData = Data;
while (wLength--)
{
unsigned char nTemp = *pData++ ^ wCRCWord;
@ -34,20 +35,29 @@ CProtocol::setCRC(unsigned short CRC)
unsigned short
CProtocol::getCRC()
CProtocol::getCRC() const
{
unsigned short CRC;
CRC = Data[22]; // MSB of CRC in Data[22]
CRC <<= 8;
CRC |= Data[23]; // LSB of CRC in Data[23]
return CRC;
}
// return true for CRC match
bool
CProtocol::verifyCRC()
CProtocol::verifyCRC() const
{
unsigned short CRC = CalcCRC(22); // calculate CRC based on first 22 bytes
return (getCRC() == CRC); // does it match the stored values?
unsigned short FrameCRC = getCRC();
bool bOK = (FrameCRC == CRC);
if(!bOK) {
DebugPort.print("verifyCRC FAILED: calc:");
DebugPort.print(CRC, HEX);
DebugPort.print(" data:");
DebugPort.println(FrameCRC, HEX);
}
return bOK; // does it match the stored values?
}
CProtocol&
@ -57,21 +67,6 @@ CProtocol::operator=(const CProtocol& rhs)
return *this;
}
int
CProtocol::getCommand()
{
return Controller.Command;
}
void
CProtocol::setCommand(int cmd)
{
Controller.Command = cmd;
}
/*unsigned char getCommand();
void setCommand(int mode);*/
void
CProtocol::setFan_Min(short Speed)

View file

@ -98,16 +98,24 @@ public:
public:
CProtocol() { Init(0); };
CProtocol(int TxMode) { Init(TxMode); };
void Init(int Txmode);
// CRC handlers
unsigned short CalcCRC(int len); // calculate and set the CRC upon len bytes
void setCRC(); // calculate and set the CRC in the buffer
unsigned short CalcCRC(int len) const; // calculate the CRC upon len 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
bool verifyCRC(); // return true for CRC match
// command
int getCommand();
void setCommand(int mode);
unsigned short getCRC() const; // extract CRC value from buffer
bool verifyCRC() const; // return true for CRC match
void setActiveMode() { Controller.Byte0 = 0x76; }; // this allows heater to save tuning params to EEPROM
void setPassiveMode() { Controller.Byte0 = 0x78; }; // this prevents heater saving tuning params to EEPROM
// command helpers
void resetCommand() { setRawCommand(0x00); };
void onCommand() { setRawCommand(0xA0); };
void offCommand() { setRawCommand(0x05); };
// raw command
int getRawCommand() { return Controller.Command; };
void setRawCommand(int mode) { Controller.Command = mode; };
// Run state
unsigned char getRunState() { return Heater.RunState; };
void setRunState(unsigned char state) { Heater.RunState = state; };
@ -144,10 +152,13 @@ public:
unsigned char getTemperature_Desired() { return Controller.DesiredTemperature; };
unsigned char getTemperature_Min() { return Controller.MinTemperature; };
unsigned char getTemperature_Max() { return Controller.MaxTemperature; };
void setThermostatMode(unsigned on) { Controller.OperatingMode = on ? 0x32 : 0xCD; };
// glow plug
short getGlowPlug_Current(); // glow plug current
short getGlowPlug_Voltage(); // glow plug voltage
void setGlowPlug_Current(short ampsx100); // glow plug current
void setGlowPlug_Voltage(short voltsx10); // glow plug voltage
// heat exchanger
short getTemperature_HeatExchg(); // temperature of heat exchanger
void setTemperature_HeatExchg(short degC); // temperature of heat exchanger

View file

@ -2,7 +2,7 @@
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.
Sends and receives data from hardware serial port 1.
Terminology: Tx is to the heater unit, Rx is from the heater unit.
@ -12,7 +12,7 @@
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
If Pin 21 is grounded on the Due, this simple stream will be reported over Serial and
no control from the Arduino will be allowed.
This allows sniffing of the blue wire in a normal system.
@ -20,11 +20,11 @@
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.
data array. These bytes are then reported over Serial 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.
Once again these bytes are then reported over Serial 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.
@ -64,18 +64,33 @@
#include "Protocol.h"
#include "TxManage.h"
#include "pins.h"
#include "NVStorage.h"
#include "debugport.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();
#define BLUETOOTH
#define DEBUG_BTRX
#ifdef BLUETOOTH
#include "Bluetooth.h"
#endif
#if defined(__arm__)
// Required for Arduino Due, UARTclass is derived from HardwareSerial
static UARTClass& BlueWire(Serial1);
#else
// for ESP32, Mega
// HardwareSerial is it for these boards
static HardwareSerial& BlueWire(Serial1);
#endif
void DebugReportFrame(const char* hdr, const CProtocol& Frame, const char* ftr);
class CommStates {
public:
// comms states
enum eCS {
Idle, ControllerRx, ControllerReport, HeaterRx1, HeaterReport1, SelfTx, HeaterRx2, HeaterReport2
Idle, ControllerRx, ControllerReport, HeaterRx1, HeaterReport1, BTC_Tx, HeaterRx2, HeaterReport2
};
CommStates() {
set(Idle);
@ -96,123 +111,100 @@ private:
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
CTxManage TxManage(TxEnbPin, BlueWire);
CProtocol OEMControllerFrame; // 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
bool bBlueToothAvailable = false;
String BluetoothRxData;
// setup Non Volatile storage
// this is very much hardware dependent, we can use the ESP32's FLASH
#ifdef ESP32
CESP32HeaterStorage NVStorage;
#else
CHeaterStorage NVStorage; // dummy, for now
#endif
CHeaterStorage* pNVStorage = NULL;
void setup()
{
// initialize listening serial port
// 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, special baud rate for Chinese heater controllers
// Tx/Rx data to/from heater,
// Note 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__)
pinMode(Rx1Pin, INPUT_PULLUP); // required for MUX to work properly
#elif ESP32
// ESP32
#define RXD2 16
#define TXD2 17
BlueWire.begin(25000, SERIAL_8N1, Rx1Pin, Tx1Pin);
BlueWire.begin(25000, SERIAL_8N1, Rx1Pin, Tx1Pin); // need to explicitly specify pins for pin multiplexer!
pinMode(Rx1Pin, INPUT_PULLUP); // required for MUX to work properly
#endif
// initialise serial monitor on serial port 0
USB.begin(115200);
// this is the usual USB connection to a PC
DebugPort.begin(115200);
// prepare for first long delay detection
lastRxTime = millis();
TxManage.begin(); // ensure Tx enable pin setup
TxManage.begin(); // ensure Tx enable pin is 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);
DefaultBTCParams.setTemperature_Desired(23);
DefaultBTCParams.setTemperature_Actual(22);
DefaultBTCParams.Controller.OperatingVoltage = 120;
DefaultBTCParams.setPump_Min(16);
DefaultBTCParams.setPump_Max(55);
DefaultBTCParams.setFan_Min(1680);
DefaultBTCParams.setFan_Max(4500);
BluetoothDetect();
#ifdef BLUETOOTH
Bluetooth_Init();
#endif
// create pointer to CHeaterStorage
// via the magic of polymorphism we can use this to access whatever
// storage is required for a specifc platform in a uniform way
pNVStorage = &NVStorage;
pNVStorage->init();
pNVStorage->load();
}
// main functional loop is based about a state machine approach, waiting for data
// to appear upon the blue wire, and marshalling into an appropriate receive buffer
// according to the state.
void loop()
{
unsigned long timenow = millis();
// check for test commands received from PC Over USB
if(USB.available()) {
char rxval = USB.read();
if(DebugPort.available()) {
char rxval = DebugPort.read();
if(rxval == '+') {
TxManage.RequestOn();
TxManage.queueOnRequest();
}
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
TxManage.queueOffRequest();
}
}
#ifdef BLUETOOTH
Bluetooth_Check(); // check for Bluetooth activity
#endif
// Handle time interval where we send data to the blue wire
if(CommState.is(CommStates::SelfTx)) {
if(CommState.is(CommStates::BTC_Tx)) {
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
@ -226,25 +218,28 @@ void loop()
// 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 :-)
// OEM controller is probably not connected.
// Skip state machine immediately to BTC_Tx, sending our own settings.
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()); // BTC => Bluetooth Controller :-)
#endif
}
// 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::HeaterRx1) ||
CommState.is(CommStates::HeaterRx2) ) {
CommState.set(CommStates::Idle);
}
}
// read from port 1, the "blue wire" (to/from heater), store according to CommState
// read data from Serial port 1, the "blue wire" (to/from heater), store according to CommState
if (BlueWire.available()) {
lastRxTime = timenow;
@ -257,19 +252,19 @@ void loop()
int inByte = BlueWire.read(); // read hex byte
if( CommState.is(CommStates::ControllerRx) ) {
if(CommState.saveData(Controller.Data, inByte) ) {
if(CommState.saveData(OEMControllerFrame.Data, inByte) ) {
CommState.set(CommStates::ControllerReport);
}
}
else if( CommState.is(CommStates::HeaterRx1) ) {
if( CommState.saveData(Heater1.Data, inByte) ) {
if( CommState.saveData(HeaterFrame1.Data, inByte) ) {
CommState.set(CommStates::HeaterReport1);
}
}
else if( CommState.is(CommStates::HeaterRx2) ) {
if( CommState.saveData(Heater2.Data, inByte) ) {
if( CommState.saveData(HeaterFrame2.Data, inByte) ) {
CommState.set(CommStates::HeaterReport2);
}
}
@ -279,174 +274,127 @@ void loop()
if( CommState.is(CommStates::ControllerReport) ) {
// filled controller frame, report
BlueToothReport("[OEM]", Controller.Data);
SerialReport("Ctrl ", Controller.Data, " ");
#ifdef BLUETOOTH
// echo received OEM controller frame over Bluetooth, using [OEM] header
Bluetooth_SendFrame("[OEM]", OEMControllerFrame);
#endif
DebugReportFrame("OEM ", OEMControllerFrame, " ");
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);
DebugReportFrame("Htr1 ", HeaterFrame1, "\r\n");
#ifdef BLUETOOTH
// echo heater reponse data to Bluetooth client
Bluetooth_SendFrame("[HTR]", HeaterFrame1);
#endif
if(digitalRead(ListenOnlyPin)) {
bool bOurParams = false;
TxManage.Start(Controller, timenow, bOurParams);
CommState.set(CommStates::SelfTx);
bool isBTCmaster = false;
TxManage.PrepareFrame(OEMControllerFrame, isBTCmaster); // parrot OEM parameters, but block NV modes
TxManage.Start(timenow);
CommState.set(CommStates::BTC_Tx);
}
else {
CommState.set(CommStates::Idle); // "Listen Only" input held low, don't send out Tx
CommState.set(CommStates::Idle); // "Listen Only" input is 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");
DebugReportFrame("Htr2 ", HeaterFrame2, "\r\n");
// if(!digitalRead(ListenOnlyPin)) {
BlueToothReport("[HTR]", Heater2.Data); // pin not grounded, suppress duplicate to BT
#ifdef BLUETOOTH
Bluetooth_SendFrame("[HTR]", HeaterFrame2); // pin not grounded, suppress duplicate to BT
#endif
// }
CommState.set(CommStates::Idle);
}
} // loop
void SerialReport(const char* hdr, const unsigned char* pData, const char* ftr)
void DebugReportFrame(const char* hdr, const CProtocol& Frame, const char* ftr)
{
USB.print(hdr); // header
DebugPort.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
sprintf(str, "%02X ", Frame.Data[i]); // build 2 dig hex values
DebugPort.print(str); // and print
}
USB.print(ftr); // footer
DebugPort.print(ftr); // footer
}
void BluetoothDetect()
void Command_Interpret(String line)
{
#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);
unsigned char cVal;
unsigned short sVal;
#ifdef DEBUG_BTRX
DebugPort.println(line);
DebugPort.println();
#endif
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: ");
if(line.startsWith("[CMD]") ) {
DebugPort.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();
line.remove(0, 5); // strip away "[CMD]" header
if(line.startsWith("ON") ) {
DebugPort.write("ON\n");
TxManage.queueOnRequest();
}
else if(BluetoothRxData.startsWith("OFF")) {
USB.write("OFF\n");
TxManage.RequestOff();
else if(line.startsWith("OFF")) {
DebugPort.write("OFF\n");
TxManage.queueOffRequest();
}
else if(BluetoothRxData.startsWith("Pmin")) {
USB.write("Pmin\n");
else if(line.startsWith("Pmin")) {
line.remove(0, 4);
DebugPort.write("Pmin=");
cVal = (line.toFloat() * 10) + 0.5;
DebugPort.println(cVal);
pNVStorage->setPmin(cVal);
}
else if(BluetoothRxData.startsWith("Pmax")) {
USB.write("Pmax\n");
else if(line.startsWith("Pmax")) {
line.remove(0, 4);
DebugPort.write("Pmax=");
cVal = (line.toFloat() * 10) + 0.5;
DebugPort.println(cVal);
pNVStorage->setPmax(cVal);
}
else if(BluetoothRxData.startsWith("Fmin")) {
USB.write("Fmin\n");
else if(line.startsWith("Fmin")) {
line.remove(0, 4);
DebugPort.print("Fmin=");
sVal = line.toInt();
DebugPort.println(sVal);
pNVStorage->setFmin(sVal);
}
else if(BluetoothRxData.startsWith("Fmax")) {
USB.write("Fmax\n");
else if(line.startsWith("Fmax")) {
line.remove(0, 4);
DebugPort.print("Fmax=");
sVal = line.toInt();
DebugPort.println(sVal);
pNVStorage->setFmax(sVal);
}
else if(line.startsWith("save")) {
line.remove(0, 4);
DebugPort.write("save\n");
pNVStorage->save();
}
else if(line.startsWith("degC")) {
line.remove(0, 4);
DebugPort.write("degC=");
cVal = line.toInt();
DebugPort.println(cVal);
pNVStorage->setTemperature(cVal);
}
else if(line.startsWith("Mode")) {
line.remove(0, 4);
DebugPort.write("Mode=");
cVal = !pNVStorage->getThermostatMode();
pNVStorage->setThermostatMode(cVal);
DebugPort.println(cVal);
}
}
BluetoothRxData = ""; //flush string, ready for new data
}
}

View file

@ -1,52 +1,90 @@
#include <Arduino.h>
#include "TxManage.h"
#include "NVStorage.h"
extern void SerialReport(const char* hdr, const unsigned char* pData, const char* ftr);
extern void DebugReportFrame(const char* hdr, const CProtocol&, const char* ftr);
CTxManage::CTxManage(int TxEnbPin, HardwareSerial& serial) :
m_Serial(serial),
m_Frame(CProtocol::CtrlMode)
CTxManage::CTxManage(int TxGatePin, HardwareSerial& serial) :
m_BlueWireSerial(serial),
m_TxFrame(CProtocol::CtrlMode)
{
m_bOnReq = false;
m_bOffReq = false;
m_bTxPending = false;
m_nStartTime = 0;
m_nTxEnbPin = TxEnbPin;
m_nTxGatePin = TxGatePin;
}
void CTxManage::begin()
{
pinMode(m_nTxEnbPin, OUTPUT);
digitalWrite(m_nTxEnbPin, LOW);
pinMode(m_nTxGatePin, OUTPUT);
digitalWrite(m_nTxGatePin, LOW);
}
void
CTxManage::RequestOn()
CTxManage::queueOnRequest()
{
m_bOnReq = true;
}
void
CTxManage::RequestOff()
CTxManage::queueOffRequest()
{
m_bOffReq = true;
}
void
CTxManage::Start(const CProtocol& ref, unsigned long timenow, bool self)
void
CTxManage::PrepareFrame(const CProtocol& basisFrame, bool isBTCmaster)
{
m_Frame = ref;
// copy supplied frame, typically this will be the values an OEM controller delivered
// which means we parrot that data by default.
// When parroting, we must especially avoid ping ponging "set temperature"!
// Otherwise we are supplied with the default params for standalone mode, which we
// then instil the NV parameters
m_TxFrame = basisFrame;
// ALWAYS install on/off commands if required
m_TxFrame.resetCommand(); // no command upon blue wire initially, unless a request is pending
if(m_bOnReq) {
m_bOnReq = false;
m_TxFrame.onCommand();
}
if(m_bOffReq) {
m_bOffReq = false;
m_TxFrame.offCommand();
}
// 0x78 prevents the controller showing bum information when we parrot the OEM controller
// heater is happy either way, the OEM controller has set the max/min stuff already
m_Frame.Data[0] = self ? 0x76 : 0x78;
if(isBTCmaster) {
m_TxFrame.setActiveMode(); // this allows heater to svae the tuning params to EEPROM
m_TxFrame.setFan_Min(pNVStorage->getFmin());
m_TxFrame.setFan_Max(pNVStorage->getFmax());
m_TxFrame.setPump_Min(pNVStorage->getPmin());
m_TxFrame.setPump_Max(pNVStorage->getPmax());
m_TxFrame.setThermostatMode(pNVStorage->getThermostatMode());
m_TxFrame.setTemperature_Desired(pNVStorage->getTemperature());
}
else {
m_TxFrame.setPassiveMode(); // this prevents the tuning parameters being saved by heater
}
if(timenow == 0)
// ensure CRC valid
m_TxFrame.setCRC();
}
void
CTxManage::Start(unsigned long timenow)
{
if(timenow == 0) // avoid a black hole if millis() wraps
timenow++;
m_nStartTime = timenow;
m_bTxPending = true;
}
// generate a Tx Gate, then send the TxFrame to the Blue wire
// Note the serial data is ISR driven, we need to hold off
// for a while to let teh buffewred dat clear before closing the Tx Gate.
bool
CTxManage::CheckTx(unsigned long timenow)
{
@ -56,51 +94,20 @@ CTxManage::CheckTx(unsigned long timenow)
if(diff > m_nStartDelay) {
// begin front porch of Tx gating pulse
digitalWrite(m_nTxEnbPin, HIGH);
digitalWrite(m_nTxGatePin, HIGH);
}
if(m_bTxPending && (diff > (m_nStartDelay + m_nFrontPorch))) {
// begin serial transmission
m_bTxPending = false;
_send();
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;
digitalWrite(m_nTxEnbPin, LOW);
digitalWrite(m_nTxGatePin, LOW);
}
}
return m_nStartTime == 0;
}
void
CTxManage::_send()
{
// install on/off commands if required
if(m_bOnReq) {
m_bOnReq = false;
m_Frame.Controller.Command = 0xa0;
}
else if(m_bOffReq) {
m_bOffReq = false;
m_Frame.Controller.Command = 0x05;
}
else {
m_Frame.Controller.Command = 0x00;
}
// ensure CRC valid
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, " ");
}

View file

@ -8,23 +8,25 @@ class CTxManage
const int m_nFrontPorch = 2;
public:
CTxManage(int TxEnbPin, HardwareSerial& serial);
void RequestOn();
void RequestOff();
void Start(const CProtocol& ref, unsigned long timenow, bool self);
CTxManage(int TxGatePin, HardwareSerial& serial);
void queueOnRequest();
void queueOffRequest();
void PrepareFrame(const CProtocol& Frame, bool isBTCmaster);
void Start(unsigned long timenow);
bool CheckTx(unsigned long timenow);
void Report();
void begin();
const CProtocol& getFrame() const { return m_TxFrame; };
private:
CProtocol m_Frame;
CProtocol m_TxFrame;
bool m_bOnReq;
bool m_bOffReq;
bool m_bTxPending;
int m_nTxEnbPin;
int m_nTxGatePin;
unsigned long m_nStartTime;
HardwareSerial& m_Serial;
HardwareSerial& m_BlueWireSerial;
void _send();
};
extern CTxManage TxManage;

View file

@ -0,0 +1,8 @@
#if defined(__arm__)
// Typically Arduino Due
static UARTClass& DebugPort(Serial);
#else
static HardwareSerial& DebugPort(Serial); // reference Serial as DebugPort
#endif

View file

@ -0,0 +1,8 @@
const int TxEnbPin = 22;
const int ListenOnlyPin = 21;
const int KeyPin = 15;
const int Tx1Pin = 18;
const int Rx1Pin = 19;
const int Tx2Pin = 16;
const int Rx2Pin = 17;