Added missing files

This commit is contained in:
Ray Jones 2020-06-19 10:43:50 +10:00
parent 2f1605e6a3
commit 8006b98ba2
11 changed files with 2536 additions and 0 deletions

332
src/OLED/433MHzScreen.cpp Normal file
View file

@ -0,0 +1,332 @@
/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2020 Ray Jones <ray@mrjones.id.au>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
///////////////////////////////////////////////////////////////////////////
//
// 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();
}

54
src/OLED/433MHzScreen.h Normal file
View file

@ -0,0 +1,54 @@
/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2020 Ray Jones <ray@mrjones.id.au>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#ifndef __433MHZSCREEN_H__
#define __433MHZSCREEN_H__
#include <stdint.h>
#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

382
src/Protocol/433MHz.cpp Normal file
View file

@ -0,0 +1,382 @@
/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2020 Ray Jones <ray@mrjones.id.au>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#include <FreeRTOS.h>
#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<size; i++) {
// Serial.printf("[%d] %d:%5d %d:%5d\r\n", i, rxItems[i].level0, rxItems[i].duration0, rxItems[i].level1, rxItems[i].duration1);
// }
// #endif
int workingsize = 0;
unsigned long newCode = 0;
if(size == 32)
workingsize = 23;
else if(size == 25)
workingsize = 24;
else {
if(_debug)
Serial.printf("433MHz remote incorrect number of transitions: %d?\r\n", size);
return false;
}
// start OK, now read the 24 bit payload
int meanBitTime = 0;
for (int i = 0; i < workingsize; i++)
{
meanBitTime += rxItems[i].duration0 + rxItems[i].duration1; // add 1st and 2nd part times
}
meanBitTime /= workingsize;
for (int i = 0; i < workingsize; i++)
{
int bitTime = rxItems[i].duration0 + rxItems[i].duration1; // add 1st and 2nd part times
// if(INBOUNDS(bitTime, 900, 1700) // confirm duration
if(INBOUNDS(bitTime, meanBitTime - 500, meanBitTime + 500) // confirm duration
&& rxItems[i].level0 == 1 // confirm 1st part is high
&& rxItems[i].level1 == 0) // confirm 2nd part is low
{
newCode <<= 1;
// OK, a 1 is accepted if high > 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<size; i++) {
Serial.printf("[%2d] %d:%5d %d:%5d (%d)\r\n", i, rxItems[i].level0, rxItems[i].duration0, rxItems[i].level1, rxItems[i].duration1, rxItems[i].duration0+rxItems[i].duration1);
}
}
// #endif
}
_timeout = (xTaskGetTickCount() + 100) | 1; // | 1 ensures non zero - timeout to allow injection of no button press
return true;
}
void
C433MHzRemote::begin(gpio_num_t pin, rmt_channel_t channel)
{
_readNV();
_rxCfg.rmt_mode = RMT_MODE_RX;
_rxCfg.channel = channel;
_rxCfg.gpio_num = pin;
_rxCfg.mem_block_num = 1;
_rxCfg.clk_div = 80; // 1us / clock
_rxCfg.rx_config.filter_en = true;
_rxCfg.rx_config.filter_ticks_thresh = 250;
_rxCfg.rx_config.idle_threshold = 6000; // > 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;

68
src/Protocol/433MHz.h Normal file
View file

@ -0,0 +1,68 @@
/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2020 Ray Jones <ray@mrjones.id.au>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#ifndef __433MHzREMOTE_H__
#define __433MHzREMOTE_H__
#include <FreeRTOS.h>
#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

View file

@ -0,0 +1,608 @@
/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2020 Ray Jones <ray@mrjones.id.au>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#include <Arduino.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"
#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);
}

View file

@ -0,0 +1,60 @@
/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2020 Ray Jones <ray@mrjones.id.au>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#ifndef __ALTCONTROLLER_H__
#define __ALTCONTROLLER_H__
#include <FreeRTOS.h>
#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

View file

@ -0,0 +1,311 @@
/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2020 Ray Jones <ray@mrjones.id.au>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#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<rx_size/4; i++) {
Serial.printf("[%d] %d:%5d %d:%5d\r\n", i, rxItems[i].level0, rxItems[i].duration0, rxItems[i].level1, rxItems[i].duration1);
}
#endif
if (rxItems) {
bAltCommsOnline |= decodeRxItems(rxItems, rx_size / 4);
vRingbufferReturnItem(_ringbuffer, (void *)rxItems);
}
rmt_rx_stop(_rxCfg.channel);
}
void buildTxItems(int val, rmt_item32_t txItems[9])
{
txItems[0].duration0 = 30000; // 30ms low sync pulse
txItems[0].level0 = 0;
int mask = 0x80;
for(int i = 0; i <8 ; i++) {
txItems[i].duration1 = mask & val ? 8000 : 4000; // HIGH interval: 8ms for a 1, 4ms for a zero
txItems[i].level1 = 1;
txItems[i+1].duration0 = mask & val ? 4000 : 8000; // LOW interval: 4ms for a 1, 8ms for a zero
txItems[i+1].level0 = 0;
mask >>= 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;
}

View file

@ -0,0 +1,131 @@
/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2020 Ray Jones <ray@mrjones.id.au>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#ifndef __ALTCTRLTASK_H__
#define __ALTCTRLTASK_H__
#include <FreeRTOS.h>
#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

124
src/Protocol/CommsTask.h Normal file
View file

@ -0,0 +1,124 @@
/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2020 Ray Jones <ray@mrjones.id.au>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#ifndef __COMMSTASK_H__
#define __COMMSTASK_H__
#include <FreeRTOS.h>
#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

View file

@ -0,0 +1,396 @@
/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2020 Ray Jones <ray@mrjones.id.au>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#include <FreeRTOS.h>
#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;
}

View file

@ -0,0 +1,70 @@
/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2020 Ray Jones <ray@mrjones.id.au>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#ifndef __HEATERMANAGER_H__
#define __HEATERMANAGER_H__
#include <FreeRTOS.h>
#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