From 8006b98ba23219337b871c2137f842010900d528 Mon Sep 17 00:00:00 2001 From: Ray Jones Date: Fri, 19 Jun 2020 10:43:50 +1000 Subject: [PATCH] Added missing files --- src/OLED/433MHzScreen.cpp | 332 ++++++++++++++++ src/OLED/433MHzScreen.h | 54 +++ src/Protocol/433MHz.cpp | 382 ++++++++++++++++++ src/Protocol/433MHz.h | 68 ++++ src/Protocol/AltController.cpp | 608 +++++++++++++++++++++++++++++ src/Protocol/AltController.h | 60 +++ src/Protocol/AltControllerTask.cpp | 311 +++++++++++++++ src/Protocol/AltControllerTask.h | 131 +++++++ src/Protocol/CommsTask.h | 124 ++++++ src/Protocol/HeaterManager.cpp | 396 +++++++++++++++++++ src/Protocol/HeaterManager.h | 70 ++++ 11 files changed, 2536 insertions(+) create mode 100644 src/OLED/433MHzScreen.cpp create mode 100644 src/OLED/433MHzScreen.h create mode 100644 src/Protocol/433MHz.cpp create mode 100644 src/Protocol/433MHz.h create mode 100644 src/Protocol/AltController.cpp create mode 100644 src/Protocol/AltController.h create mode 100644 src/Protocol/AltControllerTask.cpp create mode 100644 src/Protocol/AltControllerTask.h create mode 100644 src/Protocol/CommsTask.h create mode 100644 src/Protocol/HeaterManager.cpp create mode 100644 src/Protocol/HeaterManager.h diff --git a/src/OLED/433MHzScreen.cpp b/src/OLED/433MHzScreen.cpp new file mode 100644 index 0000000..aef18c0 --- /dev/null +++ b/src/OLED/433MHzScreen.cpp @@ -0,0 +1,332 @@ +/* + * This file is part of the "bluetoothheater" distribution + * (https://gitlab.com/mrjones.id.au/bluetoothheater) + * + * Copyright (C) 2020 Ray Jones + * + * 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 . + * + */ + + +/////////////////////////////////////////////////////////////////////////// +// +// C433MHzScreen +// +// This screen allows the pairing of 433MHz remotes +// +/////////////////////////////////////////////////////////////////////////// + +#include "433MHzScreen.h" +#include "KeyPad.h" +#include "fonts/Arial.h" +#include "../RTC/Clock.h" +#include "../Utility/macros.h" +#include "../Utility/NVStorage.h" +#include "../Protocol/433MHz.h" +#include "fonts/Icons.h" + +extern bool pair433MHz; +static const int column[] = { 64, 84, 104, 120 }; +static const int line[] = { 42, 31, 20 }; + +C433MHzScreen::C433MHzScreen(C128x64_OLED& display, CScreenManager& mgr) : CUIEditScreen(display, mgr) +{ + _initUI(); +} + +bool +C433MHzScreen::onSelect() +{ + CScreen::onSelect(); + _initUI(); + pair433MHz = true; + UHFremote.getCodes(_rawCodes); + return true; +} + +void +C433MHzScreen::onExit() +{ + pair433MHz = false; +} + + +void +C433MHzScreen::_initUI() +{ + CUIEditScreen::_initUI(); + _repeatCount = 0; +} + + +bool +C433MHzScreen::show() +{ + _display.clearDisplay(); + + if(!CUIEditScreen::show()) { + + _showTitle("433MHz Remote"); + + _drawBitmap(61, 13, medStopIconInfo); + _drawBitmap(81, 12, medStartIconInfo); + _drawBitmap(100, 14, dnIconInfo); + _drawBitmap(116, 13, upIconInfo); + + _printMenuText(5, line[2], "Remote 1", _rowSel == 3 && _colSel == 0); + _printMenuText(5, line[1], "Remote 2", _rowSel == 2 && _colSel == 0); + _printMenuText(5, line[0], "Remote 3", _rowSel == 1 && _colSel == 0); + + if(_rowSel == 0) { + _printMenuText(_display.xCentre(), 53, " \021 Exit \020 ", true, eCentreJustify); + } + else { + switch(_colSel) { + case 0: + _printMenuText(_display.xCentre(), 54, " \020 to start teaching ", false, eCentreJustify); + break; + case 1: + _printMenuText(_display.xCentre(), 54, " Teach \"Off\"", false, eCentreJustify); + break; + case 2: + _printMenuText(_display.xCentre(), 54, " Teach \"On\" ", false, eCentreJustify); + break; + case 3: + _printMenuText(_display.xCentre(), 54, " Teach \"Decrease\" ", false, eCentreJustify); + break; + case 4: + _printMenuText(_display.xCentre(), 54, " Teach \"Increase\" ", false, eCentreJustify); + break; + } + } + } + + return true; +} + +bool +C433MHzScreen::animate() +{ + + if(_saveBusy()) { + return false; + } + + if(UHFremote.available()) { + UHFremote.read(_code); + DebugPort.printf("UHF remote code = %08lX\r\n", _code); + if(_colSel) { + if(_code) { + _rawCodes[_rowSel-1][_colSel-1] = _code; + } + else { + _colSel++; + WRAPLIMITS(_colSel, 0, 4); + } + } + } + for(int row = 0; row < 3; row++) { + for(int col = 0; col < 4; col++) { + int xPos = column[col]; + int yPos = line[row]; + bool rowColMatch = (row == (_rowSel-1)) && (col == (_colSel-1)); + if(_rawCodes[row][col]) { + if(rowColMatch) + _printMenuText(xPos, yPos, "*", true, eCentreJustify); + else + _printInverted(xPos, yPos, "*", _rawCodes[row][col] == _code, eCentreJustify); + } + else { + _printMenuText(xPos, yPos, " ", rowColMatch, eCentreJustify); + } + } + } + return true; +} + +bool +C433MHzScreen::keyHandler(uint8_t event) +{ + + if(CUIEditScreen::keyHandler(event)) { // manage password collection and NV save confirm + return true; + } + + if(event & keyPressed) { + _repeatCount = 0; + // press CENTRE + if(event & key_Centre) { + } + // press LEFT + if(event & key_Left) { + if(_rowSel == 0) { + _ScreenManager.prevMenu(); + } + else { + _colSel--; + WRAPLOWERLIMIT(_colSel, 0, 4); + } + } + // press RIGHT + if(event & key_Right) { + if(_rowSel == 0) { + _ScreenManager.nextMenu(); + } + else { + _colSel++; + WRAPUPPERLIMIT(_colSel, 4, 0); + } + } + // press UP + if(event & key_Up) { + _rowSel++; + _colSel = 0; + UPPERLIMIT(_rowSel, 4); + } + // press DOWN + if(event & key_Down) { + _rowSel--; + _colSel = 0; + LOWERLIMIT(_rowSel, 0); + } + } + + if(event & keyRepeat) { + _repeatCount++; + UPPERLIMIT(_repeatCount, 5); + if(_repeatCount == 2) { + if(_rowSel && _colSel) { + _rawCodes[_rowSel-1][_colSel-1] = 0; // scrub code for button + _colSel++; + WRAPLIMITS(_colSel, 0, 4); + } + } + } + + if(event & keyReleased) { + // press CENTRE + if(event & key_Centre) { + if(_rowSel == 0) { + _ScreenManager.selectMenu(CScreenManager::RootMenuLoop); // force return to main menu + } + else if(_repeatCount == 0) { + _confirmSave(); // enter save confirm mode + _rowSel = 0; + } + } + } + + _ScreenManager.reqUpdate(); + return true; +} + +// Data word in NV ram is stored as follows +// +// | 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +// | | | | | +// | | | | Enabled | Key Codes | +// | Unique ID (20 bits) | U D R S| KCU | KCD | KCR | KCS | +// +// Key enabled bits +// U = Up - key code in b7-b6 +// D = Down - key code in b5-b5 +// R = Run - key code in b3-b2 +// S = Stop - key code in b1-b0 +// +// key code bits +// 00 => 0x01 +// 01 => 0x02 +// 10 => 0x04 +// 11 => 0x08 +// +/*void +C433MHzScreen::_decode(int idx) +{ + unsigned long code = _savedCodes[idx]; + for(int i=0; i<4; i++) { + int mask = 0x100 << i; + if(code & mask) { + int uniqueID = (code >> 8) & 0xFFFFF0; + int shift = (code >> (i*2)) & 0x3; + int keyCode = 1 << shift; + _rawCodes[idx][i] = uniqueID | keyCode; + } + else + _rawCodes[idx][i] = 0; + } +}*/ + +/*int +C433MHzScreen::_encode(int idx) +{ + unsigned long uniqueCode = _rawCodes[idx][0] & 0xFFFFF0; + + // confirm all recorded keys share the same unique code + for(int i=1; i<4; i++) { + if(_rawCodes[idx][i] && (uniqueCode != (_rawCodes[idx][i] & 0xFFFFF0))) { + return -1; + } + } + + // start building the encoded value for NV storage + unsigned long encoded = uniqueCode << 8; + for(int i=0; i<4; i++) { + if(_rawCodes[idx][i]) { + int keyCode = _rawCodes[idx][i] & 0xf; + switch(keyCode) { + case 1: + encoded |= (0 << i*2); + break; + case 2: + encoded |= (1 << i*2); + break; + case 4: + encoded |= (2 << i*2); + break; + case 8: + encoded |= (3 << i*2); + break; + default: + return -2; + break; + } + encoded |= (0x100 << i); + } + } + _savedCodes[idx] = encoded; + return 0; +}*/ + +void +C433MHzScreen::_saveNV() +{ + UHFremote.saveNV(_rawCodes); + // sUserSettings userSettings = NVstore.getUserSettings(); + // for(int i=0; i<3; i++) { + // int err = _encode(i); + // if(err != 0) { + // DebugPort.printf("Error encoding UHF code (%d)\r\n", err); + // return; + // } + // userSettings.UHFcode[i] = _savedCodes[i]; + // } + + // DebugPort.println("UHF Remote encodes"); + // for(int i = 0; i<3; i++) { + // DebugPort.printf("0x%08lX 0x%08lX 0x%08lX 0x%08lX => 0x%08lX\r\n", _rawCodes[i][0], _rawCodes[i][1], _rawCodes[i][2], _rawCodes[i][3], _savedCodes[i]); + // } + // NVstore.setUserSettings(userSettings); + // NVstore.save(); +} diff --git a/src/OLED/433MHzScreen.h b/src/OLED/433MHzScreen.h new file mode 100644 index 0000000..8c5bd41 --- /dev/null +++ b/src/OLED/433MHzScreen.h @@ -0,0 +1,54 @@ +/* + * This file is part of the "bluetoothheater" distribution + * (https://gitlab.com/mrjones.id.au/bluetoothheater) + * + * Copyright (C) 2020 Ray Jones + * + * 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 __433MHZSCREEN_H__ +#define __433MHZSCREEN_H__ + +#include +#include "UIEditScreen.h" +#include "../RTC/BTCDateTime.h" + +class C128x64_OLED; +class CScreenManager; +class CProtocol; + +class C433MHzScreen : public CUIEditScreen { + void _initUI(); + // void _decode(int idx); + // int _encode(int idx); + void _saveNV(); + unsigned long _code; + // unsigned long _savedCodes[3]; + unsigned long _rawCodes[3][4]; + unsigned long _ID; + uint8_t _defined; + uint8_t _keyCode; + uint8_t _repeatCount; +public: + C433MHzScreen(C128x64_OLED& display, CScreenManager& mgr); + bool onSelect(); + void onExit(); + bool show(); + bool animate(); + bool keyHandler(uint8_t event); +}; + +#endif diff --git a/src/Protocol/433MHz.cpp b/src/Protocol/433MHz.cpp new file mode 100644 index 0000000..52a8901 --- /dev/null +++ b/src/Protocol/433MHz.cpp @@ -0,0 +1,382 @@ +/* + * This file is part of the "bluetoothheater" distribution + * (https://gitlab.com/mrjones.id.au/bluetoothheater) + * + * Copyright (C) 2020 Ray Jones + * + * 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 +#include "433MHz.h" +#include "../cfg/pins.h" +#include "../Utility/macros.h" +#include "../Utility/NVStorage.h" +#include "../Utility/helpers.h" + +#define DEBUG_433MHz + +C433MHzRemote UHFremote; + +static void IRAM_ATTR rmt_driver_isr_default(void *arg); + + +C433MHzRemote::C433MHzRemote() +{ + _rxQueue = NULL; + _taskHandle = NULL; + _runState = 0; + _prevCode = 0; + _timeout = 0; + _debug = false; +} + +C433MHzRemote::~C433MHzRemote() +{ + end(); +} + +void +C433MHzRemote::_staticTask(void* arg) +{ + C433MHzRemote* pThis = (C433MHzRemote*)arg; + + pThis->_task(); + + vTaskDelete(NULL); +} + + +void +C433MHzRemote::_task() +{ + rmt_rx_start(_rxCfg.channel, true); + _runState = 1; + while(_runState == 1) { + _doComms(); + delay(1); + } + rmt_rx_stop(_rxCfg.channel); + _runState = 0; +} + + +void +C433MHzRemote::_doComms() +{ + // wait for ring buffer response, or time out + size_t rx_size; + rmt_item32_t* rxItems = (rmt_item32_t *)xRingbufferReceive(_ringbuffer, &rx_size, 45); + + if (rxItems) { + _decodeRxItems(rxItems, rx_size / 4); + vRingbufferReturnItem(_ringbuffer, (void *)rxItems); + } + + if(_timeout) { + long tDelta = xTaskGetTickCount() - _timeout; + if(tDelta >= 0) { + _timeout = 0; + _prevCode = 0; + if(_rxQueue) + xQueueSend(_rxQueue, &_prevCode, 0); // inject no button press + } + } +} + +bool +C433MHzRemote::_decodeRxItems(const rmt_item32_t* rxItems, int size) +{ +// #ifdef DEBUG_433MHz +// Serial.printf("433MHz RxItems = %d\r\n", size); +// for(int i=0; i 0.6ms + // if(rxItems[i].duration0 > 600) + if(rxItems[i].duration0 > meanBitTime/2) + newCode |= 0x0001; + } + else { + // Serial.printf("433MHz remote @ transition %d: bitTime=%d lvl0=%d lvl1=%d?\r\n", i, bitTime, rxItems[i].level0, rxItems[i].level1); + newCode = 0; + return false; + } + } + // Serial.printf("433MHz val = 0x%08lX (%d)\r\n", newCode, size); + if(_prevCode != newCode) { + _prevCode = newCode; + if(_rxQueue) + xQueueSend(_rxQueue, &newCode, 0); // queue new button press +// #ifdef DEBUG_433MHz + if(_debug) { + Serial.printf("433MHz RxItems = %d (%d)\r\n", size, meanBitTime); + for(int i=0; i 6ms no transitions => end of Rx + + ESP_ERROR_CHECK(rmt_config(&_rxCfg)); + ESP_ERROR_CHECK(rmt_driver_install(_rxCfg.channel, 512, 0)); + // ringbuffer for rx + ESP_ERROR_CHECK(rmt_get_ringbuf_handle(_rxCfg.channel, &_ringbuffer)); + + _rxQueue = xQueueCreate(4, sizeof(unsigned long)); + + xTaskCreate(_staticTask, + "UHFremoteTask", + 4000, + this, + TASK_PRIORITY_HEATERCOMMS, + &_taskHandle); + +} + +void +C433MHzRemote::end() +{ + DebugPort.printf("Stopping UHF remote task %d\r\n", _runState); + if(_runState == 1) { // check task is running + _runState = 2; // ask task to stop + DebugPort.println("Stopping UHF remote task wait"); + while(_runState != 0) { + vTaskDelay(1); + } + _taskHandle = NULL; + } + + ESP_ERROR_CHECK(rmt_driver_uninstall(_rxCfg.channel)); + _ringbuffer = NULL; +} + +bool +C433MHzRemote::available() +{ + unsigned long test; + return xQueuePeek(_rxQueue, &test, 0) != 0; +} + +bool +C433MHzRemote::read(unsigned long& val) +{ + return xQueueReceive(_rxQueue, &val, 0) != 0; +} + +// Data word in NV ram is stored as follows +// +// | 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +// | | | | | +// | | | | Enabled | Key Codes | +// | Unique ID (20 bits) | U D R S| KCU | KCD | KCR | KCS | +// +// Key enabled bits +// U = Up - key code in b7-b6 +// D = Down - key code in b5-b5 +// R = Run - key code in b3-b2 +// S = Stop - key code in b1-b0 +// +// key code bits +// 00 => 0x01 +// 01 => 0x02 +// 10 => 0x04 +// 11 => 0x08 +// +void +C433MHzRemote::_readNV() +{ + for(int rmt=0; rmt<3; rmt++) { + unsigned long code = NVstore.getUserSettings().UHFcode[rmt]; + for(int i=0; i<4; i++) { + int mask = 0x100 << i; + if(code & mask) { + int uniqueID = (code >> 8) & 0xFFFFF0; + int shift = (code >> (i*2)) & 0x3; + int keyCode = 1 << shift; + _rawCodes[rmt][i] = uniqueID | keyCode; + } + else + _rawCodes[rmt][i] = 0; + } + DebugPort.printf("0x%08lX => 0x%08lX 0x%08lX 0x%08lX 0x%08lX\r\n", code, _rawCodes[rmt][0], _rawCodes[rmt][1], _rawCodes[rmt][2], _rawCodes[rmt][3]); + } +} + +int +C433MHzRemote::saveNV(unsigned long codes[3][4]) +{ + sUserSettings userSettings = NVstore.getUserSettings(); + + for(int rmt=0; rmt<3; rmt++) { + unsigned long uniqueCode = codes[rmt][0] & 0xFFFFF0; + + // confirm all recorded keys share the same unique code + for(int i=1; i<4; i++) { + if(codes[rmt][i] && (uniqueCode != (codes[rmt][i] & 0xFFFFF0))) { + return -1; + } + } + + // start building the encoded value for NV storage + unsigned long encoded = uniqueCode << 8; + for(int i=0; i<4; i++) { + if(codes[rmt][i]) { + int keyCode = codes[rmt][i] & 0xf; + switch(keyCode) { + case 1: + encoded |= (0 << i*2); + break; + case 2: + encoded |= (1 << i*2); + break; + case 4: + encoded |= (2 << i*2); + break; + case 8: + encoded |= (3 << i*2); + break; + default: + return -2; + break; + } + encoded |= (0x100 << i); + } + } + userSettings.UHFcode[rmt] = encoded; + + DebugPort.printf("0x%08lX 0x%08lX 0x%08lX 0x%08lX => 0x%08lX\r\n", codes[rmt][0], codes[rmt][1], codes[rmt][2], codes[rmt][3], encoded); + } + + NVstore.setUserSettings(userSettings); + NVstore.save(); + + for(int rmt=0; rmt<3; rmt++) { + for(int i=0; i<4; i++) { + _rawCodes[rmt][i] = codes[rmt][i]; + } + } + + return 0; +} + +void +C433MHzRemote::getCodes(unsigned long codes[3][4]) +{ + for(int rmt=0; rmt<3; rmt++) { + for(int i=0; i<4; i++) { + codes[rmt][i] = _rawCodes[rmt][i]; + } + } +} + +void +C433MHzRemote::manage() +{ + if(available()) { + unsigned long code; + read(code); + DebugPort.printf("UHF remote code = %08lX\r\n", code); + + if(code) { // only react to an actual code, not release + const int IDmatch = (code << 8) & 0xfffff000; + int rmt; + // find a mtaching unique ID + for(rmt=0; rmt<3; rmt++) { + if( IDmatch == (NVstore.getUserSettings().UHFcode[rmt] & 0xfffff000) ) { + break; + } + } + if(rmt == 3) + return; // match not found - abort + + const int subCode = code & 0xf; + if(subCode == (_rawCodes[rmt][0] & 0xf) ) { + DebugPort.println("UHF stop request!"); + requestOff(); + } + if(subCode == (_rawCodes[rmt][1] & 0xf) ) { + DebugPort.println("UHF start request!"); + requestOn(); + } + if(subCode == (_rawCodes[rmt][2] & 0xf) ) { + DebugPort.println("UHF dec temp request!"); + CDemandManager::deltaDemand(-1); + } + if(subCode == (_rawCodes[rmt][3] & 0xf) ) { + DebugPort.println("UHF inc temp request!"); + CDemandManager::deltaDemand(+1); + } + } + } +} + +void +C433MHzRemote::enableISR(bool state) +{ + rmt_set_rx_intr_en(_rxCfg.channel, state); + rmt_set_err_intr_en(_rxCfg.channel, state); +} + +extern C433MHzRemote UHFremote; + diff --git a/src/Protocol/433MHz.h b/src/Protocol/433MHz.h new file mode 100644 index 0000000..8132094 --- /dev/null +++ b/src/Protocol/433MHz.h @@ -0,0 +1,68 @@ +/* + * This file is part of the "bluetoothheater" distribution + * (https://gitlab.com/mrjones.id.au/bluetoothheater) + * + * Copyright (C) 2020 Ray Jones + * + * 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 __433MHzREMOTE_H__ +#define __433MHzREMOTE_H__ + +#include +#include "../Utility/UtilClasses.h" +#include "driver/rmt.h" + +class C433MHzRemote { +protected: + static void _staticTask(void* arg); + rmt_config_t _rxCfg; + RingbufHandle_t _ringbuffer; + QueueHandle_t _rxQueue; + TaskHandle_t _taskHandle; + int _runState; + unsigned long _prevCode; + unsigned long _timeout; + unsigned long _rawCodes[3][4]; + bool _debug; + + void _task(); + + void _doComms(); + bool _decodeRxItems(const rmt_item32_t* rxItems, int size); + // NV storage + void _readNV(); + +public: + C433MHzRemote(); + ~C433MHzRemote(); + void begin(gpio_num_t pin, rmt_channel_t channel); + void end(); + + bool available(); + bool read(unsigned long& val); + void manage(); + + void getCodes(unsigned long codes[3][4]); + + // NV storage + int saveNV(unsigned long codes[3][4]); + void enableISR(bool state); +}; + +extern C433MHzRemote UHFremote; + + +#endif \ No newline at end of file diff --git a/src/Protocol/AltController.cpp b/src/Protocol/AltController.cpp new file mode 100644 index 0000000..0551015 --- /dev/null +++ b/src/Protocol/AltController.cpp @@ -0,0 +1,608 @@ +/* + * This file is part of the "bluetoothheater" distribution + * (https://gitlab.com/mrjones.id.au/bluetoothheater) + * + * Copyright (C) 2020 Ray Jones + * + * 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 +#include "AltControllerTask.h" +#include "AltController.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" +#include "../Utility/DemandManager.h" + +#define MASK_FAN 0x0800 +#define MASK_PUMP 0x0400 +#define MASK_PLATEAU 0x0200 +#define MASK_GLOW 0x0100 +#define MASK_STOPPING 0x0080 +#define MASK_ON 0x0040 + +void matchDemand(int desired); + +/* known command values + 0xA0 - status/ Rx code 5 + 0xA1 - switch to thermo mode 'h7' - get 0x5xxx messages in thermo... + 0xA2 - power on/off + 0xA3 - increase demand (h1-h6) (also used to leave thermo mode ('h7' -> 'h1')) + 0xA4 - decrease demand (h1-h6) + 0xA5 - ?? + 0xA6 - Primary get status + 0xA7 - status (alternates status and Rx code 9... when stopped) + 0xA8 - returns status + 0xA9 - returns status + 0xAA - enter pump edit mode ->0x3xxx returned / running minimum power (alternates status and Rx code 3...) + 0xAB - get voltage + 0xAC - prime + 0xAD - ? + 0xAE - toggle plateau + 0xAF - get body temp + + 0x70 - maintain power (thermostat) + 0x71 - increase power (thermostat) + 0x72 - decrease power (thermostat) + + EDIT mode: + when you send 0xAA and 0x3yxx is returned, you can step thru 'y' by using 0xAD + ie 0x30xx, 0x31xx, 0x32xx, 0x33xx, 0x34xx, 0x35xx - Those seem to be the 6 set points. + Using 0xA3 and 0xA4 you can increase or decrease 'xx' of the selected 'y' field + 0xA2 then saves the new setting (but stops the heater if running!) + + Have not found a fan speed anywhere... + + */ + +int +sAltHeaterData::errState() +{ +/* convert fault codes from the ECU to classic codes used by more capable unit + A02 -> E-01 Power/voltage problem Normal range: 24V (18-32V), 12V (9-16V) + A03 -> E-04 Oil pump failure + A04 -> E-03 Ignition plug failure + A05 -> E-06 Fan failure + A06 -> E-09 Body Sensor failure + A07 -> E-10 Unsuccessful startup + A08 -> E-05 High temperature alarm (inlet air > 50 °C; chassis > 200 ° C) + A09 -> E-08 Flameout alarm + + Note that classic errors must +1 of that displayed +*/ + switch(Error) { + case 2: return 2; // low volts -> E=01 + case 3: return 5; // pump -> E-04 + case 4: return 4; // glow plug -> E-03 + case 5: return 7; // fan -> E-06 + case 6: return 10; // sensor -> E-09 + case 7: return 11; // start fail -> E-10 + case 8: return 6; // overtemp -> E-05 + case 9: return 9; // flameout -> E-08 + default: return 0; + } +} + +int +sAltHeaterData::runState() +{ + if(HeaterOn == 0) { + return 0; + } + else { + if(Stopping) { + return 7; + } + else { + if(GlowOn) { + if(!PumpOn) + return 9; // heating glow plug + else + return 2; // igniting + } + else { + return 5; + } + } + } +} + +float +sAltHeaterData::getPumpRate() +{ + if(PumpOn) { + if(INBOUNDS(Demand, 0, 5) && pumpRate[Demand] > 0) + return pumpRate[Demand] * 0.1; + else + return -1; + } + else { + return 0; + } +} + +float +sAltHeaterData::getDesiredPumpRate(int Idx) +{ + if(INBOUNDS(Idx, 0, 5) && pumpRate[Idx] > 0) + return pumpRate[Idx] * 0.1; + else + return -1; +} + +void +sAltHeaterData::decodeThermoDemand(int rxData) +{ + Demand = rxData & 0x07; + thermoMode = true; +} + +void +sAltHeaterData::decodeError(int rxData) +{ + Error = rxData & 0x0f; +} + +void +sAltHeaterData::decodePumpRate(int rxData) +{ + int index = (rxData & 0xF00) >> 8; + int rate = rxData & 0xFF; + + if(INBOUNDS(index, 0, 5)) { + pumpRate[index] = rate; + } +} + +void +sAltHeaterData::decodeVolts(int rxData) +{ + Volts = rxData & 0x1f; + FilteredSamples.ipVolts.update(Volts); + FilteredSamples.FastipVolts.update(Volts); +} + +void +sAltHeaterData::decodeBodyT(int rxData) +{ + BodyT = (rxData & 0xFFF); +} + + +void +sAltHeaterData::decodeStatus(int rxData) +{ + if(rxData & MASK_ON) { + HeaterOn = true; + FanOn = rxData & MASK_FAN ? true : false; + GlowOn = rxData & MASK_GLOW ? true : false; + PumpOn = rxData & MASK_PUMP ? true : false; + Plateau = rxData & MASK_PLATEAU ? true : false; + Stopping = rxData & MASK_STOPPING ? true : false; + int dmd = rxData & 0x07; + if(dmd != 6) { + Demand = dmd; + thermoMode = false; + } + else { + thermoMode = true; + } + } + else { + HeaterOn = false; + FanOn = false; + GlowOn = false; + PumpOn = false; + Plateau = false; + Stopping = false; + Error = 0; + Volts = rxData & 0x1f; + thermoMode = false; + + FilteredSamples.ipVolts.update(Volts); + FilteredSamples.FastipVolts.update(Volts); + } +} + +sAltHeaterData::sAltHeaterData() +{ + init(); +} + +void +sAltHeaterData::init() +{ + thermoMode = false; + Active = 0; + HeaterOn = false; + Stopping = false; + GlowOn = false; + FanOn = false; + PumpOn = false; + Plateau = false; + Demand = 0; + BodyT = -1; + Volts = 0; + Error = 0; + for(int i=0; i<6; i++) pumpRate[i] = -1; +} + +float +sAltHeaterData::getBodyTemp() +{ + return BodyT; // TODO: map to real world somehow +} + +void +sAltHeaterData::report() +{ + char msg[80]; + sprintf(msg, "On:%d Fan:%d Glow:%d Pump:%d Plateau:%d Demand:%d Battery:%d BodyT:%d\r\n", + HeaterOn, + FanOn, + GlowOn, + PumpOn, + Plateau, + Demand, + Volts, + BodyT); + Serial.print(msg); +} + +void +CAltCommsTask::_decode(int rxData) +{ + unsigned int ID = rxData >> 12; + switch(ID) { + case 0x0: break; + case 0x1: break; + case 0x2: break; + case 0x3: + _htrData.decodePumpRate(rxData); + break; // 0x3yxx y = H1(0) - h6(5) xx = Hz x10 + case 0x4: + _htrData.decodeBodyT(rxData); + break; + case 0x5: + _htrData.decodeThermoDemand(rxData); + break; // heat bars on OEM LCD in thermo mode 0=1 bar + case 0x6: + _htrData.decodeStatus(rxData); + break; + case 0x7: break; + case 0x8: + _htrData.decodeError(rxData); + break; + case 0x9: break; // 0x9009 in response to A7 when stopped... + case 0xa: + _htrData.decodeVolts(rxData); + break; + case 0xb: break; + case 0xc: break; + case 0xd: break; + case 0xe: break; + case 0xf: break; + } + _htrData.Active = millis() + 5000; +} + +void +CAltCommsTask::reportDecode() +{ + _htrData.report(); +} + +float +CAltCommsTask::getFanRPM() +{ + return _htrData.FanOn ? -1 : 0; +} + +float +CAltCommsTask::getActualPumpRate() +{ + return _htrData.getPumpRate(); +} + +float +CAltCommsTask::getDesiredPumpRate(int idx) +{ + return _htrData.getDesiredPumpRate(idx); +} + +float +CAltCommsTask::getGlow() +{ + return _htrData.GlowOn ? -1 : 0; +} + +float +CAltCommsTask::getBodyTemp() +{ + return _htrData.getBodyTemp(); +} + +int +CAltCommsTask::getRunState() +{ + return _htrData.runState(); +} + +int +CAltCommsTask::getErrState() +{ + return _htrData.errState(); +} + +void +CAltCommsTask::reqPower() +{ + putTxQueue(0xA2); +} + +void +CAltCommsTask::_reqStatus() +{ + putTxQueue(0xA6); +} + +void +CAltCommsTask::_reqVolts() +{ + putTxQueue(0xAB); +} + + +void +CAltCommsTask::_reqBodyT() +{ + putTxQueue(0xAF); +} + +void +CAltCommsTask::_reqDemand(int dir) +{ + if(_htrData.thermoMode) + putTxQueue(0xA1); // switch from thermo mode + + if(dir == 0) + putTxQueue(0xA6); + else if(dir > 0) + putTxQueue(0xA3); + else + putTxQueue(0xA4); +} + +void +CAltCommsTask::_reqThermo(int delta) +{ + if(!_htrData.thermoMode) + putTxQueue(0xA1); // switch to thermo mode + + if(delta == 0) + putTxQueue(0x70); + else if(delta > 0) + putTxQueue(0x71); + else + putTxQueue(0x72); +} + +void checkAltTxEvents() +{ +} + +void +CAltCommsTask::manage() +{ + static bool flipflop = false; + if(_connState == 0) { + _doStartupProbe(); + } + + else if(_connState == 1) { + long tDelta = xTaskGetTickCount() - _tPause; + if(tDelta >= 0) { + _tPause += 1000; + + if(CDemandManager::isThermostat()) { + _doThermo(); + } + else { + // _reqStatus(); +// _reqDemand(0); + _matchDemand(CDemandManager::getPumpHz()); + } + + if(_htrData.HeaterOn) { + if((flipflop = !flipflop)) + _reqVolts(); + else + _reqBodyT(); + } + } + } +} + +void +CAltCommsTask::_doStartupProbe() +{ + // interrogate the ehater and extract the fixed pump rates for each heat demand setting H1->H6 + if(_startup.state == 0) { + // erase pump rate record + _htrData.init(); + _startup.pumpIdx = 0; + putTxQueue(0xAA); // request initial pump rate + _tPause = xTaskGetTickCount() + 600; + _startup.state++; + } + + else if(_startup.state == 1) { + if(_htrData.pumpRate[_startup.pumpIdx] > 0) { + _startup.state++; + } + else { + long tDelta = xTaskGetTickCount() - _tPause; + if(tDelta >= 0) { + _startup.state = 0; + } + } + } + + else if(_startup.state == 2) { + if(_htrData.pumpRate[5] < 0) { + _startup.pumpIdx++; + if(_startup.pumpIdx == 6) { + _startup.state = 0; + } + else { + putTxQueue(0xAD); // req next pump rate + _tPause = xTaskGetTickCount() + 600; + _startup.state++; + } + } + else { + putTxQueue(0xAA); // finished - leave + _tPause = xTaskGetTickCount() + 600; + _connState = 1; + } + } + + else if(_startup.state == 3) { + if(_htrData.pumpRate[_startup.pumpIdx] > 0) { + _startup.state = 2; + } + else { + long tDelta = xTaskGetTickCount() - _tPause; + if(tDelta >= 0) { + _startup.state = 0; + } + } + } +} + + +void +CAltCommsTask::checkEvents() +{ + int rxHeater; + if(AltCommsTask.getRxQueue(rxHeater)) { + _decode(rxHeater); + } + isActive(); + + manage(); +} + + +bool +CAltCommsTask::isActive() { + if(_htrData.Active) { + long tDelta = millis() - _htrData.Active; + if(tDelta > 0) { + _htrData.Active = 0; + } + } + return _htrData.Active != 0; +} + + +void +CAltCommsTask::_doThermo() +{ + static int demandMemory = 5; + uint8_t ThermostatMode = NVstore.getUserSettings().ThermostatMethod; // get the METHOD of thermostat control + float Window = NVstore.getUserSettings().ThermostatWindow; + float tCurrent = getTemperatureSensor(); + float tDesired = float(CDemandManager::getDegC()); + float tDelta = tCurrent - tDesired; + Window /= 2; + switch(ThermostatMode) { + + case 3: // GPIO controlled thermostat mode + if(CDemandManager::isExtThermostatMode()) { + if(CDemandManager::isExtThermostatOn()) { + _matchDemand(5); + } + else { + _matchDemand(0); + } + break; + } + // deliberately fall through if not enabled for GPIO control to standard thermostat + // | + // V + case 0: // conventional heater controlled thermostat mode + Window = 1.0; + // deliberately fall through with +-1C window + // | + // V + case 1: // heater controlled thermostat mode - BUT actual temp is tweaked via a changed window + // if(fabs(tDelta) < Window) { + // _reqThermo(0); // hold at desired if inside window + // } + // else if(tDelta < -Window) { + // _reqThermo(+1); + // } + // else { + // _reqThermo(-1); + // } + if(tDelta >= Window) { + demandMemory = 0; // flip flop to minimum power when over +ve threshold + } + else if(tDelta <= -Window){ + demandMemory = 5; // flip flop to maximum power when under -ve threshold + } + _matchDemand(demandMemory); + break; + + case 2: // BTC controlled thermostat mode + // map linear deviation within thermostat window to a Hz value, + // Hz mode however uses the desired temperature field, somewhere between 8 - 35 for min/max + // so create a desired "temp" according the the current hystersis + if(fabs(tDelta) < Window/2) { + _matchDemand(3); + } + else if(fabs(tDelta) < Window) { + _matchDemand((tDelta > 0) ? 2 : 4); + } + else { + _matchDemand((tDelta > 0) ? 0 : 5); + } + break; + + case 4: + _matchDemand(5); + break; + } +} + +void +CAltCommsTask::_matchDemand(int desired) +{ + if(_htrData.Demand == desired) + _reqDemand(0); + else if(_htrData.Demand > desired) + _reqDemand(-1); + else + _reqDemand(+1); +} \ No newline at end of file diff --git a/src/Protocol/AltController.h b/src/Protocol/AltController.h new file mode 100644 index 0000000..c2f67e6 --- /dev/null +++ b/src/Protocol/AltController.h @@ -0,0 +1,60 @@ +/* + * This file is part of the "bluetoothheater" distribution + * (https://gitlab.com/mrjones.id.au/bluetoothheater) + * + * Copyright (C) 2020 Ray Jones + * + * 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 __ALTCONTROLLER_H__ +#define __ALTCONTROLLER_H__ + +#include +#include "../Utility/UtilClasses.h" + +// struct sAltHeaterData { +// unsigned long Active; +// bool On; +// bool Stopping; +// bool Glow; +// bool Fan; +// bool Pump; +// bool Plateau; +// int Demand; +// int BodyT; +// int Volts; +// int Error; +// int pumpRate[6]; +// sAltHeaterData(); +// int runState(); +// int errState(); +// float getPumpRate(); +// }; + +// void decodeAltHeater(int rxdata); + +// void reqAltPower(); +// void reqAltStatus(); +// void reqAltVolts(); +// void reqAltBodyT(); +// bool isAltActive(); +// void checkAltRxEvents(); +// void checkAltTxEvents(); + + + +// extern sAltHeaterData AltHeaterData; + +#endif \ No newline at end of file diff --git a/src/Protocol/AltControllerTask.cpp b/src/Protocol/AltControllerTask.cpp new file mode 100644 index 0000000..253150f --- /dev/null +++ b/src/Protocol/AltControllerTask.cpp @@ -0,0 +1,311 @@ +/* + * This file is part of the "bluetoothheater" distribution + * (https://gitlab.com/mrjones.id.au/bluetoothheater) + * + * Copyright (C) 2020 Ray Jones + * + * 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 "CommsTask.h" +#include "AltControllerTask.h" +#include "AltController.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" + +#define RX_DATA_TIMOUT 50 +// #define ALTCTRL_DEBUG + + +char altdbgMsg[COMMS_MSGQUEUESIZE]; + +CAltCommsTask AltCommsTask; // AltCommsTaskInfo; + +volatile int nRunAltController = 0; +volatile bool bAltCommsOnline = false; + +void buildTxItems(int val, rmt_item32_t txItems[9]); + +rmt_channel_t CAltCommsTask::__txChannel; +volatile int CAltCommsTask::_txPending = 0; + +CAltCommsTask::CAltCommsTask() : CCommsTask() +{ + _connState = 0; + _startup.state = 0; + _tPause = 0; + _startup.pumpIdx = 0; +} + +void +CAltCommsTask::taskStart() +{ + CCommsTask::taskStart(); + _runState = 0; + xTaskCreate(commsTask, + "AltCtrlrTask", + 4000, + this, + TASK_PRIORITY_HEATERCOMMS, + &_taskHandle); +} + +void +CAltCommsTask::commsTask(void* arg) { + ////////////////////////////////////////////////////////////////////////////////////// + // Alternate controller data reception + // + CAltCommsTask* pThis = (CAltCommsTask*)arg; + + pThis->_task(); + + vTaskDelete(NULL); // NEVER fall out from a task! + for(;;); +} + +void +CAltCommsTask::_task() +{ // create FreeRTOS queues etc + // pThis->create(ALTCTRL_DATAQUEUESIZE); + create(ALTCTRL_DATAQUEUESIZE); + + pinMode(Tx1Pin, OUTPUT); + pinMode(Rx1Pin, INPUT_PULLUP); + pinMode(TxEnbPin, OUTPUT); + + // RMT Tx configuration + _txCfg.rmt_mode = RMT_MODE_TX; + _txCfg.channel = __txChannel = RMT_CHANNEL_2; + _txCfg.gpio_num = Tx1Pin; + _txCfg.mem_block_num = 1; + _txCfg.tx_config.loop_en = 0; + _txCfg.tx_config.idle_output_en = 1; + _txCfg.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH; + _txCfg.tx_config.carrier_en = 0; + _txCfg.tx_config.carrier_duty_percent = 50; + _txCfg.tx_config.carrier_freq_hz = 10000; + _txCfg.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; + _txCfg.clk_div = 80; // 1us / tick + + // RMT Rx configuration + _rxCfg.rmt_mode = RMT_MODE_RX; + _rxCfg.channel = RMT_CHANNEL_3; + _rxCfg.gpio_num = Rx1Pin; + _rxCfg.mem_block_num = 1; + _rxCfg.clk_div = 80; // 1us / clock + _rxCfg.rx_config.filter_en = true; + _rxCfg.rx_config.filter_ticks_thresh = 100; + _rxCfg.rx_config.idle_threshold = 32000; // > 32ms no transitions => end of Rx + + ESP_ERROR_CHECK(rmt_config(&_txCfg)); + ESP_ERROR_CHECK(rmt_driver_install(_txCfg.channel, 0, 0)); + + ESP_ERROR_CHECK(rmt_config(&_rxCfg)); + ESP_ERROR_CHECK(rmt_driver_install(_rxCfg.channel, 512, 0)); + // ringbuffer for rx + ESP_ERROR_CHECK(rmt_get_ringbuf_handle(_rxCfg.channel, &_ringbuffer)); + + // setup call back to terminate tx gate + rmt_register_tx_end_callback(RmtSendDone, this); + // rmt_register_tx_end_callback(RmtSendDone, pThis); + + _runState = 1; + putTxQueue(0xA6); // initial poll to detect heater + while(_runState == 1) { + + int command; + if(getTxQueue(command)) { + doComms(command); + } + delay(1); + } + // pThis->_runState = 1; + // while(pThis->_runState == 1) { + + // int command; + // if(pThis->getTxQueue(command)) { + // pThis->doComms(command); + // } + // delay(1); + // } + + // disconnect from RMT peripheral + rmt_register_tx_end_callback(NULL, NULL); + ESP_ERROR_CHECK(rmt_driver_uninstall(_txCfg.channel)); + ESP_ERROR_CHECK(rmt_driver_uninstall(_rxCfg.channel)); + _ringbuffer = NULL; + + // return pins to standard GPIO functions + pinMode(Tx1Pin, OUTPUT); + pinMode(Rx1Pin, INPUT_PULLUP); // required for MUX to work properly + pinMode(TxEnbPin, OUTPUT); + digitalWrite(Tx1Pin, HIGH); + digitalWrite(TxEnbPin, LOW); + + // pThis->_runState = 0; + _runState = 0; +} + +// static callback for end of RMT transmission - used to terminate Tx Gate pulse +void +CAltCommsTask::RmtSendDone(rmt_channel_t channel, void *arg) +{ + if(channel == __txChannel) { + gpio_set_level(TxEnbPin, 0); + _txPending = 2; + } +} + + +void +CAltCommsTask::doComms(int command) +{ + // ensure the ringbuffer is cleared + void *p; + size_t s; + while ((p = xRingbufferReceive(_ringbuffer, &s, 0))) + { + ESP_LOGD(TAG, "flushing entry"); + vRingbufferReturnItem(_ringbuffer, p); + } + + // send the command, and wait for a response + rmt_item32_t txItems[9]; + buildTxItems(command, txItems); // create transition list + digitalWrite(TxEnbPin, HIGH); // enable Tx gate + + NVstore.takeSemaphore(); // an issue with RmtSDendDone occuring during NV saves exists - block NV saves whilst we send + _txPending = 1; + rmt_write_items(_txCfg.channel, txItems, 9, 0); // send the transitions + + // await reception, TxGate holds Rx high during Tx + rmt_rx_start(_rxCfg.channel, true); + + // wait for ring buffer response, or time out + size_t rx_size; + int toCount = 50; + rmt_item32_t* rxItems = NULL; + while(--toCount) { + rxItems = (rmt_item32_t *)xRingbufferReceive(_ringbuffer, &rx_size, 10); + if(_txPending == 2) { + NVstore.giveSemaphore(); + _txPending = 0; + } + if(rxItems != NULL) + break; + } + + if(_txPending) { + NVstore.giveSemaphore(); + _txPending = 0; + } + +#ifdef ALTCTRL_DEBUG + Serial.printf("RxItems = %d\r\n", rx_size/4); + for(int i=0; i>= 1; + } + txItems[8].duration1 = 250; // 250us to drive line high - not relying upon pull up so much + txItems[8].level1 = 1; +} + +bool +CAltCommsTask::decodeRxItems(rmt_item32_t* rxItems, int size) +{ + int read_data = 0; + if (size >= 17) + { + + // _ __ __ _ __ _ __________________ + // |________| x|_| x|_| |x_| x|_| |x_|~~~~~~~|_| + // . . . . . . + // Start '1' '1' '0' '1' '0' ..... Last + // + // Start is held in rxItems[0].duration0 & level0, it is ~30ms long + // Each data bit is always ~12ms long + // a '1' is 8ms high, 4ms low + // a '0' is 4ms high, 8ms low + // Data bits are held in rxItems[n].level1 for 1st part & rxItems[n+1].duration0 for 2nd half + + // confirm valid start + if(rxItems[0].level0 == 0 && INBOUNDS(rxItems[0].duration0, 29500, 30500)) { + // start OK, now read the 16 bit payload + for (int i = 0; i < 16; i++) + { + read_data <<= 1; + + // total bit time should be ~12ms + int bitTime = rxItems[i].duration1 + rxItems[i+1].duration0; // add 1st and 2nd part times + if(INBOUNDS(bitTime, 11500, 12500) // confirm duration + && rxItems[i].level1 == 1 // confirm 1st part is high + && rxItems[i+1].level0 == 0) // confirm 2nd part is low + { + // OK, a 1 is accepted if high > 6ms + if(rxItems[i].duration1 > 6000) + read_data |= 0x0001; + } + else { + sprintf(altdbgMsg, "Alt controller @%d bitTime=%d lvl1=%d lvl0=%d?\r\n", i, bitTime, rxItems[i].level1, rxItems[i+1].level0); + putMsgQueue(altdbgMsg); + read_data = 0; + return false; + } + } + } + else { + strcpy(altdbgMsg, "Alt Controller decode Invalid start pulse\r\n"); + putMsgQueue(altdbgMsg); + return false; + } + } + // sprintf(altdbgMsg, "Alt controller read: 0x%04X\r\n", read_data); + // putMsgQueue(altdbgMsg); + + _online = true; + putRxQueue(read_data); + xSemaphoreGive(_semaphore); + return true; +} + diff --git a/src/Protocol/AltControllerTask.h b/src/Protocol/AltControllerTask.h new file mode 100644 index 0000000..05e11c0 --- /dev/null +++ b/src/Protocol/AltControllerTask.h @@ -0,0 +1,131 @@ +/* + * This file is part of the "bluetoothheater" distribution + * (https://gitlab.com/mrjones.id.au/bluetoothheater) + * + * Copyright (C) 2020 Ray Jones + * + * 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 __ALTCTRLTASK_H__ +#define __ALTCTRLTASK_H__ + +#include +#include "../Utility/UtilClasses.h" +#include "CommsTask.h" +#include "driver/rmt.h" + +struct sAltHeaterData { +private: + int BodyT; +public: + bool thermoMode; + unsigned long Active; + bool HeaterOn; + bool Stopping; + bool GlowOn; + bool FanOn; + bool PumpOn; + bool Plateau; + int Demand; + int Volts; + int Error; + int pumpRate[6]; + sAltHeaterData(); + void init(); + int runState(); + int errState(); + float getDesiredPumpRate(int idx); + float getPumpRate(); + float getBodyTemp(); + void decodeBodyT(int rxData); + void decodeVolts(int rxData); + void decodeStatus(int rxData); + void decodePumpRate(int rxData); + void decodeThermoDemand(int rxData); + void decodeError(int rxData); + void report(); +}; + + + +class CAltCommsTask : public CCommsTask { +protected: + static void commsTask(void* arg); + static void RmtSendDone(rmt_channel_t channel, void *arg); + static rmt_channel_t __txChannel; + volatile static int _txPending; + rmt_config_t _txCfg; + rmt_config_t _rxCfg; + RingbufHandle_t _ringbuffer; + + void _task(); + + void doComms(int command); + bool decodeRxItems(rmt_item32_t* rxItems, int size); + sAltHeaterData _htrData; + int _connState; + unsigned long _tPause; + struct { + int state; + int pumpIdx; + } _startup; + + void _doStartupProbe(); + void _decode(int rxData); // interpret data word received from heater + void _reqStatus(); + void _reqVolts(); + void _reqBodyT(); + void _reqDemand(int dir); + void _reqThermo(int delta); + void _doThermo(); + void _matchDemand(int desired); + +public: + CAltCommsTask(); + void taskStart(); + + void manage(); + void checkEvents(); + bool isActive(); // comms active and connected + void reqPower(); + void reportDecode(); + + float getFanRPM(); + float getDesiredPumpRate(int idx); + float getActualPumpRate(); + float getGlow(); + float getBodyTemp(); + int getRunState(); + int getErrState(); + + void putTxQueue(int command) { + CCommsTask::putTxQueue(&command); + } + bool getTxQueue(int& command) { + return CCommsTask::getTxQueue(&command); + } + void putRxQueue(int response) { + CCommsTask::putRxQueue(&response); + } + bool getRxQueue(int& response) { + return CCommsTask::getRxQueue(&response); + } + +}; + +extern CAltCommsTask AltCommsTask; + + +#endif \ No newline at end of file diff --git a/src/Protocol/CommsTask.h b/src/Protocol/CommsTask.h new file mode 100644 index 0000000..c12f0da --- /dev/null +++ b/src/Protocol/CommsTask.h @@ -0,0 +1,124 @@ +/* + * This file is part of the "bluetoothheater" distribution + * (https://gitlab.com/mrjones.id.au/bluetoothheater) + * + * Copyright (C) 2020 Ray Jones + * + * 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 __COMMSTASK_H__ +#define __COMMSTASK_H__ + +#include +#include "../Utility/UtilClasses.h" + +const int COMMS_MSGQUEUESIZE = 192; +const int ALTCTRL_DATAQUEUESIZE = sizeof(int); + +extern void AltControllerTask(void*); + +class CCommsTask { +protected: + TaskHandle_t _taskHandle; + QueueHandle_t _msgQueue; + QueueHandle_t _rxQueue; + QueueHandle_t _txQueue; + SemaphoreHandle_t _semaphore; + volatile int _runState; + volatile bool _online; + +public: +public: + CCommsTask() { + _taskHandle = NULL; + _msgQueue = NULL; + _rxQueue = NULL; + _txQueue = NULL; + _semaphore = NULL; + _runState = 0; + _online = false; + } + virtual ~CCommsTask() { + taskStop(); + destroy(); + } + void create(int dataElementSize) { + if(_msgQueue == NULL) _msgQueue = xQueueCreate(20, COMMS_MSGQUEUESIZE); + if(_rxQueue == NULL) _rxQueue = xQueueCreate(4, dataElementSize); + if(_txQueue == NULL) _txQueue = xQueueCreate(4, dataElementSize); + if(_semaphore == NULL) _semaphore = xSemaphoreCreateBinary(); + } + void destroy() { + vQueueDelete(_msgQueue); _msgQueue = NULL; + vQueueDelete(_rxQueue); _rxQueue = NULL; + vQueueDelete(_txQueue); _txQueue = NULL; + vSemaphoreDelete(_semaphore); _semaphore = NULL; + } + void putTxQueue(void* pData) { + if(_txQueue) + xQueueSend(_txQueue, pData, 0); + } + bool getTxQueue(void* pData) { + if(_txQueue && xQueueReceive(_txQueue, pData, 0)) { + return true; + } + return false; + } + void putRxQueue(void* pData) { + if(_rxQueue) + xQueueSend(_rxQueue, pData, 0); + } + bool getRxQueue(void* pData) { + if(_rxQueue && xQueueReceive(_rxQueue, pData, 0)) + return true; + return false; + } + void putMsgQueue(const char msg[COMMS_MSGQUEUESIZE]) { + if(_msgQueue) + xQueueSend(_msgQueue, msg, 0); + } + bool getMsgQueue(char msg[COMMS_MSGQUEUESIZE]) { + if(_msgQueue && xQueueReceive(_msgQueue, msg, 0)) { + return true; + } + return false; + } + TaskHandle_t getTaskHandle() const { + return _taskHandle; + } + SemaphoreHandle_t getSemaphore() const { + return _semaphore; + } + virtual void taskStart() { + _online = false; + } + virtual void taskStop() // create task to run blue wire interface + { + DebugPort.printf("Stopping comms task %d\r\n", _runState); + if(_runState == 1) { // check task is running + _runState = 2; // ask task to stop + DebugPort.println("Stopping comms task wait"); + while(_runState != 0) { + vTaskDelay(1); + } + _taskHandle = NULL; + } + } + bool isOnline() const { + return _online; + } +}; + +#endif \ No newline at end of file diff --git a/src/Protocol/HeaterManager.cpp b/src/Protocol/HeaterManager.cpp new file mode 100644 index 0000000..65728be --- /dev/null +++ b/src/Protocol/HeaterManager.cpp @@ -0,0 +1,396 @@ +/* + * This file is part of the "bluetoothheater" distribution + * (https://gitlab.com/mrjones.id.au/bluetoothheater) + * + * Copyright (C) 2020 Ray Jones + * + * 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 +#include "HeaterManager.h" +#include "../Utility/helpers.h" +#include "../Utility/macros.h" +#include "../Protocol/Protocol.h" +#include "Protocol/TxManage.h" +#include "Protocol/SmartError.h" +#include "Utility/NVStorage.h" + +CHeaterManager HeaterManager; + + +const char* Runstates [] PROGMEM = { + " Stopped/Ready ", // 0 + "Starting...", // 1 + "Igniting...", // 2 + "Ignition retry pause", // 3 + "Ignited", // 4 + "Running", // 5 + "Stopping", // 6 + "Shutting down", // 7 + "Cooling", // 8 + "Heating glow plug", // 9 - interpreted state - actually runstate 2 with no pump action! + "Suspended", // 10 - interpreted state - cyclic mode has suspended heater + "Suspending...", // 11 - interpreted state - cyclic mode is suspending heater + "Suspend cooling", // 12 - interpreted state - cyclic mode is suspending heater + "Unknown run state" +}; + +const char* Errstates [] PROGMEM = { + "", // [0] + "", // [1] + "Low voltage", // [2] E-01 + "High voltage", // [3] E-02 + "Glow plug fault", // [4] E-03 + "Pump fault", // [5] E-04 + "Overheat", // [6] E-05 + "Motor fault", // [7] E-06 + "Comms fault", // [8] E-07 + "Flame out", // [9] E-08 + "Temp sense", // [10] E-09 + "Ignition fail", // [11] E-10 SmartError manufactured state - sensing runstate 2 -> >5 + "Failed 1st ignite", // [12] E-11 SmartError manufactured state - sensing runstate 2 -> 3 + "Excess fuel usage", // [13] E-12 SmartError manufactured state - excess fuel consumed + "Unknown error?" // mystery code! +}; + +const char* ErrstatesEx [] PROGMEM = { + "E-00: OK", // [0] + "E-00: OK", // [1] + "E-01: Low voltage", // [2] E-01 + "E-02: High voltage", // [3] E-02 + "E-03: Glow plug fault", // [4] E-03 + "E-04: Pump fault", // [5] E-04 + "E-05: Overheat", // [6] E-05 + "E-06: Motor fault", // [7] E-06 + "E-07: No heater comms", // [8] E-07 + "E-08: Flame out", // [9] E-08 + "E-09: Temp sense", // [10] E-09 + "E-10: Ignition fail", // [11] E-10 SmartError manufactured state - sensing runstate 2 -> >5 + "E-11: Failed 1st ignite", // [12] E-11 SmartError manufactured state - sensing runstate 2 -> 3 + "E-12: Excess fuel shutdown", // [13] E-12 SmartError manufactured state - excess fuel consumed + "Unknown error?" // mystery code! +}; + + +CHeaterManager::CHeaterManager() { + _heaterStyle = -1; + _taskHandle = NULL; + _pCommsTask = NULL; +} + +bool +CHeaterManager::detect() +{ + int style = NVstore.getHeaterTuning().heaterStyle; + DebugPort.printf("Initial heater style %d\r\n", style); + + bool tested[2] = { false, false}; + bool found = false; + + for(;;) { + if(tested[style]) { + setHeaterStyle(NVstore.getHeaterTuning().heaterStyle); // revert to saved heater type for now, but we did not find it + return false; + } + + tested[style] = true; + setHeaterStyle(style); + unsigned long timeout = millis() + 1500; + + long tDelta; + do { + delay(10); + if(_pCommsTask && _pCommsTask->isOnline()) { + found = true; + sHeaterTuning tuning = NVstore.getHeaterTuning(); + DebugPort.printf("Found heater style %d\r\n", style); + if(tuning.heaterStyle != style) { + DebugPort.printf("saving heater style %d\r\n", style); + tuning.heaterStyle = style; + NVstore.setHeaterTuning(tuning); + NVstore.save(); + // NVstore.doSave(); + } + return true; + } + tDelta = millis() - timeout; + } while(tDelta < 0); + + style++; + WRAPLIMITS(style, 0, 1); + } +} + +void +CHeaterManager::setHeaterStyle(int mode) +{ + if(INBOUNDS(mode, 0, 1)) { + if(mode != _heaterStyle) { + + + // stop existing comms connection if running + if(_pCommsTask) { + _pCommsTask->taskStop(); + _taskHandle = NULL; + _pCommsTask = NULL; + } + + // start comms connection + _heaterStyle = mode; + switch(_heaterStyle) { + case 0: + _pCommsTask = &BlueWireCommsTask; + break; + + case 1: + _pCommsTask = &AltCommsTask; + break; + } + + if(_pCommsTask) { + _pCommsTask->taskStart(); + delay(10); + } + } + } +} + +TaskHandle_t +CHeaterManager::getTaskHandle() const +{ + if(_pCommsTask) + return _pCommsTask->getTaskHandle(); + return NULL; +} + +SemaphoreHandle_t +CHeaterManager::getSemaphore() const +{ + if(_pCommsTask) + return _pCommsTask->getSemaphore(); + return NULL; +} + +// char dbgMsg[COMMS_MSGQUEUESIZE]; + +/*QueueHandle_t +CHeaterManager::getMsgQueue() const +{ + switch(_heaterStyle) { + case 0: return BlueWireMsgQueue; + case 1: + if(AltCommsTask.getMsgQueue(dbgMsg)) + DebugPort.print(dbgMsg); + return NULL; + default: return NULL; + } +}*/ + + // QueueHandle_t MsgQueue = HeaterManager.getMsgQueue(); + + +int +CHeaterManager::getHeaterStyle() const +{ + return _heaterStyle; +} + + +float CHeaterManager::getFanRPM() const +{ + switch(_heaterStyle) { + case 0: return getHeaterInfo().getFan_Actual(); + case 1: return AltCommsTask.getFanRPM(); + default: return 0; + } +} + +float +CHeaterManager::getFanVoltage() const +{ + switch(_heaterStyle) { + case 0: return getHeaterInfo().getFan_Voltage(); + case 1: return -1; + default: return 0; + } +} + +float +CHeaterManager::getPumpDemand() const +{ + switch(_heaterStyle) { + case 0: return getHeaterInfo().getPump_Fixed(); + case 1: return AltCommsTask.getDesiredPumpRate(CDemandManager::getPumpHz()); //AltHeaterData.Demand; + default: return 0; + } +} + +float +CHeaterManager::getPumpRate() const +{ + switch(_heaterStyle) { + case 0: + return getHeaterInfo().getPump_Actual(); + case 1: + return AltCommsTask.getActualPumpRate(); + default: + return 0; + } +} + +float +CHeaterManager::getBodyTemp() const +{ + switch(_heaterStyle) { + case 0: return getHeaterInfo().getTemperature_HeatExchg(); + case 1: return AltCommsTask.getBodyTemp(); + default: return 0; + } +} + +float +CHeaterManager::getGlowPlugPower() const +{ + switch(_heaterStyle) { + case 0: return getHeaterInfo().getGlowPlug_Power(); + case 1: return AltCommsTask.getGlow(); + default: return 0; + } +} + +int +CHeaterManager::getRunStateEx() const +{ + switch(_heaterStyle) { + case 0: return getHeaterInfo().getRunStateEx(); + case 1: return AltCommsTask.getRunState(); + default: return 0; + } +} +int +CHeaterManager::getRunState() const +{ + switch(_heaterStyle) { + case 0: return getHeaterInfo().getRunState(); + case 1: return AltCommsTask.getRunState(); + default: return 0; + } +} + +int +CHeaterManager::getErrState() const +{ + switch(_heaterStyle) { + case 0: return getHeaterInfo().getErrState(); + case 1: return AltCommsTask.getErrState(); + default: return 8; + } +} + +const char* +CHeaterManager::getRunStateStr() const +{ + uint8_t runstate = getRunStateEx(); + UPPERLIMIT(runstate, 13); + if(runstate == 2 && getPumpRate() == 0) { // split runstate 2 - glow, then fuel + runstate = 9; + } + return Runstates[runstate]; +} + +const char* +CHeaterManager::getErrStateStr() const +{ + uint8_t errstate = getErrState(); + UPPERLIMIT(errstate, 13); + return Errstates[errstate]; +} + +const char* +CHeaterManager::getErrStateStrEx() const +{ + uint8_t errstate = getErrState(); + UPPERLIMIT(errstate, 13); + return ErrstatesEx[errstate]; +} + + + +void +CHeaterManager::reqOnOff(bool state) +{ + switch(_heaterStyle) { + case 0: + if(state) { + TxManage.queueOnRequest(); + SmartError.reset(); + } + else { + TxManage.queueOffRequest(); + SmartError.inhibit(); + } + break; + case 1: + DebugPort.println("Alt heater start request queued"); + AltCommsTask.reqPower(); + break; + } +} + +void +CHeaterManager::checkRxEvents() +{ + if(_heaterStyle == 0) { + checkBlueWireRxEvents(); + } + else if(_heaterStyle == 1) { + AltCommsTask.checkEvents(); + } +} + +void +CHeaterManager::checkTxEvents() +{ + if(_heaterStyle == 0) { + checkBlueWireTxEvents(); + } + else if(_heaterStyle == 1) { + AltCommsTask.manage();//checkAltTxEvents(); + } +} + +char taskMsg[COMMS_MSGQUEUESIZE]; + +void +CHeaterManager::checkMsgEvents() +{ + if(_pCommsTask && _pCommsTask->getMsgQueue(taskMsg)) + DebugPort.print(taskMsg); +} + +bool +CHeaterManager::isOnline() +{ + switch(_heaterStyle) { + case 0: + return hasHtrData(); + break; + case 1: + return AltCommsTask.isActive(); + break; + } + return false; +} diff --git a/src/Protocol/HeaterManager.h b/src/Protocol/HeaterManager.h new file mode 100644 index 0000000..298c821 --- /dev/null +++ b/src/Protocol/HeaterManager.h @@ -0,0 +1,70 @@ +/* + * This file is part of the "bluetoothheater" distribution + * (https://gitlab.com/mrjones.id.au/bluetoothheater) + * + * Copyright (C) 2020 Ray Jones + * + * 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 __HEATERMANAGER_H__ +#define __HEATERMANAGER_H__ + +#include +#include "../Utility/UtilClasses.h" +#include "AltController.h" +#include "AltControllerTask.h" +#include "BlueWireTask.h" + + +class CHeaterManager { + int _heaterStyle; + TaskHandle_t _taskHandle; + CCommsTask* _pCommsTask; +public: + CHeaterManager(); + bool detect(); + void setHeaterStyle(int mode); + + float getFanRPM() const; + float getFanVoltage() const; + float getPumpRate() const; + float getPumpDemand() const; + float getBodyTemp() const; + float getGlowPlugPower() const; + int getErrState() const; + int getRunState() const; + int getRunStateEx() const; + int getHeaterStyle() const; + const char* getErrStateStr() const; + const char* getErrStateStrEx() const; + const char* getRunStateStr() const; + TaskHandle_t getTaskHandle() const; + SemaphoreHandle_t getSemaphore() const; + // QueueHandle_t getMsgQueue() const; + + void checkMsgEvents(); + void checkRxEvents(); + void checkTxEvents(); + bool isOnline(); + + + void reqOnOff(bool state); + void reqVolts(); + void reqBodyT(); +}; + +extern CHeaterManager HeaterManager; + +#endif \ No newline at end of file