diff --git a/src/Afterburner.cpp b/src/Afterburner.cpp index 7962fe6..8cb0e24 100644 --- a/src/Afterburner.cpp +++ b/src/Afterburner.cpp @@ -120,12 +120,13 @@ #include "RTC/TimerManager.h" #include "Utility/GetLine.h" #include "Utility/DemandManager.h" +#include "Protocol/BlueWireTask.h" // SSID & password now stored in NV storage - these are still the default values. //#define AP_SSID "Afterburner" //#define AP_PASSWORD "thereisnospoon" -#define RX_DATA_TIMOUT 50 +// #define RX_DATA_TIMOUT 50 const int FirmwareRevision = 32; const int FirmwareSubRevision = 0; @@ -139,17 +140,6 @@ const char* FirmwareDate = "11 Apr 2020"; #include "Bluetooth/BluetoothHC05.h" #endif -// Setup Serial Port Definitions -#if defined(__arm__) -// Required for Arduino Due, UARTclass is derived from HardwareSerial -static UARTClass& BlueWireSerial(Serial1); -#else -// for ESP32, Mega -// HardwareSerial is it for these boards -static HardwareSerial& BlueWireSerial(Serial1); -#endif - -void initBlueWireSerial(); bool validateFrame(const CProtocol& frame, const char* name); void checkDisplayUpdate(); void checkDebugCommands(); @@ -159,7 +149,7 @@ void manageHumidity(); void doStreaming(); void heaterOn(); void heaterOff(); -void updateFilteredData(); +void updateFilteredData(CProtocol& HeaterInfo); bool HandleMQTTsetup(char rxVal); void showMainmenu(); @@ -178,16 +168,11 @@ bool bReportStack = false; unsigned long lastAnimationTime; // used to sequence updates to LCD for animation sFilteredData FilteredSamples; -CommStates CommState; -CTxManage TxManage(TxEnbPin, BlueWireSerial); -CModeratedFrame OEMCtrlFrame; // data packet received from heater in response to OEM controller packet -CModeratedFrame 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 CSmartError SmartError; CKeyPad KeyPad; CScreenManager ScreenManager; ABTelnetSpy DebugPort; + #if USE_JTAG == 0 //CANNOT USE GPIO WITH JTAG DEBUG CGPIOin GPIOin; @@ -199,11 +184,6 @@ CMQTTsetup MQTTmenu; -long lastRxTime; // used to observe inter character delays -bool bHasOEMController = false; -bool bHasOEMLCDController = false; -bool bHasHtrData = false; - // these variables will persist over a soft reboot. __NOINIT_ATTR float persistentRunTime; __NOINIT_ATTR float persistentGlowTime; @@ -217,10 +197,10 @@ bool bReportJSONData = REPORT_JSON_TRANSMIT; bool bReportRecyleEvents = REPORT_BLUEWIRE_RECYCLES; bool bReportOEMresync = REPORT_OEM_RESYNC; -CProtocolPackage reportHeaterData; -CProtocolPackage primaryHeaterData; +CProtocol BlueWireRxData; +CProtocol BlueWireTxData; +CProtocolPackage BlueWireData; -unsigned long moderator; bool bUpdateDisplay = false; bool bHaveWebClient = false; bool bBTconnected = false; @@ -281,6 +261,14 @@ CBluetoothAbstract& getBluetoothClient() return Bluetooth; } +// collect and report any debug messages from the blue wire task +char taskMsg[BLUEWIRE_MSGQUEUESIZE]; +void checkBlueWireDebugMsgs() +{ + if(BlueWireMsgBuf && xQueueReceive(BlueWireMsgBuf, taskMsg, 0)) + DebugPort.print(taskMsg); +} + // callback function for Keypad events. // must be an absolute function, cannot be a class member due the "this" element! void parentKeyHandler(uint8_t event) @@ -462,31 +450,11 @@ void setup() { pinMode(LED_Pin, OUTPUT); // On board LED indicator digitalWrite(LED_Pin, LOW); - initBlueWireSerial(); - - // prepare for first long delay detection - lastRxTime = millis(); - - TxManage.begin(); // ensure Tx enable pin is setup - - // define defaults should OEM controller be missing - DefaultBTCParams.setHeaterDemand(23); - DefaultBTCParams.setTemperature_Actual(22); - DefaultBTCParams.setSystemVoltage(12.0); - DefaultBTCParams.setPump_Min(1.6f); - DefaultBTCParams.setPump_Max(5.5f); - DefaultBTCParams.setFan_Min(1680); - DefaultBTCParams.setFan_Max(4500); - DefaultBTCParams.Controller.FanSensor = 1; - bBTconnected = false; Bluetooth.begin(); setupGPIO(); -// pinMode(0, OUTPUT); -// digitalWrite(0, LOW); - #if USE_SW_WATCHDOG == 1 && USE_JTAG == 0 // create a high priority FreeRTOS task as a watchdog monitor TaskHandle_t wdTask; @@ -553,6 +521,15 @@ void setup() { TempSensor.getDS18B20().mapSensor(1, NVstore.getHeaterTuning().DS18B20probe[1].romCode); TempSensor.getDS18B20().mapSensor(2, NVstore.getHeaterTuning().DS18B20probe[2].romCode); + // create task to run blue wire interface + TaskHandle_t bwTask; + xTaskCreate(BlueWireTask, + "BlueWireTask", + 2000, + NULL, + 3, + &bwTask); + delay(1000); // just to hold the splash screeen for while } @@ -570,386 +547,89 @@ void loop() // DebugPort.handle(); // keep telnet spy alive - ////////////////////////////////////////////////////////////////////////////////////// - // 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 - // Note that the data is read now, then held for later use in the state machine - // - sRxData BlueWireData; + // report any debug messages from the blue wire task + checkBlueWireDebugMsgs(); - // calc elapsed time since last rxd byte - // used to detect no OEM controller, or the start of an OEM frame sequence - unsigned long RxTimeElapsed = timenow - lastRxTime; - - if (BlueWireSerial.available()) { - // Data is available, read and store it now, use it later - // Note that if not in a recognised data receive frame state, the data - // will be deliberately lost! - BlueWireData.setValue(BlueWireSerial.read()); // read hex byte, store for later use + feedWatchdog(); // feed watchdog - lastRxTime = timenow; // tickle last rx time, for rx data timeout purposes - } + doStreaming(); // do wifi, BT tx etc + Clock.update(); + checkDisplayUpdate(); - // precautionary state machine action if all 24 bytes were not received - // whilst expecting a frame from the blue wire - if(RxTimeElapsed > RX_DATA_TIMOUT) { + long tDelta = timenow - lastTemperatureTime; + if(tDelta > MIN_TEMPERATURE_INTERVAL) { // maintain a minimum holdoff period + lastTemperatureTime = millis(); // reset time to observe temeprature - if(NVstore.getUserSettings().menuMode == 2) - bReportRecyleEvents = false; - - if( CommState.is(CommStates::OEMCtrlRx) || - CommState.is(CommStates::HeaterRx1) || - CommState.is(CommStates::HeaterRx2) ) { - - if(RxTimeElapsed >= moderator) { - moderator += 10; - if(bReportRecyleEvents) { - DebugPort.printf("%ldms - ", RxTimeElapsed); - } - if(CommState.is(CommStates::OEMCtrlRx)) { - bHasOEMController = false; - bHasOEMLCDController = false; - if(bReportRecyleEvents) - DebugPort.println("Timeout collecting OEM controller data, returning to Idle State"); - } - else if(CommState.is(CommStates::HeaterRx1)) { - bHasHtrData = false; - if(bReportRecyleEvents) - DebugPort.println("Timeout collecting OEM heater response data, returning to Idle State"); - } - else { - bHasHtrData = false; - if(bReportRecyleEvents) - DebugPort.println("Timeout collecting BTC heater response data, returning to Idle State"); - } - } - - if(bReportRecyleEvents) - DebugPort.println("Recycling blue wire serial interface"); - initBlueWireSerial(); - CommState.set(CommStates::TemperatureRead); // revert to idle mode, after passing thru temperature mode + if(bReportStack) { + int stackdepth = uxTaskGetStackHighWaterMark(NULL); + DebugPort.printf("Stack : %d\r\n", stackdepth); } + + TempSensor.readSensors(); + if(TempSensor.getTemperature(0, fTemperature)) { // get Primary sensor temperature + if(DS18B20holdoff) { + DS18B20holdoff--; + DebugPort.printf("Skipped initial DS18B20 reading: %f\r\n", fTemperature); + } // first value upon sensor connect is bad + else { + // exponential mean to stabilse readings + FilteredSamples.AmbientTemp.update(fTemperature); + + manageCyclicMode(); + manageFrostMode(); + manageHumidity(); + } + } + else { + DS18B20holdoff = 3; + FilteredSamples.AmbientTemp.reset(-100.0); + } + + TempSensor.startConvert(); // request a new conversion, will be ready by the time we loop back around + + ScreenManager.reqUpdate(); } - /////////////////////////////////////////////////////////////////////////////////////////// - // do our state machine to track the reception and delivery of blue wire data - - long tDelta; - switch(CommState.get()) { - - case CommStates::Idle: - - feedWatchdog(); // feed watchdog - - doStreaming(); // do wifi, BT tx etc when NOT in midst of handling blue wire - // this especially avoids E-07 faults due to larger data transfers - - moderator = 50; - -#if RX_LED == 1 - digitalWrite(LED_Pin, LOW); -#endif - // Detect the possible start of a new frame sequence from an OEM controller - // This will be the first activity for considerable period on the blue wire - // The heater always responds to a controller frame, but otherwise never by itself - - if(RxTimeElapsed >= (NVstore.getUserSettings().FrameRate - 60)) { // compensate for the time spent just doing things in this state machine - // 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. - bHasHtrData = false; - bHasOEMController = false; - bHasOEMLCDController = false; - bool isBTCmaster = true; - TxManage.PrepareFrame(DefaultBTCParams, isBTCmaster); // use our parameters, and mix in NV storage values - TxManage.Start(timenow); - CommState.set(CommStates::BTC_Tx); - break; - } - -#if SUPPORT_OEM_CONTROLLER == 1 - if(BlueWireData.available() && (RxTimeElapsed > (RX_DATA_TIMOUT+10))) { - - if(bReportOEMresync) { - DebugPort.printf("Re-sync'd with OEM Controller. %ldms Idle time.\r\n", RxTimeElapsed); - } - - bHasHtrData = false; - bHasOEMController = true; - CommState.set(CommStates::OEMCtrlRx); // we must add this new byte! - // - // ** IMPORTANT - we must drop through to OEMCtrlRx *NOW* (skipping break) ** - // ** otherwise the first byte will be lost! ** - // - } - else { - Clock.update(); - checkDisplayUpdate(); - break; // only break if we fail all Idle state tests - } -#else - Clock.update(); - checkDisplayUpdate(); - break; -#endif - - - case CommStates::OEMCtrlRx: - -#if RX_LED == 1 - digitalWrite(LED_Pin, HIGH); -#endif - // collect OEM controller frame - if(BlueWireData.available()) { - if(CommState.collectData(OEMCtrlFrame, BlueWireData.getValue()) ) { - CommState.set(CommStates::OEMCtrlValidate); // collected 24 bytes, move on! - } - } - break; - - - case CommStates::OEMCtrlValidate: -#if RX_LED == 1 - digitalWrite(LED_Pin, LOW); -#endif - // test for valid CRC, abort and restarts Serial1 if invalid - if(!validateFrame(OEMCtrlFrame, "OEM")) { - break; - } - - // filled OEM controller frame - OEMCtrlFrame.setTime(); - // LCD controllers use 0x76 as first byte, rotary knobs use 0x78 - bHasOEMLCDController = (OEMCtrlFrame.Controller.Byte0 != 0x78); - - CommState.set(CommStates::HeaterRx1); - break; - - - case CommStates::HeaterRx1: -#if RX_LED == 1 - digitalWrite(LED_Pin, HIGH); -#endif - // collect heater frame, always in response to an OEM controller frame - if(BlueWireData.available()) { - if( CommState.collectData(HeaterFrame1, BlueWireData.getValue()) ) { - CommState.set(CommStates::HeaterValidate1); - } - } - break; - - - case CommStates::HeaterValidate1: -#if RX_LED == 1 - digitalWrite(LED_Pin, LOW); -#endif - - // test for valid CRC, abort and restarts Serial1 if invalid - if(!validateFrame(HeaterFrame1, "RX1")) { - bHasHtrData = false; - break; - } - bHasHtrData = true; - - // received heater frame (after controller message), report - - // do some monitoring of the heater state variable - // if abnormal transitions, introduce a smart error! - // This routine also cancels ON/OFF requests if runstate in startup/shutdown periods - SmartError.monitor(HeaterFrame1); - - HeaterFrame1.setTime(); - - while(BlueWireSerial.available()) { - DebugPort.println("DUMPED ROGUE RX DATA"); - BlueWireSerial.read(); - } - BlueWireSerial.flush(); - - primaryHeaterData.set(HeaterFrame1, OEMCtrlFrame); // OEM is always *the* controller - if(bReportBlueWireData) { - primaryHeaterData.reportFrames(true); - CommState.setDelay(20); // let serial get sent before we send blue wire - } - else { - CommState.setDelay(0); - } - CommState.set(CommStates::HeaterReport1); - break; - - - case CommStates::HeaterReport1: - if(CommState.delayExpired()) { - bool isBTCmaster = false; - TxManage.PrepareFrame(OEMCtrlFrame, isBTCmaster); // parrot OEM parameters, but block NV modes - TxManage.Start(timenow); - CommState.set(CommStates::BTC_Tx); - } - break; - - - case CommStates::BTC_Tx: - // Handle time interval where we send data to the blue wire - lastRxTime = timenow; // *we* are pumping onto blue wire, track this activity! - if(TxManage.CheckTx(timenow) ) { // monitor progress of our data delivery - CommState.set(CommStates::HeaterRx2); // then await heater repsonse - } - break; - - - case CommStates::HeaterRx2: -#if RX_LED == 1 - digitalWrite(LED_Pin, HIGH); -#endif - // collect heater frame, in response to our control frame - if(BlueWireData.available()) { -#ifdef BADSTARTCHECK - if(!CommState.checkValidStart(BlueWireData.getValue())) { - DebugPort.println("***** Invalid start of frame - restarting Serial port *****"); - initBlueWireSerial(); - CommState.set(CommStates::Idle); - } - else { - if( CommState.collectData(HeaterFrame2, BlueWireData.getValue()) ) { - CommState.set(CommStates::HeaterValidate2); - } - } -#else - if( CommState.collectData(HeaterFrame2, BlueWireData.getValue()) ) { - CommState.set(CommStates::HeaterValidate2); - } -#endif - } - break; - - - case CommStates::HeaterValidate2: -#if RX_LED == 1 - digitalWrite(LED_Pin, LOW); -#endif - - // test for valid CRC, abort and restart Serial1 if invalid - if(!validateFrame(HeaterFrame2, "RX2")) { - bHasHtrData = false; - break; - } - bHasHtrData = true; - - // received heater frame (after our control message), report - - // do some monitoring of the heater state variables - // if abnormal transitions, introduce a smart error! - SmartError.monitor(HeaterFrame2); - - if(!bHasOEMController) // no OEM controller - BTC is *the* controller - primaryHeaterData.set(HeaterFrame2, TxManage.getFrame()); - - if(bReportBlueWireData) { - reportHeaterData.set(HeaterFrame2, TxManage.getFrame()); - reportHeaterData.reportFrames(false); - CommState.setDelay(20); // let serial get sent before we send blue wire - } - else { - CommState.setDelay(0); - } - CommState.set(CommStates::HeaterReport2); - break; - - - case CommStates::HeaterReport2: - if(CommState.delayExpired()) { - CommState.set(CommStates::TemperatureRead); - } - break; - - - case CommStates::TemperatureRead: - // update temperature reading, - // synchronised with serial reception as interrupts do get disabled in the OneWire library - tDelta = timenow - lastTemperatureTime; - if(tDelta > MIN_TEMPERATURE_INTERVAL) { // maintain a minimum holdoff period - lastTemperatureTime = millis(); // reset time to observe temeprature - - if(bReportStack) { - int stackdepth = uxTaskGetStackHighWaterMark(NULL); - DebugPort.printf("Stack : %d\r\n", stackdepth); - } - - TempSensor.readSensors(); - if(TempSensor.getTemperature(0, fTemperature)) { // get Primary sensor temperature - if(DS18B20holdoff) { - DS18B20holdoff--; - DebugPort.printf("Skipped initial DS18B20 reading: %f\r\n", fTemperature); - } // first value upon sensor connect is bad - else { - // exponential mean to stabilse readings - FilteredSamples.AmbientTemp.update(fTemperature); - - manageCyclicMode(); - manageFrostMode(); - manageHumidity(); - } - } - else { - DS18B20holdoff = 3; - FilteredSamples.AmbientTemp.reset(-100.0); - } - - TempSensor.startConvert(); // request a new conversion, will be ready by the time we loop back around - - ScreenManager.reqUpdate(); - } - - if(bHasHtrData) { - // apply exponential mean to the anlogue readings for some smoothing - updateFilteredData(); - - // integrate fuel pump activity for fuel gauge - FuelGauge.Integrate(getHeaterInfo().getPump_Actual()); - - // test for low volts shutdown during normal run - if(INBOUNDS(getHeaterInfo().getRunState(), 1, 5)) { // check for Low Voltage Cutout - SmartError.checkVolts(FilteredSamples.FastipVolts.getValue(), FilteredSamples.FastGlowAmps.getValue()); - SmartError.checkfuelUsage(); - } - - // trap being in state 0 with a heater error - cancel user on memory to avoid unexpected cyclic restarts - if(RTC_Store.getCyclicEngaged() && (getHeaterInfo().getRunState() == 0) && (getHeaterInfo().getErrState() > 1)) { - DebugPort.println("Forcing cyclic cancel due to error induced shutdown"); - RTC_Store.setCyclicEngaged(false); - } - - pHourMeter->monitor(HeaterFrame2); - } - updateJSONclients(bReportJSONData); - updateMQTT(); - CommState.set(CommStates::Idle); - NVstore.doSave(); // now is a good time to store to the NV storage, well away from any blue wire activity - break; - } // switch(CommState) - - BlueWireData.reset(); // ensure we flush any used data - -// 21/11/19 vTaskDelay() causes E-07 errors when OEM controller is attached. -// may look at a specific freertos task to handle the blue wire.... - if(!bHasOEMController) { - vTaskDelay(1); // give up for now - allow power lowering... + if(BlueWireSemaphore && xSemaphoreTake(BlueWireSemaphore, 0)) { + updateJSONclients(bReportJSONData); + updateMQTT(); + NVstore.doSave(); // now is a good time to store to the NV storage, well away from any blue wire activity } + // collect transmitted heater data from blue wire task + if(BlueWireTxQueue && xQueueReceive(BlueWireTxQueue, BlueWireTxData.Data, 0)) { + } + + // collect and process received heater data from blue wire task + if(BlueWireRxQueue && xQueueReceive(BlueWireRxQueue, BlueWireRxData.Data, 0)) { + BlueWireData.set(BlueWireRxData, BlueWireTxData); + SmartError.monitor(BlueWireRxData); + + updateFilteredData(BlueWireRxData); + + FuelGauge.Integrate(BlueWireRxData.getPump_Actual()); + + if(INBOUNDS(BlueWireRxData.getRunState(), 1, 5)) { // check for Low Voltage Cutout + SmartError.checkVolts(FilteredSamples.FastipVolts.getValue(), FilteredSamples.FastGlowAmps.getValue()); + SmartError.checkfuelUsage(); + } + + // trap being in state 0 with a heater error - cancel user on memory to avoid unexpected cyclic restarts + if(RTC_Store.getCyclicEngaged() && (BlueWireRxData.getRunState() == 0) && (BlueWireRxData.getErrState() > 1)) { + const char* msg = "Forcing cyclic cancel due to error induced shutdown\r\n"; + xQueueSend(BlueWireMsgBuf, msg, 0); + // DebugPort.println("Forcing cyclic cancel due to error induced shutdown"); + RTC_Store.setCyclicEngaged(false); + } + + pHourMeter->monitor(BlueWireRxData); + + } + + } // loop -void DebugReportFrame(const char* hdr, const CProtocol& Frame, const char* ftr) -{ - DebugPort.print(hdr); // header - for(int i=0; i<24; i++) { - char str[16]; - sprintf(str, " %02X", Frame.Data[i]); // build 2 dig hex values - DebugPort.print(str); // and print - } - DebugPort.print(ftr); // footer -} void manageCyclicMode() { @@ -1029,36 +709,6 @@ void manageHumidity() } -void initBlueWireSerial() -{ - // 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 -#if defined(__arm__) || defined(__AVR__) - BlueWireSerial.begin(25000); - pinMode(Rx1Pin, INPUT_PULLUP); // required for MUX to work properly -#elif ESP32 - // ESP32 - BlueWireSerial.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 -} - -bool validateFrame(const CProtocol& frame, const char* name) -{ - if(!frame.verifyCRC()) { - // Bad CRC - restart blue wire Serial port - DebugPort.printf("\007Bad CRC detected for %s frame - restarting blue wire's serial port\r\n", name); - DebugReportFrame("BAD CRC:", frame, "\r\n"); - initBlueWireSerial(); - CommState.set(CommStates::TemperatureRead); - return false; - } - return true; -} - - CDemandManager::eStartCode requestOn() { @@ -1068,7 +718,7 @@ requestOn() return CDemandManager::eStartLowFuel; } bool LVCOK = 2 != SmartError.checkVolts(FilteredSamples.FastipVolts.getValue(), FilteredSamples.FastGlowAmps.getValue()); - if(bHasHtrData && LVCOK) { + if(hasHtrData() && LVCOK) { RTC_Store.setCyclicEngaged(true); // for cyclic mode RTC_Store.setFrostOn(false); // cancel frost mode // only start if below appropriate temperature threshold, raised for cyclic mode @@ -1130,11 +780,6 @@ void checkDisplayUpdate() } } -void reqPumpPrime(bool on) -{ - DefaultBTCParams.setPump_Prime(on); -} - void forceBootInit() { RTC_Store.setBootInit(); @@ -1150,11 +795,6 @@ float getTemperatureSensor(int source) } -const CProtocolPackage& getHeaterInfo() -{ - return primaryHeaterData; -} - bool isWebClientConnected() { return bHaveWebClient; @@ -1199,10 +839,22 @@ void checkDebugCommands() } switch(nGetConf) { case 1: - setSSID(line.getString()); + setName(line.getString(), 0); break; case 2: - setAPpassword(pw2.c_str()); + setPassword(pw2.c_str(), 0); + break; + case 3: + setName(line.getString(), 1); + break; + case 4: + setPassword(pw2.c_str(), 1); + break; + case 5: + setName(line.getString(), 2); + break; + case 6: + setPassword(pw2.c_str(), 2); break; } nGetConf = 0; @@ -1276,6 +928,122 @@ void checkDebugCommands() } nGetString = 0; return; + case 10: + if(line.getLen() <= 31) { + nGetConf = 3; + DebugPort.printf("\r\nSet Web page username to %s? (y/n) - ", line.getString()); + } + else { + DebugPort.println("\r\nNew username is longer than 31 characters - ABORTING"); + } + nGetString = 0; + return; + case 11: + pw1 = line.getString(); + pw2 = NVstore.getCredentials().webPassword; + if(pw1 != pw2) { + DebugPort.println("\r\nPassword does not match existing - ABORTING"); + nGetString = 0; + } + else { + nGetString = 12; + DebugPort.print("\r\nPlease enter new password - "); + DebugPort.enable(false); // block other debug msgs whilst we get the password + } + line.reset(); + line.maskEntry(); + return; + case 12: + pw1 = line.getString(); + if(line.getLen() < 8) { + // ABORT - too short + DebugPort.println("\r\nNew password must be at least 8 characters - ABORTING"); + nGetString = 0; + } + else if(line.getLen() > 31) { + // ABORT - too long! + DebugPort.println("\r\nNew password is longer than 31 characters - ABORTING"); + nGetString = 0; + } + else { + nGetString = 13; + DebugPort.print("\r\nPlease confirm new password - "); + DebugPort.enable(false); // block other debug msgs whilst we get the password + } + line.reset(); + line.maskEntry(); + return; + case 13: + pw2 = line.getString(); + line.reset(); + if(pw1 != pw2) { + DebugPort.println("\r\nNew passwords do not match - ABORTING"); + } + else { + nGetConf = 4; + DebugPort.print("\r\nSet new password (y/n) - "); + } + nGetString = 0; + return; + + case 20: + if(line.getLen() <= 31) { + nGetConf = 5; + DebugPort.printf("\r\nSet Web /update username to %s? (y/n) - ", line.getString()); + } + else { + DebugPort.println("\r\nNew username is longer than 31 characters - ABORTING"); + } + nGetString = 0; + return; + + case 21: + pw1 = line.getString(); + pw2 = NVstore.getCredentials().webUpdatePassword; + if(pw1 != pw2) { + DebugPort.println("\r\nPassword does not match existing - ABORTING"); + nGetString = 0; + } + else { + nGetString = 22; + DebugPort.print("\r\nPlease enter new password - "); + DebugPort.enable(false); // block other debug msgs whilst we get the password + } + line.reset(); + line.maskEntry(); + return; + case 22: + pw1 = line.getString(); + if(line.getLen() < 8) { + // ABORT - too short + DebugPort.println("\r\nNew password must be at least 8 characters - ABORTING"); + nGetString = 0; + } + else if(line.getLen() > 31) { + // ABORT - too long! + DebugPort.println("\r\nNew password is longer than 31 characters - ABORTING"); + nGetString = 0; + } + else { + nGetString = 23; + DebugPort.print("\r\nPlease confirm new password - "); + DebugPort.enable(false); // block other debug msgs whilst we get the password + } + line.reset(); + line.maskEntry(); + return; + case 23: + pw2 = line.getString(); + line.reset(); + if(pw1 != pw2) { + DebugPort.println("\r\nNew passwords do not match - ABORTING"); + } + else { + nGetConf = 6; + DebugPort.print("\r\nSet new password (y/n) - "); + } + nGetString = 0; + return; } } DebugPort.enable(false); @@ -1347,8 +1115,10 @@ void checkDebugCommands() bReportJSONData = !bReportJSONData; DebugPort.printf("Toggled JSON data reporting %s\r\n", bReportJSONData ? "ON" : "OFF"); } - else if(rxVal == 'w') { + else if(rxVal == ('w' & 0x1f)) { bReportRecyleEvents = !bReportRecyleEvents; + if(NVstore.getUserSettings().menuMode == 2) + bReportRecyleEvents = false; DebugPort.printf("Toggled blue wire recycling event reporting %s\r\n", bReportRecyleEvents ? "ON" : "OFF"); } else if(rxVal == 'n') { @@ -1360,7 +1130,7 @@ void checkDebugCommands() else if(rxVal == 'm') { MQTTmenu.setActive(); } - else if(rxVal == 'o') { + else if(rxVal == ('o' & 0x1f)) { bReportOEMresync = !bReportOEMresync; DebugPort.printf("Toggled OEM resync event reporting %s\r\n", bReportOEMresync ? "ON" : "OFF"); } @@ -1371,7 +1141,31 @@ void checkDebugCommands() nGetString = 2; DebugPort.enable(false); // block other debug msgs whilst we get strings } - else if(rxVal == 's') { + else if(rxVal == 'u') { + DebugPort.print("Please enter username for Web page access (CTRL-X to disable) - "); + line.reset(); + nGetString = 10; + DebugPort.enable(false); // block other debug msgs whilst we get strings + } + else if(rxVal == 'w') { + DebugPort.print("Please enter current Web page password - "); + line.reset(); + nGetString = 11; + DebugPort.enable(false); // block other debug msgs whilst we get strings + } + else if(rxVal == 'y') { + DebugPort.print("Please enter username for /update Web page access - "); + line.reset(); + nGetString = 20; + DebugPort.enable(false); // block other debug msgs whilst we get strings + } + else if(rxVal == 'z') { + DebugPort.print("Please enter current /update Web page password - "); + line.reset(); + nGetString = 21; + DebugPort.enable(false); // block other debug msgs whilst we get strings + } + else if(rxVal == ('c' & 0x1f)) { CommState.toggleReporting(); } else if(rxVal == '+') { @@ -1393,7 +1187,7 @@ void checkDebugCommands() else if(rxVal == ('r' & 0x1f)) { // CTRL-R reboot ESP.restart(); // reset the esp } - else if(rxVal == ('s' & 0x1f)) { // CTRL-B Test Mode: bluetooth module route + else if(rxVal == ('s' & 0x1f)) { // CTRL-S Test Mode: bluetooth module route bReportStack = !bReportStack; } } @@ -1422,38 +1216,6 @@ void checkDebugCommands() } } -// 0x00 - Normal: BTC, with heater responding -// 0x01 - Error: BTC, heater not responding -// 0x02 - Special: OEM controller & heater responding -// 0x03 - Error: OEM controller, heater not responding -int getBlueWireStat() -{ - int stat = 0; - if(!bHasHtrData) { - stat |= 0x01; - } - if(bHasOEMController) { - stat |= 0x02; - } - return stat; -} - -const char* getBlueWireStatStr() -{ - static const char* BlueWireStates[] = { "BTC,Htr", "BTC", "OEM,Htr", "OEM" }; - - return BlueWireStates[getBlueWireStat()]; -} - -bool hasOEMcontroller() -{ - return bHasOEMController; -} - -bool hasOEMLCDcontroller() -{ - return bHasOEMLCDController; -} int getSmartError() { @@ -1756,14 +1518,14 @@ int getFanSpeed() #endif } -void updateFilteredData() +void updateFilteredData(CProtocol& HeaterInfo) { - FilteredSamples.ipVolts.update(getHeaterInfo().getBattVoltage()); - FilteredSamples.GlowVolts.update(getHeaterInfo().getGlow_Voltage()); - FilteredSamples.GlowAmps.update(getHeaterInfo().getGlow_Current()); - FilteredSamples.Fan.update(getHeaterInfo().getFan_Actual()); - FilteredSamples.FastipVolts.update(getHeaterInfo().getBattVoltage()); - FilteredSamples.FastGlowAmps.update(getHeaterInfo().getGlow_Current()); + FilteredSamples.ipVolts.update(HeaterInfo.getVoltage_Supply()); + FilteredSamples.GlowVolts.update(HeaterInfo.getGlowPlug_Voltage()); + FilteredSamples.GlowAmps.update(HeaterInfo.getGlowPlug_Current()); + FilteredSamples.Fan.update(HeaterInfo.getFan_Actual()); + FilteredSamples.FastipVolts.update(HeaterInfo.getVoltage_Supply()); + FilteredSamples.FastGlowAmps.update(HeaterInfo.getGlowPlug_Current()); } int sysUptime() @@ -1776,32 +1538,72 @@ void resetFuelGauge() FuelGauge.reset(); } -void setSSID(const char* name) +void setName(const char* name, int type) { sCredentials creds = NVstore.getCredentials(); - strncpy(creds.APSSID, name, 31); - creds.APSSID[31] = 0; + char* pDest = NULL; + switch (type) { + case 0: pDest = creds.APSSID; break; + case 1: pDest = creds.webUsername; break; + case 2: pDest = creds.webUpdateUsername; break; + } + if(pDest) { + strncpy(pDest, name, 31); + pDest[31] = 0; + } NVstore.setCredentials(creds); NVstore.save(); NVstore.doSave(); // ensure NV storage - DebugPort.println("Restarting ESP to invoke new network credentials"); - DebugPort.handle(); - delay(1000); - ESP.restart(); + if(type == 0) { + DebugPort.println("Restarting ESP to invoke new network credentials"); + DebugPort.handle(); + delay(1000); + ESP.restart(); + } } -void setAPpassword(const char* name) +void setPassword(const char* name, int type) { sCredentials creds = NVstore.getCredentials(); - strncpy(creds.APpassword, name, 31); - creds.APpassword[31] = 0; + char* pDest = NULL; + switch (type) { + case 0: pDest = creds.APpassword; break; + case 1: pDest = creds.webPassword; break; + case 2: pDest = creds.webUpdatePassword; break; + } + if(pDest) { + strncpy(pDest, name, 31); + pDest[31] = 0; + } + NVstore.setCredentials(creds); + NVstore.save(); + NVstore.doSave(); // ensure NV storage + if(type == 0) { + DebugPort.println("Restarting ESP to invoke new network credentials"); + DebugPort.handle(); + delay(1000); + ESP.restart(); + } +} + +void setWebUsername(const char* name) +{ + sCredentials creds = NVstore.getCredentials(); + strncpy(creds.webUsername, name, 31); + creds.webUsername[31] = 0; + NVstore.setCredentials(creds); + NVstore.save(); + NVstore.doSave(); // ensure NV storage +} + +void setWebPassword(const char* name) +{ + sCredentials creds = NVstore.getCredentials(); + strncpy(creds.webPassword, name, 31); + creds.webPassword[31] = 0; NVstore.setCredentials(creds); NVstore.save(); NVstore.doSave(); // ensure NV storage - DebugPort.println("Restarting ESP to invoke new network credentials"); - DebugPort.handle(); - delay(1000); - ESP.restart(); } @@ -1812,15 +1614,19 @@ void showMainmenu() DebugPort.println(""); DebugPort.printf(" - toggle raw blue wire data reporting, currently %s\r\n", bReportBlueWireData ? "ON" : "OFF"); DebugPort.printf(" - toggle output JSON reporting, currently %s\r\n", bReportJSONData ? "ON" : "OFF"); - DebugPort.printf(" - toggle reporting of blue wire timeout/recycling event, currently %s\r\n", bReportRecyleEvents ? "ON" : "OFF"); - DebugPort.printf(" - toggle reporting of OEM resync event, currently %s\r\n", bReportOEMresync ? "ON" : "OFF"); - DebugPort.printf(" - toggle reporting of state machine transits %s\r\n", CommState.isReporting() ? "ON" : "OFF"); DebugPort.printf(" - change AP SSID, currently \"%s\"\r\n", NVstore.getCredentials().APSSID); DebugPort.println("

- change AP password"); DebugPort.println(" - configure MQTT"); + DebugPort.println(" - change Web page username"); + DebugPort.println(" - change Web page password"); + DebugPort.println(" - change Web /update username"); + DebugPort.println(" - change Web /update password"); DebugPort.println(" <+> - request heater turns ON"); DebugPort.println(" <-> - request heater turns OFF"); - DebugPort.println(" - restart the ESP"); + DebugPort.println(" - restart the ESP"); + DebugPort.printf(" - toggle reporting of state machine transits %s\r\n", CommState.isReporting() ? "ON" : "OFF"); + DebugPort.printf(" - toggle reporting of OEM resync event, currently %s\r\n", bReportOEMresync ? "ON" : "OFF"); + DebugPort.printf(" - toggle reporting of blue wire timeout/recycling event, currently %s\r\n", bReportRecyleEvents ? "ON" : "OFF"); DebugPort.println(""); DebugPort.println(""); DebugPort.println(""); @@ -1844,3 +1650,9 @@ void reqHeaterCalUpdate() { TxManage.queueSysUpdate(); } + +const CProtocolPackage& getHeaterInfo() +{ + return BlueWireData; +} + diff --git a/src/Protocol/BlueWireTask.cpp b/src/Protocol/BlueWireTask.cpp new file mode 100644 index 0000000..381bf3d --- /dev/null +++ b/src/Protocol/BlueWireTask.cpp @@ -0,0 +1,478 @@ +/* + * This file is part of the "bluetoothheater" distribution + * (https://gitlab.com/mrjones.id.au/bluetoothheater) + * + * Copyright (C) 2018 Ray Jones + * Copyright (C) 2018 James Clark + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "BlueWireTask.h" +#include "../cfg/BTCConfig.h" +#include "../cfg/pins.h" +#include "Protocol.h" +#include "TxManage.h" +// #include "SmartError.h" +#include "../Utility/UtilClasses.h" +#include "../Utility/DataFilter.h" +#include "../Utility/FuelGauge.h" +#include "../Utility/HourMeter.h" +#include "../Utility/macros.h" + +// Setup Serial Port Definitions +#if defined(__arm__) +// Required for Arduino Due, UARTclass is derived from HardwareSerial +static UARTClass& BlueWireSerial(Serial1); +#else +// for ESP32, Mega +// HardwareSerial is it for these boards +static HardwareSerial& BlueWireSerial(Serial1); +#endif + +#define RX_DATA_TIMOUT 50 + +CommStates CommState; +CTxManage TxManage(TxEnbPin, BlueWireSerial); +CProtocol DefaultBTCParams(CProtocol::CtrlMode); // defines the default parameters, used in case of no OEM controller +CModeratedFrame OEMCtrlFrame; // data packet received from heater in response to OEM controller packet +CModeratedFrame HeaterFrame1; // data packet received from heater in response to OEM controller packet +CProtocol HeaterFrame2; // data packet received from heater in response to our packet +// CSmartError SmartError; +CProtocolPackage reportHeaterData; +CProtocolPackage primaryHeaterData; + +static bool bHasOEMController = false; +static bool bHasOEMLCDController = false; +static bool bHasHtrData = false; + +extern bool bReportRecyleEvents; +extern bool bReportOEMresync; +extern bool bReportBlueWireData; +extern sFilteredData FilteredSamples; + +QueueHandle_t BlueWireMsgBuf = NULL; // cannot use general Serial.print etc from this task without causing conflicts +QueueHandle_t BlueWireRxQueue = NULL; // queue to pass down heater receive data +QueueHandle_t BlueWireTxQueue = NULL; // queue to pass down heater transmit data +SemaphoreHandle_t BlueWireSemaphore = NULL; // flag to indicate completion of heater data exchange + +bool validateFrame(const CProtocol& frame, const char* name); +void DebugReportFrame(const char* hdr, const CProtocol& Frame, const char* ftr, char* msg); +// void updateFilteredData(); +void initBlueWireSerial(); + +void pushDebugMsg(const char* msg) { + if(BlueWireMsgBuf) + xQueueSend(BlueWireMsgBuf, msg, 0); +} + +void BlueWireTask(void*) { + ////////////////////////////////////////////////////////////////////////////////////// + // 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 + // Note that the data is read now, then held for later use in the state machine + // + static unsigned long lastRxTime = 0; // used to observe inter character delays + static unsigned long moderator = 50; + bool isBTCmaster = false; + + BlueWireMsgBuf = xQueueCreate(4, BLUEWIRE_MSGQUEUESIZE); + BlueWireRxQueue = xQueueCreate(4, BLUEWIRE_DATAQUEUESIZE); + BlueWireTxQueue = xQueueCreate(4, BLUEWIRE_DATAQUEUESIZE); + BlueWireSemaphore = xSemaphoreCreateBinary(); + + TxManage.begin(); // ensure Tx enable pin is setup + + // define defaults should OEM controller be missing + DefaultBTCParams.setHeaterDemand(23); + DefaultBTCParams.setTemperature_Actual(22); + DefaultBTCParams.setSystemVoltage(12.0); + DefaultBTCParams.setPump_Min(1.6f); + DefaultBTCParams.setPump_Max(5.5f); + DefaultBTCParams.setFan_Min(1680); + DefaultBTCParams.setFan_Max(4500); + DefaultBTCParams.Controller.FanSensor = 1; + + initBlueWireSerial(); + + CommState.setCallback(pushDebugMsg); + TxManage.setCallback(pushDebugMsg); + + for(;;) { + + sRxData BlueWireRxData; + unsigned long timenow = millis(); + + // calc elapsed time since last rxd byte + // used to detect no OEM controller, or the start of an OEM frame sequence + unsigned long RxTimeElapsed = timenow - lastRxTime; + + if (BlueWireSerial.available()) { + // Data is available, read and store it now, use it later + // Note that if not in a recognised data receive frame state, the data + // will be deliberately lost! + BlueWireRxData.setValue(BlueWireSerial.read()); // read hex byte, store for later use + + lastRxTime = timenow; // tickle last rx time, for rx data timeout purposes + } + + + // precautionary state machine action if all 24 bytes were not received + // whilst expecting a frame from the blue wire + if(RxTimeElapsed > RX_DATA_TIMOUT) { + + + if( CommState.is(CommStates::OEMCtrlRx) || + CommState.is(CommStates::HeaterRx1) || + CommState.is(CommStates::HeaterRx2) ) { + + if(RxTimeElapsed >= moderator) { + moderator += 10; + if(bReportRecyleEvents) { + char msg[32]; + sprintf(msg, "%ldms - ", RxTimeElapsed); + pushDebugMsg(msg); + } + if(CommState.is(CommStates::OEMCtrlRx)) { + bHasOEMController = false; + bHasOEMLCDController = false; + if(bReportRecyleEvents) { + pushDebugMsg("Timeout collecting OEM controller data, returning to Idle State\r\n"); + } + } + else if(CommState.is(CommStates::HeaterRx1)) { + bHasHtrData = false; + if(bReportRecyleEvents) { + pushDebugMsg("Timeout collecting OEM heater response data, returning to Idle State\r\n"); + } + } + else { + bHasHtrData = false; + if(bReportRecyleEvents) { + pushDebugMsg("Timeout collecting BTC heater response data, returning to Idle State\r\n"); + } + } + } + + if(bReportRecyleEvents) { + pushDebugMsg("Recycling blue wire serial interface\r\n"); + } + #ifdef REBOOT_BLUEWIRE + initBlueWireSerial(); + #endif + CommState.set(CommStates::ExchangeComplete); // revert to idle mode, after passing thru exchange complete mode + } + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // do our state machine to track the reception and delivery of blue wire data + + switch(CommState.get()) { + + case CommStates::Idle: + + moderator = 50; + + digitalWrite(LED_Pin, LOW); + // Detect the possible start of a new frame sequence from an OEM controller + // This will be the first activity for considerable period on the blue wire + // The heater always responds to a controller frame, but otherwise never by itself + + if(RxTimeElapsed >= (NVstore.getUserSettings().FrameRate - 60)) { // compensate for the time spent just doing things in this state machine + // have not seen any receive data for a second (typ.). + // OEM controller is probably not connected. + // Skip state machine immediately to BTC_Tx, sending our own settings. + bHasHtrData = false; + bHasOEMController = false; + bHasOEMLCDController = false; + isBTCmaster = true; + TxManage.PrepareFrame(DefaultBTCParams, isBTCmaster); // use our parameters, and mix in NV storage values + TxManage.Start(timenow); + CommState.set(CommStates::TxStart); + break; + } + + if(BlueWireRxData.available() && (RxTimeElapsed > (RX_DATA_TIMOUT+10))) { + + if(bReportOEMresync) { + char msg[64]; + sprintf(msg, "Re-sync'd with OEM Controller. %ldms Idle time.\r\n", RxTimeElapsed); + pushDebugMsg(msg); + } + + bHasHtrData = false; + bHasOEMController = true; + CommState.set(CommStates::OEMCtrlRx); // we must add this new byte! + // + // ** IMPORTANT - we must drop through to OEMCtrlRx *NOW* (skipping break) ** + // ** otherwise the first byte will be lost! ** + // + } + else { + break; // only break if we fail all Idle state tests + } + + + case CommStates::OEMCtrlRx: + + digitalWrite(LED_Pin, HIGH); + + // collect OEM controller frame + if(BlueWireRxData.available()) { + if(CommState.collectData(OEMCtrlFrame, BlueWireRxData.getValue()) ) { + CommState.set(CommStates::OEMCtrlValidate); // collected 24 bytes, move on! + } + } + break; + + + case CommStates::OEMCtrlValidate: + + digitalWrite(LED_Pin, LOW); + + // test for valid CRC, abort and restarts Serial1 if invalid + if(!validateFrame(OEMCtrlFrame, "OEM")) { + break; + } + + // filled OEM controller frame + OEMCtrlFrame.setTime(); + // LCD controllers use 0x76 as first byte, rotary knobs use 0x78 + bHasOEMLCDController = (OEMCtrlFrame.Controller.Byte0 != 0x78); + + // xQueueSend(BlueWireTxQueue, OEMCtrlFrame.Data, 0); + + CommState.set(CommStates::HeaterRx1); + break; + + + case CommStates::HeaterRx1: + + digitalWrite(LED_Pin, HIGH); + + // collect heater frame, always in response to an OEM controller frame + if(BlueWireRxData.available()) { + if( CommState.collectData(HeaterFrame1, BlueWireRxData.getValue()) ) { + CommState.set(CommStates::HeaterValidate1); + } + } + break; + + + case CommStates::HeaterValidate1: + + digitalWrite(LED_Pin, LOW); + + // test for valid CRC, abort and restarts Serial1 if invalid + if(!validateFrame(HeaterFrame1, "RX1")) { + bHasHtrData = false; + break; + } + bHasHtrData = true; + + HeaterFrame1.setTime(); + + while(BlueWireSerial.available()) { + pushDebugMsg("DUMPED ROGUE RX DATA\r\n"); + BlueWireSerial.read(); + } + BlueWireSerial.flush(); + + // received heater frame (after controller message), report + primaryHeaterData.set(HeaterFrame1, OEMCtrlFrame); // OEM is always *the* controller + if(bReportBlueWireData) { + primaryHeaterData.reportFrames(true, pushDebugMsg); + } + isBTCmaster = false; + TxManage.PrepareFrame(OEMCtrlFrame, isBTCmaster); // parrot OEM parameters, but block NV modes + CommState.set(CommStates::TxStart); + break; + + + case CommStates::TxStart: + xQueueSend(BlueWireTxQueue, TxManage.getFrame().Data, 0); + TxManage.Start(timenow); + CommState.set(CommStates::TxInterval); + break; + + + case CommStates::TxInterval: + // Handle time interval where we send data to the blue wire + lastRxTime = timenow; // *we* are pumping onto blue wire, track this activity! + if(TxManage.CheckTx(timenow) ) { // monitor progress of our data delivery + CommState.set(CommStates::HeaterRx2); // then await heater repsonse + } + break; + + + case CommStates::HeaterRx2: + + digitalWrite(LED_Pin, HIGH); + + // collect heater frame, in response to our control frame + if(BlueWireRxData.available()) { +#ifdef BADSTARTCHECK + if(!CommState.checkValidStart(BlueWireData.getValue())) { + DebugPort.println("***** Invalid start of frame - restarting Serial port *****"); + initBlueWireSerial(); + CommState.set(CommStates::Idle); + } + else { + if( CommState.collectData(HeaterFrame2, BlueWireData.getValue()) ) { + CommState.set(CommStates::HeaterValidate2); + } + } +#else + if( CommState.collectData(HeaterFrame2, BlueWireRxData.getValue()) ) { + CommState.set(CommStates::HeaterValidate2); + } +#endif + } + break; + + + case CommStates::HeaterValidate2: + + digitalWrite(LED_Pin, LOW); + + // test for valid CRC, abort and restart Serial1 if invalid + if(!validateFrame(HeaterFrame2, "RX2")) { + bHasHtrData = false; + break; + } + bHasHtrData = true; + + // received heater frame (after our control message), report + + xQueueSend(BlueWireRxQueue, HeaterFrame2.Data, 0); + + // do some monitoring of the heater state variables + // if abnormal transitions, introduce a smart error! + // SmartError.monitor(HeaterFrame2); + + if(!bHasOEMController) // no OEM controller - BTC is *the* controller + primaryHeaterData.set(HeaterFrame2, TxManage.getFrame()); + + if(bReportBlueWireData) { // debug or investigation purposes + reportHeaterData.set(HeaterFrame2, TxManage.getFrame()); + reportHeaterData.reportFrames(false, pushDebugMsg); + } + CommState.set(CommStates::ExchangeComplete); + break; + + + case CommStates::ExchangeComplete: + xSemaphoreGive(BlueWireSemaphore); + CommState.set(CommStates::Idle); + break; + } // switch(CommState) + + vTaskDelay(1); + } +} + + +bool validateFrame(const CProtocol& frame, const char* name) +{ + if(!frame.verifyCRC(pushDebugMsg)) { + // Bad CRC - restart blue wire Serial port + char msg[128]; + sprintf(msg, "\007Bad CRC detected for %s frame - restarting blue wire's serial port\r\n", name); + pushDebugMsg(msg); + msg[0] = 0; // empty string + DebugReportFrame("BAD CRC:", frame, "\r\n", msg); + pushDebugMsg(msg); +#ifdef REBOOT_BLUEWIRE + initBlueWireSerial(); +#endif + CommState.set(CommStates::ExchangeComplete); + return false; + } + return true; +} + + +void DebugReportFrame(const char* hdr, const CProtocol& Frame, const char* ftr, char* msg) +{ + strcat(msg, hdr); // header + for(int i=0; i<24; i++) { + char str[16]; + sprintf(str, " %02X", Frame.Data[i]); // build 2 dig hex values + strcat(msg, str); // and print + } + strcat(msg, ftr); // footer +} + +bool hasOEMcontroller() +{ + return bHasOEMController; +} + +bool hasOEMLCDcontroller() +{ + return bHasOEMLCDController; +} + +bool hasHtrData() +{ + return bHasHtrData; +} + +void initBlueWireSerial() +{ + // 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 +#if defined(__arm__) || defined(__AVR__) + BlueWireSerial.begin(25000); + pinMode(Rx1Pin, INPUT_PULLUP); // required for MUX to work properly +#elif ESP32 + // ESP32 + BlueWireSerial.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 +} + +// 0x00 - Normal: BTC, with heater responding +// 0x01 - Error: BTC, heater not responding +// 0x02 - Special: OEM controller & heater responding +// 0x03 - Error: OEM controller, heater not responding +int getBlueWireStat() +{ + int stat = 0; + if(!bHasHtrData) { + stat |= 0x01; + } + if(bHasOEMController) { + stat |= 0x02; + } + return stat; +} + +const char* getBlueWireStatStr() +{ + static const char* BlueWireStates[] = { "BTC,Htr", "BTC", "OEM,Htr", "OEM" }; + + return BlueWireStates[getBlueWireStat()]; +} + +void reqPumpPrime(bool on) +{ + DefaultBTCParams.setPump_Prime(on); +} + + diff --git a/src/Protocol/BlueWireTask.h b/src/Protocol/BlueWireTask.h new file mode 100644 index 0000000..bc457d3 --- /dev/null +++ b/src/Protocol/BlueWireTask.h @@ -0,0 +1,40 @@ +/* + * This file is part of the "bluetoothheater" distribution + * (https://gitlab.com/mrjones.id.au/bluetoothheater) + * + * Copyright (C) 2018 Ray Jones + * Copyright (C) 2018 James Clark + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef __BLUEWIRETASK_H__ +#define __BLUEWIRETASK_H__ + +#include +#include "../Utility/UtilClasses.h" + +extern QueueHandle_t BlueWireMsgBuf; // cannot use general Serial.print etc from this task without causing conflicts +extern QueueHandle_t BlueWireRxQueue; // queue to pass down heater receive data +extern QueueHandle_t BlueWireTxQueue; // queue to pass down heater transmit data +extern SemaphoreHandle_t BlueWireSemaphore; // flag to indicate completion of heater data exchange + +const int BLUEWIRE_MSGQUEUESIZE = 192; +const int BLUEWIRE_DATAQUEUESIZE = 24; + +extern void BlueWireTask(void*); +extern CommStates CommState; + + +#endif \ No newline at end of file diff --git a/src/Protocol/Protocol.cpp b/src/Protocol/Protocol.cpp index c221a1e..1a2f607 100644 --- a/src/Protocol/Protocol.cpp +++ b/src/Protocol/Protocol.cpp @@ -54,15 +54,17 @@ CProtocol::getCRC() const // return true for CRC match bool -CProtocol::verifyCRC(bool bSilent) const +CProtocol::verifyCRC(std::function pushMsg) const { + char errmsg[32]; CModBusCRC16 CRCengine; uint16_t CRC = CRCengine.process(22, Data); // calculate CRC based on first 22 bytes of our data buffer uint16_t FrameCRC = getCRC(); bool bOK = (FrameCRC == CRC); - if(!bOK && !bSilent) { - DebugPort.printf("verifyCRC FAILED: calc: %04X data: %04X\r\n", CRC, FrameCRC); + if(!bOK) { + sprintf(errmsg, "verifyCRC FAILED: calc: %04X data: %04X\r\n", CRC, FrameCRC); + pushMsg(errmsg); } return bOK; // does it match the stored values? } @@ -430,16 +432,19 @@ CProtocolPackage::setRefTime() }*/ void -CProtocolPackage::reportFrames(bool isOEM) +CProtocolPackage::reportFrames(bool isOEM, std::function pushMsg) { - _timeStamp.report(); // absolute time + char msg[192]; + msg[0] = 0; + _timeStamp.report(msg); // absolute time if(isOEM) { - DebugReportFrame("OEM:", Controller, TERMINATE_OEM_LINE ? "\r\n" : " "); + DebugReportFrame("OEM:", Controller, TERMINATE_OEM_LINE ? "\r\n" : " ", msg); } else { - DebugReportFrame("BTC:", Controller, TERMINATE_BTC_LINE ? "\r\n" : " "); + DebugReportFrame("BTC:", Controller, TERMINATE_BTC_LINE ? "\r\n" : " ", msg); } - DebugReportFrame("HTR:", Heater, "\r\n"); + DebugReportFrame("HTR:", Heater, "\r\n", msg); + pushMsg(msg); } int diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h index fa6503b..0176122 100644 --- a/src/Protocol/Protocol.h +++ b/src/Protocol/Protocol.h @@ -95,7 +95,7 @@ public: void setCRC(); // calculate and set the CRC in the buffer void setCRC(uint16_t CRC); // set the CRC in the buffer uint16_t getCRC() const; // extract CRC value from buffer - bool verifyCRC(bool silent=false) const; // return true for CRC match + bool verifyCRC(std::function pushMsg) 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 @@ -214,7 +214,7 @@ public: int getAltitude() const { return Controller.getAltitude(); }; // void setRefTime(); - void reportFrames(bool isOEM); + void reportFrames(bool isOEM, std::function pushMsg); }; extern const CProtocolPackage& getHeaterInfo(); diff --git a/src/Protocol/SmartError.cpp b/src/Protocol/SmartError.cpp index a4f5e21..c9d771a 100644 --- a/src/Protocol/SmartError.cpp +++ b/src/Protocol/SmartError.cpp @@ -59,8 +59,7 @@ CSmartError::inhibit(bool reseterror) void CSmartError::monitor(const CProtocol& heaterFrame) { - bool bSilent = true; - if(heaterFrame.verifyCRC(bSilent)) { // check but don't report dodgy frames to debug + if(heaterFrame.verifyCRC(NULL)) { // check but don't report dodgy frames to debug // only accept valid heater frames! monitor(heaterFrame.getRunState()); } diff --git a/src/Protocol/TxManage.cpp b/src/Protocol/TxManage.cpp index 1676327..3fc4403 100644 --- a/src/Protocol/TxManage.cpp +++ b/src/Protocol/TxManage.cpp @@ -28,8 +28,6 @@ //#define DEBUG_THERMOSTAT -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 @@ -71,6 +69,7 @@ CTxManage::CTxManage(int TxGatePin, HardwareSerial& serial) : m_nTxGatePin = TxGatePin; _rawCommand = 0; m_HWTimer = NULL; + _callback = NULL; } // static function used for the tx gate termination @@ -198,7 +197,11 @@ CTxManage::PrepareFrame(const CProtocol& basisFrame, bool isBTCmaster) float tDelta = tCurrent - tDesired; float fTemp; #ifdef DEBUG_THERMOSTAT - DebugPort.printf("Window=%.1f tCurrent=%.1f tDesired=%.1f tDelta=%.1f\r\n", Window, tCurrent, tDesired, tDelta); + if(_callback) { + char msg[80]; + sprintf(msg, "Window=%.1f tCurrent=%.1f tDesired=%.1f tDelta=%.1f\r\n", Window, tCurrent, tDesired, tDelta); + _callback(msg); + } #endif Window /= 2; switch(ThermoMode) { @@ -224,7 +227,11 @@ CTxManage::PrepareFrame(const CProtocol& basisFrame, bool isBTCmaster) s8Temp = (int8_t)(tActual + 0.5); m_TxFrame.setTemperature_Actual(s8Temp); #ifdef DEBUG_THERMOSTAT - DebugPort.printf("Conventional thermostat mode: tActual = %d\r\n", u8Temp); + if(_callback) { + char msg[80]; + sprintf(msg, "Conventional thermostat mode: tActual = %d\r\n", u8Temp); + _callback(msg); + } #endif break; @@ -241,7 +248,11 @@ CTxManage::PrepareFrame(const CProtocol& basisFrame, bool isBTCmaster) } m_TxFrame.setTemperature_Actual(s8Temp); #ifdef DEBUG_THERMOSTAT - DebugPort.printf("Heater controlled windowed thermostat mode: tActual=%d\r\n", u8Temp); + if(_callback) { + char msg[80]; + sprintf(msg, "Heater controlled windowed thermostat mode: tActual=%d\r\n", u8Temp); + _callback(msg); + } #endif break; @@ -251,7 +262,11 @@ CTxManage::PrepareFrame(const CProtocol& basisFrame, bool isBTCmaster) // so create a desired "temp" according the the current hystersis tDelta /= Window; // convert tDelta to fraction of window (CAUTION - may be > +-1 !) #ifdef DEBUG_THERMOSTAT - DebugPort.printf("Linear window thermostat mode: Fraction=%f", tDelta); + if(_callback) { + char msg[80]; + DebugPort.printf("Linear window thermostat mode: Fraction=%f", tDelta); + _callback(msg); + } #endif fTemp = (m_TxFrame.getTemperature_Max() + m_TxFrame.getTemperature_Min()) * 0.5; // midpoint - tDelta = 0 hinges here tDelta *= (m_TxFrame.getTemperature_Max() - fTemp); // linear offset from setpoint @@ -264,8 +279,12 @@ CTxManage::PrepareFrame(const CProtocol& basisFrame, bool isBTCmaster) m_TxFrame.setHeaterDemand(s8Temp); m_TxFrame.setThermostatModeProtocol(0); // direct heater to use Hz Mode m_TxFrame.setTemperature_Actual(0); // must force actual to 0 for Hz mode -#ifdef DEBUG_THERMOSTAT - DebugPort.printf(" tDesired (pseudo Hz demand) = %d\r\n", u8Temp); +#ifdef DEBUG_THERMOSTAT + if(_callback) { + char msg[80]; + sprintf(msg, " tDesired (pseudo Hz demand) = %d\r\n", u8Temp); + _callback(msg); + } #endif break; } diff --git a/src/Protocol/TxManage.h b/src/Protocol/TxManage.h index cb2fcc7..820efed 100644 --- a/src/Protocol/TxManage.h +++ b/src/Protocol/TxManage.h @@ -27,6 +27,7 @@ class CTxManage const int m_nFrameTime = 14; const int m_nFrontPorch = 0; int m_sysUpdate; + std::function _callback; public: CTxManage(int TxGatePin, HardwareSerial& serial); @@ -40,6 +41,7 @@ public: const CProtocol& getFrame() const { return m_TxFrame; }; static void IRAM_ATTR callbackGateTerminate(); void queueSysUpdate(); // use to implant NV settings into heater + void setCallback(std::function fn) { _callback = fn; }; private: HardwareSerial& m_BlueWireSerial; diff --git a/src/Utility/DebugPort.h b/src/Utility/DebugPort.h index 417fd6e..d2df013 100644 --- a/src/Utility/DebugPort.h +++ b/src/Utility/DebugPort.h @@ -30,6 +30,6 @@ class CProtocol; extern ABTelnetSpy DebugPort; -void DebugReportFrame(const char* hdr, const CProtocol& Frame, const char* ftr); +void DebugReportFrame(const char* hdr, const CProtocol& Frame, const char* ftr, char* msg); #endif // __DEBUGPORT_H__ diff --git a/src/Utility/TempSense.cpp b/src/Utility/TempSense.cpp index d602db7..dd15beb 100644 --- a/src/Utility/TempSense.cpp +++ b/src/Utility/TempSense.cpp @@ -438,16 +438,22 @@ CBME280Sensor::getTemperature(float& tempReading, bool filtered) } bool -CBME280Sensor::getAltitude(float& reading) +CBME280Sensor::getAltitude(float& reading, bool fresh) { - reading = _bme.readAltitude(1013.25); //use standard atmosphere as reference + if(fresh) { + _fAltitude = _bme.readAltitude(1013.25); //use standard atmosphere as reference + } + reading = _fAltitude; return true; } bool -CBME280Sensor::getHumidity(float& reading) +CBME280Sensor::getHumidity(float& reading, bool fresh) { - reading = _bme.readHumidity(); + if(fresh) { + _fHumidity = _bme.readHumidity(); + } + reading = _fHumidity; return true; } @@ -489,6 +495,10 @@ CTempSense::startConvert() bool CTempSense::readSensors() { + float fDummy; + getAltitude(fDummy, true); + getHumidity(fDummy, true); + return DS18B20.readSensors(); } @@ -628,19 +638,19 @@ CTempSense::getTemperatureBME280(float& reading) } bool -CTempSense::getAltitude(float& reading) +CTempSense::getAltitude(float& reading, bool fresh) { if(BME280.getCount()) - return BME280.getAltitude(reading); + return BME280.getAltitude(reading, fresh); else return false; } bool -CTempSense::getHumidity(float& reading) +CTempSense::getHumidity(float& reading, bool fresh) { if(BME280.getCount()) - return BME280.getHumidity(reading); + return BME280.getHumidity(reading, fresh); else return false; } \ No newline at end of file diff --git a/src/Utility/TempSense.h b/src/Utility/TempSense.h index 8a35875..33429af 100644 --- a/src/Utility/TempSense.h +++ b/src/Utility/TempSense.h @@ -89,13 +89,15 @@ public: class CBME280Sensor : public CSensor { Adafruit_BME280 _bme; // I2C long _lastSampleTime; + float _fAltitude; + float _fHumidity; int _count; public: CBME280Sensor(); bool begin(int ID); bool getTemperature(float& tempReading, bool filtered) ; - bool getAltitude(float& reading); - bool getHumidity(float& reading); + bool getAltitude(float& reading, bool fresh=false); + bool getHumidity(float& reading, bool fresh=false); const char* getID(); int getCount() const { return _count; }; }; @@ -119,8 +121,8 @@ public: bool getTemperatureBME280(float& tempReading) ; // index is sensor discovery order on one-wire bus bool getTemperatureDS18B20Idx(int sensIdx, float& tempReading) ; // index is sensor discovery order on one-wire bus int getNumSensors() const; - bool getAltitude(float& reading); - bool getHumidity(float& reading); + bool getAltitude(float& reading, bool fresh=false); + bool getHumidity(float& reading, bool fresh=false); CBME280Sensor& getBME280() { return BME280; }; CDS18B20SensorSet& getDS18B20() { return DS18B20; }; static void format(char* msg, float fTemp); diff --git a/src/Utility/UtilClasses.cpp b/src/Utility/UtilClasses.cpp index 313e17b..88042f8 100644 --- a/src/Utility/UtilClasses.cpp +++ b/src/Utility/UtilClasses.cpp @@ -38,13 +38,16 @@ CommStates::set(eCS eState) { _State = eState; _Count = 0; - if(_report) { + if(_report && _callback != NULL) { static const char* stateNames[] = { - "Idle", "OEMCtrlRx", "OEMCtrlValidate", "HeaterRx1", "HeaterValidate1", "HeaterReport1", - "BTC_Tx", "HeaterRx2", "HeaterValidate2", "HeaterReport2", "TemperatureRead" + "Idle", "OEMCtrlRx", "OEMCtrlValidate", "HeaterRx1", "HeaterValidate1", "TxStart", + "TxInterval", "HeaterRx2", "HeaterValidate2", "ExchangeComplete" }; - if(_State == Idle) DebugPort.println(""); // clear screen - DebugPort.printf("State: %s\r\n", stateNames[_State]); + if(_State == Idle) + _callback("\r\n"); + char msg[32]; + sprintf(msg, "State: %s\r\n", stateNames[_State]); + _callback(msg); } } @@ -54,17 +57,6 @@ CommStates::collectData(CProtocol& Frame, uint8_t val, int limit) { // returns return _Count >= limit; } -bool -CommStates::collectDataEx(CProtocol& Frame, uint8_t val, int limit) { // returns true when buffer filled - // guarding against rogue rx kernel buffer stutters.... - if((_Count == 0) && (val != 0x76)) { - DebugPort.println("First heater byte not 0x76 - SKIPPING"); - return false; - } - Frame.Data[_Count++] = val; - return _Count >= limit; -} - bool CommStates::checkValidStart(uint8_t val) { @@ -87,6 +79,8 @@ CommStates::delayExpired() return(test >= 0); } + + CProfile::CProfile() { tStart = millis(); @@ -103,6 +97,8 @@ CProfile::elapsed(bool reset/* = false*/) return retval; } + + void DecodeCmd(const char* cmd, String& payload) { int val; diff --git a/src/Utility/UtilClasses.h b/src/Utility/UtilClasses.h index cc07859..9d4fe96 100644 --- a/src/Utility/UtilClasses.h +++ b/src/Utility/UtilClasses.h @@ -29,11 +29,14 @@ class CProtocol; // a class to track the blue wire receive / transmit states + +#define COMMSTATES_CALLBACK_SIGNATURE std::function CScallback + class CommStates { public: // comms states enum eCS { - Idle, OEMCtrlRx, OEMCtrlValidate, HeaterRx1, HeaterValidate1, HeaterReport1, BTC_Tx, HeaterRx2, HeaterValidate2, HeaterReport2,TemperatureRead + Idle, OEMCtrlRx, OEMCtrlValidate, HeaterRx1, HeaterValidate1, TxStart, TxInterval, HeaterRx2, HeaterValidate2, ExchangeComplete }; private: @@ -41,12 +44,14 @@ private: int _Count; unsigned long _delay; bool _report; + std::function _callback; public: CommStates() { _State = Idle; _Count = 0; _delay = millis(); - _report = REPORT_STATE_MACHINE_TRANSITIONS; + _report = false; + _callback = NULL; } void set(eCS eState); eCS get() { @@ -56,7 +61,6 @@ public: return _State == eState; } bool collectData(CProtocol& Frame, uint8_t val, int limit = 24); - bool collectDataEx(CProtocol& Frame, uint8_t val, int limit = 24); bool checkValidStart(uint8_t val); void setDelay(int ms); bool delayExpired(); @@ -67,6 +71,7 @@ public: bool isReporting() { return _report != 0; }; + void setCallback(std::function fn) { _callback = fn; }; }; @@ -128,19 +133,29 @@ public: void setRefTime() { refTime = millis(); }; - void report(bool isDelta) { + void report(bool isDelta, char* msg=NULL) { if(isDelta) { long delta = millis() - prevTime; - DebugPort.printf("%+8ldms ", delta); + if(msg) + sprintf(msg, "%+8ldms ", delta); + else + DebugPort.printf("%+8ldms ", delta); } else { prevTime = millis(); - DebugPort.printf("%8ldms ", prevTime - refTime); + if(msg) + sprintf(msg, "%8ldms ", prevTime - refTime); + else + DebugPort.printf("%8ldms ", prevTime - refTime); } }; - void report() { + void report(char* msg = NULL) { prevTime = millis(); - DebugPort.printf("%8ldms ", prevTime - refTime); + if(msg) { + sprintf(msg, "%8ldms ", prevTime - refTime); + } + else + DebugPort.printf("%8ldms ", prevTime - refTime); }; }; diff --git a/src/Utility/helpers.h b/src/Utility/helpers.h index fd530ac..679f640 100644 --- a/src/Utility/helpers.h +++ b/src/Utility/helpers.h @@ -45,6 +45,7 @@ extern void resetFuelGauge(); extern const char* getBlueWireStatStr(); extern bool hasOEMcontroller(); extern bool hasOEMLCDcontroller(); +extern bool hasHtrData(); extern int getBlueWireStat(); extern int getSmartError(); extern bool isCyclicActive(); @@ -73,8 +74,8 @@ extern CTempSense& getTempSensor() ; extern void reqHeaterCalUpdate(); -void setSSID(const char* name); -void setAPpassword(const char* name); +void setName(const char* name, int type); +void setPassword(const char* name, int type); extern void ShowOTAScreen(int percent=0, eOTAmodes updateType=eOTAnormal); diff --git a/src/WiFi/BTCWifi.cpp b/src/WiFi/BTCWifi.cpp index 1f80be2..77e896a 100644 --- a/src/WiFi/BTCWifi.cpp +++ b/src/WiFi/BTCWifi.cpp @@ -150,7 +150,7 @@ bool initWifi() } // WiFi.setTxPower(WIFI_POWER_MINUS_1dBm); - // WiFi.setTxPower(WIFI_POWER_19_5dBm); + WiFi.setTxPower(WIFI_POWER_19_5dBm); return retval; } diff --git a/src/WiFi/BrowserUpload.cpp b/src/WiFi/BrowserUpload.cpp index b006d93..7172079 100644 --- a/src/WiFi/BrowserUpload.cpp +++ b/src/WiFi/BrowserUpload.cpp @@ -118,9 +118,6 @@ sBrowserUpload::fragment(HTTPUpload& upload) ::SPIFFS.remove(SrcFile.name.c_str()); // remove the bad file from SPIFFS return -2; } -#ifdef SSL_SERVER - upload.totalSize += upload.currentSize; -#endif } } else { @@ -130,9 +127,6 @@ sBrowserUpload::fragment(HTTPUpload& upload) Update.printError(DebugPort); return -3; } -#ifdef SSL_SERVER - upload.totalSize += upload.currentSize; -#endif } return upload.totalSize; }