Added missing files
This commit is contained in:
parent
2f1605e6a3
commit
8006b98ba2
332
src/OLED/433MHzScreen.cpp
Normal file
332
src/OLED/433MHzScreen.cpp
Normal 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
54
src/OLED/433MHzScreen.h
Normal 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
382
src/Protocol/433MHz.cpp
Normal 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
68
src/Protocol/433MHz.h
Normal 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
|
608
src/Protocol/AltController.cpp
Normal file
608
src/Protocol/AltController.cpp
Normal 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);
|
||||||
|
}
|
60
src/Protocol/AltController.h
Normal file
60
src/Protocol/AltController.h
Normal 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
|
311
src/Protocol/AltControllerTask.cpp
Normal file
311
src/Protocol/AltControllerTask.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
131
src/Protocol/AltControllerTask.h
Normal file
131
src/Protocol/AltControllerTask.h
Normal 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
124
src/Protocol/CommsTask.h
Normal 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
|
396
src/Protocol/HeaterManager.cpp
Normal file
396
src/Protocol/HeaterManager.cpp
Normal 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;
|
||||||
|
}
|
70
src/Protocol/HeaterManager.h
Normal file
70
src/Protocol/HeaterManager.h
Normal 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
|
Loading…
Reference in a new issue