OTGW-firmware/OTGW-Core.ino

2100 lines
99 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
***************************************************************************
** Program : OTGW-Core.ino
** Version : v0.10.3
**
** Copyright (c) 2021-2024 Robert van den Breemen
** Borrowed from OpenTherm library from:
** https://github.com/jpraus/arduino-opentherm
**
** TERMS OF USE: MIT License. See bottom of file.
***************************************************************************
*/
#define OTGWDebugTln(...) ({ if (bDebugOTmsg) DebugTln(__VA_ARGS__); })
#define OTGWDebugln(...) ({ if (bDebugOTmsg) Debugln(__VA_ARGS__); })
#define OTGWDebugTf(...) ({ if (bDebugOTmsg) DebugTf(__VA_ARGS__); })
#define OTGWDebugf(...) ({ if (bDebugOTmsg) Debugf(__VA_ARGS__); })
#define OTGWDebugT(...) ({ if (bDebugOTmsg) DebugT(__VA_ARGS__); })
#define OTGWDebug(...) ({ if (bDebugOTmsg) Debug(__VA_ARGS__); })
#define OTGWDebugFlush() ({ if (bDebugOTmsg) DebugFlush(); })
//define Nodoshop OTGW hardware
#define OTGW_BUTTON 0 //D3
#define OTGW_RESET 14 //D5
#define OTGW_LED1 2 //D4
#define OTGW_LED2 16 //D0
//external watchdog
#define EXT_WD_I2C_ADDRESS 0x26
#define PIN_I2C_SDA 4 //D2
#define PIN_I2C_SCL 5 //D1
//used by update firmware functions
const char *hexheaders[] = {
"Last-Modified",
"X-Version"
};
//Macro to Feed the Watchdog
#define FEEDWATCHDOGNOW Wire.beginTransmission(EXT_WD_I2C_ADDRESS); Wire.write(0xA5); Wire.endTransmission();
/* --- PRINTF_BYTE_TO_BINARY macro's --- */
#define PRINTF_BINARY_PATTERN_INT8 "%c%c%c%c%c%c%c%c"
#define PRINTF_BYTE_TO_BINARY_INT8(i) \
(((i) & 0x80ll) ? '1' : '0'), \
(((i) & 0x40ll) ? '1' : '0'), \
(((i) & 0x20ll) ? '1' : '0'), \
(((i) & 0x10ll) ? '1' : '0'), \
(((i) & 0x08ll) ? '1' : '0'), \
(((i) & 0x04ll) ? '1' : '0'), \
(((i) & 0x02ll) ? '1' : '0'), \
(((i) & 0x01ll) ? '1' : '0')
#define PRINTF_BINARY_PATTERN_INT16 PRINTF_BINARY_PATTERN_INT8 PRINTF_BINARY_PATTERN_INT8
#define PRINTF_BYTE_TO_BINARY_INT16(i) PRINTF_BYTE_TO_BINARY_INT8((i) >> 8), PRINTF_BYTE_TO_BINARY_INT8(i)
#define PRINTF_BINARY_PATTERN_INT32 PRINTF_BINARY_PATTERN_INT16 PRINTF_BINARY_PATTERN_INT16
#define PRINTF_BYTE_TO_BINARY_INT32(i) PRINTF_BYTE_TO_BINARY_INT16((i) >> 16), PRINTF_BYTE_TO_BINARY_INT16(i)
#define PRINTF_BINARY_PATTERN_INT64 PRINTF_BINARY_PATTERN_INT32 PRINTF_BINARY_PATTERN_INT32
#define PRINTF_BYTE_TO_BINARY_INT64(i) PRINTF_BYTE_TO_BINARY_INT32((i) >> 32), PRINTF_BYTE_TO_BINARY_INT32(i)
/* --- Endf of macro's --- */
/* --- LOG marcro's ---*/
#define OT_LOG_BUFFER_SIZE 512
char ot_log_buffer[OT_LOG_BUFFER_SIZE];
#define ClrLog() ({ ot_log_buffer[0] = '\0'; })
#define AddLogf(...) ({ snprintf(ot_log_buffer+strlen(ot_log_buffer), OT_LOG_BUFFER_SIZE-strlen(ot_log_buffer), __VA_ARGS__); })
#define AddLog(logstring) ({ strlcat(ot_log_buffer, logstring, OT_LOG_BUFFER_SIZE); })
#define AddLogln() ({ strlcat(ot_log_buffer, "\r\n", OT_LOG_BUFFER_SIZE); })
/* --- End of LOG marcro's ---*/
//some variable's
OpenthermData_t OTdata, delayedOTdata, tmpOTdata;
#define OTGW_BANNER "OpenTherm Gateway"
//===================[ Send useful information to MQWTT ]=====================
/*
Publish usefull firmware version information to MQTT broker.
*/
void sendMQTTversioninfo(){
sendMQTTData("otgw-firmware/version", _SEMVER_FULL);
sendMQTTData("otgw-firmware/reboot_count", String(rebootCount));
sendMQTTData("otgw-firmware/reboot_reason", lastReset);
sendMQTTData("otgw-pic/version", sPICfwversion);
sendMQTTData("otgw-pic/deviceid", sPICdeviceid);
sendMQTTData("otgw-pic/firmwaretype", sPICdeviceid);
sendMQTTData("otgw-pic/picavailable", CONOFF(bPICavailable));
}
/*
Publish state information of PIC firmware version information to MQTT broker.
*/
void sendMQTTstateinformation(){
sendMQTTData(F("otgw-pic/boiler_connected"), CCONOFF(bOTGWboilerstate));
sendMQTTData(F("otgw-pic/thermostat_connected"), CCONOFF(bOTGWthermostatstate));
sendMQTTData(F("otgw-pic/gateway_mode"), CCONOFF(bOTGWgatewaystate));
sendMQTTData(F("otgw-pic/otgw_connected"), CCONOFF(bOTGWonline));
sendMQTT(CSTR(MQTTPubNamespace), CONLINEOFFLINE(bOTGWonline));
}
//===================[ Reset OTGW ]===============================
void resetOTGW() {
OTGWSerial.resetPic();
}
/*
To detect the pic, reset the pic, then find ETX in the response after reset (within 1 second).
The ETX response is send by the bootload, when received it also means you have a pic connected.
*/
void detectPIC(){
OTGWSerial.registerFirmwareCallback(fwreportinfo); //register the callback to report version, type en device ID
OTGWSerial.resetPic(); // make sure it the firmware is detected
bPICavailable = OTGWSerial.find(ETX);
if (bPICavailable) {
DebugTln("ETX found after reset: Pic detected!");
} else {
DebugTln("No ETX found after reset: no Pic detected!");
}
}
//===================[ getpicfwversion ]===========================
/*
Get the information of the pic firmware: version number, device type and firmware type.
This is done by sending a PR=A command, requesting a banner from the PIC. This will trigger detection of version.
*/
String getpicfwversion(){
String _ret="";
String line = executeCommand("PR=A");
int p = line.indexOf(OTGW_BANNER);
if (p >= 0) {
p += sizeof(OTGW_BANNER);
_ret = line.substring(p);
} else {
_ret ="No version found";
}
OTGWDebugTf(PSTR("getpicfwversion: Current firmware version: %s\r\n"), CSTR(_ret));
_ret.trim();
return _ret;
}
//===================[ checkOTWGpicforupdate ]=====================
void checkOTWGpicforupdate(){
if (sPICfwversion.isEmpty()) {
sMessage = ""; //no firmware version found for some reason
} else {
OTGWDebugTf(PSTR("OTGW PIC firmware version = [%s]\r\n"), CSTR(sPICfwversion));
String latest = checkforupdatepic("gateway.hex");
if (!bOTGWonline) {
sMessage = sPICfwversion;
} else if (latest.isEmpty()) {
sMessage = ""; //two options: no internet connection OR no firmware version
} else if (latest != sPICfwversion) {
sMessage = "New PIC version " + latest + " available!";
}
}
//check if the esp8266 and the littlefs versions match
//if (!checklittlefshash()) sMessage = "Flash your littleFS with matching version!";
}
//===================[ sendOTGWbootcmd ]=====================
void sendOTGWbootcmd(){
if (!settingOTGWcommandenable) return;
OTGWDebugTf(PSTR("OTGW boot message = [%s]\r\n"), CSTR(settingOTGWcommands));
// parse and execute commands
char bootcmds[settingOTGWcommands.length() + 1];
settingOTGWcommands.toCharArray(bootcmds, settingOTGWcommands.length() + 1);
char* cmd;
int i = 0;
cmd = strtok(bootcmds, ";");
while (cmd != NULL) {
OTGWDebugTf(PSTR("Boot command[%d]: %s\r\n"), i++, cmd);
addOTWGcmdtoqueue(cmd, strlen(cmd), true);
cmd = strtok(NULL, ";");
}
}
//===================[ OTGW Command & Response ]===================
String executeCommand(const String sCmd){
//send command to OTGW
OTGWDebugTf(PSTR("OTGW Send Cmd [%s]\r\n"), CSTR(sCmd));
OTGWSerial.setTimeout(1000);
DECLARE_TIMER_MS(tmrWaitForIt, 1000);
while((OTGWSerial.availableForWrite() < (int)(sCmd.length()+2)) && !DUE(tmrWaitForIt)){
feedWatchDog();
}
OTGWSerial.write(CSTR(sCmd));
OTGWSerial.write("\r\n");
OTGWSerial.flush();
//wait for response
RESTART_TIMER(tmrWaitForIt);
while(!OTGWSerial.available() && !DUE(tmrWaitForIt)) {
feedWatchDog();
}
String _cmd = sCmd.substring(0,2);
OTGWDebugTf(PSTR("Send command: [%s]\r\n"), CSTR(_cmd));
//fetch a line
String line = OTGWSerial.readStringUntil('\n');
line.trim();
String _ret ="";
if (line.startsWith(_cmd)){
// Responses: When a serial command is accepted by the gateway, it responds with the two letters of the command code, a colon, and the interpreted data value.
// Command: "TT=19.125"
// Response: "TT: 19.13"
// [XX:response string]
_ret = line.substring(3);
} else if (line.startsWith("NG")){
_ret = "NG - No Good. The command code is unknown.";
} else if (line.startsWith("SE")){
_ret = "SE - Syntax Error. The command contained an unexpected character or was incomplete.";
} else if (line.startsWith("BV")){
_ret = "BV - Bad Value. The command contained a data value that is not allowed.";
} else if (line.startsWith("OR")){
_ret = "OR - Out of Range. A number was specified outside of the allowed range.";
} else if (line.startsWith("NS")){
_ret = "NS - No Space. The alternative Data-ID could not be added because the table is full.";
} else if (line.startsWith("NF")){
_ret = "NF - Not Found. The specified alternative Data-ID could not be removed because it does not exist in the table.";
} else if (line.startsWith("OE")){
_ret = "OE - Overrun Error. The processor was busy and failed to process all received characters.";
} else if (line.length()==0) {
//just an empty line... most likely it's a timeout situation
_ret = "TO - Timeout. No response.";
} else {
_ret = line; //some commands return a string, just return that.
}
OTGWDebugTf(PSTR("Command send [%s]-[%s] - Response line: [%s] - Returned value: [%s]\r\n"), CSTR(sCmd), CSTR(_cmd), CSTR(line), CSTR(_ret));
return _ret;
}
//===================[ Watchdog OTGW ]===============================
String initWatchDog() {
// Hardware WatchDog is based on:
// https://github.com/rvdbreemen/ESPEasySlaves/tree/master/TinyI2CWatchdog
// Code here is based on ESPEasy code, modified to work in the project.
// configure hardware pins according to eeprom settings.
OTGWDebugTln(F("Setup Watchdog"));
OTGWDebugTln(F("INIT : I2C"));
Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL); //configure the I2C bus
//=============================================
// I2C Watchdog boot status check
String ReasonReset = "";
delay(100);
Wire.beginTransmission(EXT_WD_I2C_ADDRESS); // OTGW WD address
Wire.write(0x83); // command to set pointer
Wire.write(17); // pointer value to status byte
Wire.endTransmission();
Wire.requestFrom((uint8_t)EXT_WD_I2C_ADDRESS, (uint8_t)1);
if (Wire.available())
{
byte status = Wire.read();
if (status & 0x1)
{
OTGWDebugTln(F("INIT : Reset by WD!"));
ReasonReset = "Reset by External WD\r\n";
//lastReset = BOOT_CAUSE_EXT_WD;
}
}
return ReasonReset;
//===========================================
}
void WatchDogEnabled(byte stateWatchdog){
Wire.beginTransmission(EXT_WD_I2C_ADDRESS); //Nodoshop design uses the hardware WD on I2C, address 0x26
Wire.write(7); //Write to register 7, the action register
Wire.write(stateWatchdog); //1 = armed to reset, 0 = turned off
Wire.endTransmission(); //That's all there is...
}
//===[ Feed the WatchDog before it bites! (1x per second) ]===
void feedWatchDog() {
//make sure to do this at least once a second
//==== feed the WD over I2C ====
// Address: 0x26
// I2C Watchdog feed
DECLARE_TIMER_MS(timerWD, 1000, SKIP_MISSED_TICKS);
if DUE(timerWD)
{
Wire.beginTransmission(EXT_WD_I2C_ADDRESS); //Nodoshop design uses the hardware WD on I2C, address 0x26
Wire.write(0xA5); //Feed the dog, before it bites.
Wire.endTransmission(); //That's all there is...
blinkLEDnow(LED1);
}
//yield();
}
//===================[ END Watchdog OTGW ]===============================
//=======================================================================
float OpenthermData_t::f88() {
float value = (int8_t) valueHB;
return value + (float)valueLB / 256.0f;
}
void OpenthermData_t::f88(float value) {
if (value >= 0) {
valueHB = (byte) value;
float fraction = (value - valueHB);
valueLB = fraction * 256.0f;
}
else {
valueHB = (byte)(value - 1);
float fraction = (value - valueHB - 1);
valueLB = fraction * 256.0f;
}
}
uint16_t OpenthermData_t::u16() {
uint16_t value = valueHB;
return ((value << 8) + valueLB);
}
void OpenthermData_t::u16(uint16_t value) {
valueLB = value & 0xFF;
valueHB = (value >> 8) & 0xFF;
}
int16_t OpenthermData_t::s16() {
int16_t value = valueHB;
return ((value << 8) + valueLB);
}
void OpenthermData_t::s16(int16_t value) {
valueLB = value & 0xFF;
valueHB = (value >> 8) & 0xFF;
}
//parsing helpers
const char *statusToString(OpenThermResponseStatus status)
{
switch (status) {
case OT_NONE: return "NONE";
case OT_SUCCESS: return "SUCCESS";
case OT_INVALID: return "INVALID";
case OT_TIMEOUT: return "TIMEOUT";
default: return "UNKNOWN";
}
}
const char *messageTypeToString(OpenThermMessageType message_type)
{
switch (message_type) {
case OT_READ_DATA: return "READ_DATA";
case OT_WRITE_DATA: return "WRITE_DATA";
case OT_INVALID_DATA: return "INVALID_DATA";
case OT_RESERVED: return "RESERVED";
case OT_READ_ACK: return "READ_ACK";
case OT_WRITE_ACK: return "WRITE_ACK";
case OT_DATA_INVALID: return "DATA_INVALID";
case OT_UNKNOWN_DATA_ID: return "UNKNOWN_DATA_ID";
default: return "UNKNOWN";
}
}
const char *messageIDToString(OpenThermMessageID message_id){
if (message_id <= OT_MSGID_MAX) {
PROGMEM_readAnything (&OTmap[message_id], OTlookupitem);
return OTlookupitem.label;
} else return "Undefined";}
OpenThermMessageType getMessageType(unsigned long message)
{
OpenThermMessageType msg_type = static_cast<OpenThermMessageType>((message >> 28) & 7);
return msg_type;
}
OpenThermMessageID getDataID(unsigned long frame)
{
return (OpenThermMessageID)((frame >> 16) & 0xFF);
}
//parsing responses - helper functions
// bit: description [ clear/0, set/1]
// 0: CH enable [ CH is disabled, CH is enabled]
// 1: DHW enable [ DHW is disabled, DHW is enabled]
// 2: Cooling enable [ Cooling is disabled, Cooling is enabled]
// 3: OTC active [OTC not active, OTC is active]
// 4: CH2 enable [CH2 is disabled, CH2 is enabled]
// 5: reserved
// 6: reserved
// 7: reserved
bool isCentralHeatingEnabled() {
return OTcurrentSystemState.MasterStatus & 0x01;
}
bool isDomesticHotWaterEnabled() {
return OTcurrentSystemState.MasterStatus & 0x02;
}
bool isCoolingEnabled() {
return OTcurrentSystemState.MasterStatus & 0x04;
}
bool isOutsideTemperatureCompensationActive() {
return OTcurrentSystemState.MasterStatus & 0x08;
}
bool isCentralHeating2enabled() {
return OTcurrentSystemState.MasterStatus & 0x10;
}
//Slave
// bit: description [ clear/0, set/1]
// 0: fault indication [ no fault, fault ]
// 1: CH mode [CH not active, CH active]
// 2: DHW mode [ DHW not active, DHW active]
// 3: Flame status [ flame off, flame on ]
// 4: Cooling status [ cooling mode not active, cooling mode active ]
// 5: CH2 mode [CH2 not active, CH2 active]
// 6: diagnostic indication [no diagnostics, diagnostic event]
// 7: reserved
bool isFaultIndicator() {
return OTcurrentSystemState.SlaveStatus & 0x01;
}
bool isCentralHeatingActive() {
return OTcurrentSystemState.SlaveStatus & 0x02;
}
bool isDomesticHotWaterActive() {
return OTcurrentSystemState.SlaveStatus & 0x04;
}
bool isFlameStatus() {
return OTcurrentSystemState.SlaveStatus & 0x08;
}
bool isCoolingActive() {
return OTcurrentSystemState.SlaveStatus & 0x10;
}
bool isCentralHeating2Active() {
return OTcurrentSystemState.SlaveStatus & 0x20;
}
bool isDiagnosticIndicator() {
return OTcurrentSystemState.SlaveStatus & 0x40;
}
//bit: [clear/0, set/1]
//0: Service request [service not reqd, service required]
//1: Lockout-reset [ remote reset disabled, rr enabled]
//2: Low water press [ no WP fault, water pressure fault]
//3: Gas/flame fault [ no G/F fault, gas/flame fault ]
//4: Air press fault [ no AP fault, air pressure fault ]
//5: Water over-temp[ no OvT fault, over-temperat. Fault]
//6: reserved
//7: reserved
bool isServiceRequest() {
return OTcurrentSystemState.ASFflags & 0x0100;
}
bool isLockoutReset() {
return OTcurrentSystemState.ASFflags & 0x0200;
}
bool isLowWaterPressure() {
return OTcurrentSystemState.ASFflags & 0x0400;
}
bool isGasFlameFault() {
return OTcurrentSystemState.ASFflags & 0x0800;
}
bool isAirTemperature() {
return OTcurrentSystemState.ASFflags & 0x1000;
}
bool isWaterOverTemperature() {
return OTcurrentSystemState.ASFflags & 0x2000;
}
const char *byte_to_binary(int x)
{
static char b[9];
b[0] = '\0';
int z;
for (z = 128; z > 0; z >>= 1) {
strcat(b, ((x & z) == z) ? "1" : "0");
}
return b;
} //byte_to_binary
/*
This determines if the value in the OpenTherm message is valid and can be used in the data object, MQTT or REST API.
Rules are:
- if the message is overriden (R and A messages override B and T messages), then the value is not valid for use.
- if the OT message is a READ message, and the received OT msg is being read and acknowledged, then the value is valid.
- if the OT message is a WRITE message, and the received OT msg is being written (OT_WRITE_DATA), then the value is valid.
- if the OT message is a READ/WRITE message, and receive OT msg is being read and ackownledge, or, is being written, then the value is valid.
- if the OT message is a status message (from Heating, HAVC or Solar), then the message is always valid.
*/
bool is_value_valid(OpenthermData_t OT, OTlookup_t OTlookup) {
if (OT.skipthis) return false;
bool _valid = false;
_valid = _valid || (OTlookup.msgcmd==OT_READ && OT.type==OT_READ_ACK);
_valid = _valid || (OTlookup.msgcmd==OT_WRITE && OTdata.type==OT_WRITE_DATA);
_valid = _valid || (OTlookup.msgcmd==OT_RW && (OT.type==OT_READ_ACK || OTdata.type==OT_WRITE_DATA));
_valid = _valid || (OTdata.id==OT_Statusflags) || (OTdata.id==OT_StatusVH) || (OTdata.id==OT_SolarStorageMaster);;
return _valid;
}
void print_f88(float& value)
{
//function to print data
float _value = roundf(OTdata.f88()*100.0f) / 100.0f; // round float 2 digits, like this: x.xx
// AddLog("%s = %3.2f %s", OTlookupitem.label, _value , OTlookupitem.unit);
char _msg[15] {0};
dtostrf(_value, 3, 2, _msg);
AddLogf("%s = %s %s", OTlookupitem.label, _msg , OTlookupitem.unit);
//SendMQTT
if (is_value_valid(OTdata, OTlookupitem)){
sendMQTTData(messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), _msg);
value = _value;
}
}
void print_s16(int16_t& value)
{
int16_t _value = OTdata.s16();
// AddLogf("%s = %5d %s", OTlookupitem.label, _value, OTlookupitem.unit);
//Build string for MQTT
char _msg[15] {0};
itoa(_value, _msg, 10);
AddLogf("%s = %s %s", OTlookupitem.label, _msg, OTlookupitem.unit);
//SendMQTT
if (is_value_valid(OTdata, OTlookupitem)){
sendMQTTData(messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), _msg);
value = _value;
}
}
void print_s8s8(uint16_t& value)
{
AddLogf("%s = %3d / %3d %s", OTlookupitem.label, (int8_t)OTdata.valueHB, (int8_t)OTdata.valueLB, OTlookupitem.unit);
//Build string for MQTT
char _msg[15] {0};
char _topic[50] {0};
itoa((int8_t)OTdata.valueHB, _msg, 10);
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_value_hb", sizeof(_topic));
//AddLogf("%s = %s %s", OTlookupitem.label, _msg, OTlookupitem.unit);
if (is_value_valid(OTdata, OTlookupitem)){
sendMQTTData(_topic, _msg);
}
//Build string for MQTT
itoa((int8_t)OTdata.valueLB, _msg, 10);
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_value_lb", sizeof(_topic));
//AddLogf("%s = %s %s", OTlookupitem.label, _msg, OTlookupitem.unit);
if (is_value_valid(OTdata, OTlookupitem)){
sendMQTTData(_topic, _msg);
value = OTdata.u16();
}
}
void print_u16(uint16_t& value)
{
uint16_t _value = OTdata.u16();
//Build string for MQTT
char _msg[15] {0};
utoa(_value, _msg, 10);
AddLogf("%s = %s %s", OTlookupitem.label, _msg, OTlookupitem.unit);
//SendMQTT
if (is_value_valid(OTdata, OTlookupitem)){
sendMQTTData(messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), _msg);
value = _value;
}
}
void print_status(uint16_t& value)
{
char _flag8_master[9] {0};
char _flag8_slave[9] {0};
if (OTdata.masterslave == 0) {
// Parse master bits
//bit: [clear/0, set/1]
// 0: CH enable [ CH is disabled, CH is enabled]
// 1: DHW enable [ DHW is disabled, DHW is enabled]
// 2: Cooling enable [ Cooling is disabled, Cooling is enabled]]
// 3: OTC active [OTC not active, OTC is active]
// 4: CH2 enable [CH2 is disabled, CH2 is enabled]
// 5: Summer/winter mode [Summertime, Wintertime]
// 6: DHW blocking [ DHW not blocking, DHW blocking ]
// 7: reserved
_flag8_master[0] = (((OTdata.valueHB) & 0x01) ? 'C' : '-');
_flag8_master[1] = (((OTdata.valueHB) & 0x02) ? 'D' : '-');
_flag8_master[2] = (((OTdata.valueHB) & 0x04) ? 'C' : '-');
_flag8_master[3] = (((OTdata.valueHB) & 0x08) ? 'O' : '-');
_flag8_master[4] = (((OTdata.valueHB) & 0x10) ? '2' : '-');
_flag8_master[5] = (((OTdata.valueHB) & 0x20) ? 'S' : 'W');
_flag8_master[6] = (((OTdata.valueHB) & 0x40) ? 'B' : '-');
_flag8_master[7] = (((OTdata.valueHB) & 0x80) ? '.' : '-');
_flag8_master[8] = '\0';
AddLogf("%s = Master [%s]", OTlookupitem.label, _flag8_master);
//Master Status
if (is_value_valid(OTdata, OTlookupitem)){
sendMQTTData("status_master", _flag8_master);
sendMQTTData("ch_enable", (((OTdata.valueHB) & 0x01) ? "ON" : "OFF"));
sendMQTTData("dhw_enable", (((OTdata.valueHB) & 0x02) ? "ON" : "OFF"));
sendMQTTData("cooling_enable", (((OTdata.valueHB) & 0x04) ? "ON" : "OFF"));
sendMQTTData("otc_active", (((OTdata.valueHB) & 0x08) ? "ON" : "OFF"));
sendMQTTData("ch2_enable", (((OTdata.valueHB) & 0x10) ? "ON" : "OFF"));
sendMQTTData("summerwintertime", (((OTdata.valueHB) & 0x20) ? "ON" : "OFF"));
sendMQTTData("dhw_blocking", (((OTdata.valueHB) & 0x40) ? "ON" : "OFF"));
OTcurrentSystemState.MasterStatus = OTdata.valueHB;
}
} else {
// Parse slave bits
// 0: fault indication [ no fault, fault ]
// 1: CH mode [CH not active, CH active]
// 2: DHW mode [ DHW not active, DHW active]
// 3: Flame status [ flame off, flame on ]
// 4: Cooling status [ cooling mode not active, cooling mode active ]
// 5: CH2 mode [CH2 not active, CH2 active]
// 6: diagnostic indication [no diagnostics, diagnostic event]
// 7: Electricity production [no eletric production, eletric production]
_flag8_slave[0] = (((OTdata.valueLB) & 0x01) ? 'E' : '-');
_flag8_slave[1] = (((OTdata.valueLB) & 0x02) ? 'C' : '-');
_flag8_slave[2] = (((OTdata.valueLB) & 0x04) ? 'W' : '-');
_flag8_slave[3] = (((OTdata.valueLB) & 0x08) ? 'F' : '-');
_flag8_slave[4] = (((OTdata.valueLB) & 0x10) ? 'C' : '-');
_flag8_slave[5] = (((OTdata.valueLB) & 0x20) ? '2' : '-');
_flag8_slave[6] = (((OTdata.valueLB) & 0x40) ? 'D' : '-');
_flag8_slave[7] = (((OTdata.valueLB) & 0x80) ? 'P' : '-');
_flag8_slave[8] = '\0';
AddLogf("%s = Slave [%s]", OTlookupitem.label, _flag8_slave);
//Slave Status
if (is_value_valid(OTdata, OTlookupitem)){
sendMQTTData("status_slave", _flag8_slave);
sendMQTTData("fault", (((OTdata.valueLB) & 0x01) ? "ON" : "OFF")); //delayms(5);
sendMQTTData("centralheating", (((OTdata.valueLB) & 0x02) ? "ON" : "OFF")); //delayms(5);
sendMQTTData("domestichotwater", (((OTdata.valueLB) & 0x04) ? "ON" : "OFF")); //delayms(5);
sendMQTTData("flame", (((OTdata.valueLB) & 0x08) ? "ON" : "OFF")); //delayms(5);
sendMQTTData("cooling", (((OTdata.valueLB) & 0x10) ? "ON" : "OFF")); //delayms(5);
sendMQTTData("centralheating2", (((OTdata.valueLB) & 0x20) ? "ON" : "OFF")); //delayms(5);
sendMQTTData("diagnostic_indicator", (((OTdata.valueLB) & 0x40) ? "ON" : "OFF")); //delayms(5);
sendMQTTData("eletric_production", (((OTdata.valueLB) & 0x80) ? "ON" : "OFF")); //delayms(5);
OTcurrentSystemState.SlaveStatus = OTdata.valueLB;
}
}
if (is_value_valid(OTdata, OTlookupitem)){
// AddLogf("Status u16 [%04x] _value [%04x] hb [%02x] lb [%02x]", OTdata.u16(), _value, OTdata.valueHB, OTdata.valueLB);
value = (OTcurrentSystemState.MasterStatus<<8) | OTcurrentSystemState.SlaveStatus;
}
}
void print_solar_storage_status(uint16_t& value)
{
char _msg[15] {0};
if (OTdata.masterslave == 0) {
// Master Solar Storage
// ID101:HB012: Master Solar Storage: Solar mode
uint8_t MasterSolarMode = (OTdata.valueHB) & 0x7;
AddLogf("%s = Solar Storage Master Mode [%d] ", OTlookupitem.label, MasterSolarMode);
if (is_value_valid(OTdata, OTlookupitem)){
sendMQTTData(F("solar_storage_master_mode"), itoa(MasterSolarMode, _msg, 10)); //delayms(5);
OTcurrentSystemState.SolarMasterStatus = OTdata.valueHB;
}
} else {
//Slave
// ID101:LB0: Slave Solar Storage: Fault indication
uint8_t SlaveSolarFaultIndicator = (OTdata.valueLB) & 0x01;
// ID101:LB123: Slave Solar Storage: Solar mode status
uint8_t SlaveSolarModeStatus = (OTdata.valueLB>>1) & 0x07;
// ID101:LB45: Slave Solar Storage: Solar status
uint8_t SlaveSolarStatus = (OTdata.valueLB>>4)& 0x03;
AddLogf("\r\n%s = Slave Solar Fault Indicator [%d] ", OTlookupitem.label, SlaveSolarFaultIndicator);
AddLogf("\r\n%s = Slave Solar Mode Status [%d] ", OTlookupitem.label, SlaveSolarModeStatus);
AddLogf("\r\n%s = Slave Solar Status [%d] ", OTlookupitem.label, SlaveSolarStatus);
if (is_value_valid(OTdata, OTlookupitem)){
sendMQTTData(F("solar_storage_slave_fault_incidator"), ((SlaveSolarFaultIndicator) ? "ON" : "OFF"));
sendMQTTData(F("solar_storage_mode_status"), itoa(SlaveSolarModeStatus, _msg, 10));
sendMQTTData(F("solar_storage_slave_status"), itoa(SlaveSolarStatus, _msg, 10));
OTcurrentSystemState.SolarSlaveStatus = OTdata.valueLB;
}
}
if (is_value_valid(OTdata, OTlookupitem)){
//OTGWDebugTf(PSTR("Solar Storage Master / Slave Mode u16 [%04x] _value [%04x] hb [%02x] lb [%02x]"), OTdata.u16(), _value, OTdata.valueHB, OTdata.valueLB);
value = (OTcurrentSystemState.SolarMasterStatus<<8) | OTcurrentSystemState.SolarSlaveStatus;
}
}
void print_statusVH(uint16_t& value)
{
char _flag8_master[9] {0};
char _flag8_slave[9] {0};
if (OTdata.masterslave == 0){
// Parse master bits
//bit: [clear/0, set/1]
// ID70:HB0: Master status ventilation / heat-recovery: Ventilation enable
// ID70:HB1: Master status ventilation / heat-recovery: Bypass postion
// ID70:HB2: Master status ventilation / heat-recovery: Bypass mode
// ID70:HB3: Master status ventilation / heat-recovery: Free ventilation mode
// 4: reserved
// 5: reserved
// 6: reserved
// 7: reserved
_flag8_master[0] = (((OTdata.valueHB) & 0x01) ? 'V' : '-');
_flag8_master[1] = (((OTdata.valueHB) & 0x02) ? 'P' : '-');
_flag8_master[2] = (((OTdata.valueHB) & 0x04) ? 'M' : '-');
_flag8_master[3] = (((OTdata.valueHB) & 0x08) ? 'F' : '-');
_flag8_master[4] = (((OTdata.valueHB) & 0x10) ? '.' : '-');
_flag8_master[5] = (((OTdata.valueHB) & 0x20) ? '.' : '-');
_flag8_master[6] = (((OTdata.valueHB) & 0x40) ? '.' : '-');
_flag8_master[7] = (((OTdata.valueHB) & 0x80) ? '.' : '-');
_flag8_master[8] = '\0';
AddLogf("%s = VH Master [%s]", OTlookupitem.label, _flag8_master);
//Master Status
if (is_value_valid(OTdata, OTlookupitem)){
sendMQTTData(F("status_vh_master"), _flag8_master);
sendMQTTData(F("vh_ventilation_enabled"), (((OTdata.valueHB) & 0x01) ? "ON" : "OFF"));
sendMQTTData(F("vh_bypass_position"), (((OTdata.valueHB) & 0x02) ? "ON" : "OFF"));
sendMQTTData(F("vh_bypass_mode"), (((OTdata.valueHB) & 0x04) ? "ON" : "OFF"));
sendMQTTData(F("vh_free_ventlation_mode"), (((OTdata.valueHB) & 0x08) ? "ON" : "OFF"));
OTcurrentSystemState.MasterStatusVH = OTdata.valueHB;
}
} else {
// Parse slave bits
// ID70:LB0: Slave status ventilation / heat-recovery: Fault indication
// ID70:LB1: Slave status ventilation / heat-recovery: Ventilation mode
// ID70:LB2: Slave status ventilation / heat-recovery: Bypass status
// ID70:LB3: Slave status ventilation / heat-recovery: Bypass automatic status
// ID70:LB4: Slave status ventilation / heat-recovery: Free ventilation status
// ID70:LB6: Slave status ventilation / heat-recovery: Diagnostic indication
_flag8_slave[0] = (((OTdata.valueLB) & 0x01) ? 'F' : '-');
_flag8_slave[1] = (((OTdata.valueLB) & 0x02) ? 'V' : '-');
_flag8_slave[2] = (((OTdata.valueLB) & 0x04) ? 'P' : '-');
_flag8_slave[3] = (((OTdata.valueLB) & 0x08) ? 'A' : '-');
_flag8_slave[4] = (((OTdata.valueLB) & 0x10) ? 'F' : '-');
_flag8_slave[5] = (((OTdata.valueLB) & 0x20) ? '.' : '-');
_flag8_slave[6] = (((OTdata.valueLB) & 0x40) ? 'D' : '-');
_flag8_slave[7] = (((OTdata.valueLB) & 0x80) ? '.' : '-');
_flag8_slave[8] = '\0';
AddLogf("%s = VH Slave [%s]", OTlookupitem.label, _flag8_slave);
//Slave Status
if (is_value_valid(OTdata, OTlookupitem)){
sendMQTTData(F("status_vh_slave"), _flag8_slave);
sendMQTTData(F("vh_fault"), (((OTdata.valueLB) & 0x01) ? "ON" : "OFF"));
sendMQTTData(F("vh_ventlation_mode"), (((OTdata.valueLB) & 0x02) ? "ON" : "OFF"));
sendMQTTData(F("vh_bypass_status"), (((OTdata.valueLB) & 0x04) ? "ON" : "OFF"));
sendMQTTData(F("vh_bypass_automatic_status"), (((OTdata.valueLB) & 0x08) ? "ON" : "OFF"));
sendMQTTData(F("vh_free_ventliation_status"), (((OTdata.valueLB) & 0x10) ? "ON" : "OFF"));
sendMQTTData(F("vh_diagnostic_indicator"), (((OTdata.valueLB) & 0x40) ? "ON" : "OFF"));
OTcurrentSystemState.SlaveStatusVH = OTdata.valueLB;
}
}
if (is_value_valid(OTdata, OTlookupitem)){
//OTGWDebugTf(PSTR("Status u16 [%04x] _value [%04x] hb [%02x] lb [%02x]"), OTdata.u16(), _value, OTdata.valueHB, OTdata.valueLB);
value = (OTcurrentSystemState.MasterStatusVH<<8) | OTcurrentSystemState.SlaveStatusVH;
}
}
void print_ASFflags(uint16_t& value)
{
AddLogf("%s = ASF flags[%s] OEM faultcode [%3d]", OTlookupitem.label, byte_to_binary(OTdata.valueHB), OTdata.valueLB);
if (is_value_valid(OTdata, OTlookupitem)){
//Build string for MQTT
char _msg[15] {0};
//Application Specific Fault
sendMQTTData(F("ASF_flags"), byte_to_binary(OTdata.valueHB));
//OEM fault code
utoa(OTdata.valueLB, _msg, 10);
sendMQTTData(F("OEMFaultCode"), _msg);
//bit: [clear/0, set/1]
//0: Service request [service not reqd, service required]
//1: Lockout-reset [ remote reset disabled, rr enabled]
//2: Low water press [ no WP fault, water pressure fault]
//3: Gas/flame fault [ no G/F fault, gas/flame fault ]
//4: Air press fault [ no AP fault, air pressure fault ]
//5: Water over-temp[ no OvT fault, over-temperat. Fault]
//6: reserved
//7: reserved
sendMQTTData(F("service_request"), (((OTdata.valueHB) & 0x01) ? "ON" : "OFF"));
sendMQTTData(F("lockout_reset"), (((OTdata.valueHB) & 0x02) ? "ON" : "OFF"));
sendMQTTData(F("low_water_pressure"), (((OTdata.valueHB) & 0x04) ? "ON" : "OFF"));
sendMQTTData(F("gas_flame_fault"), (((OTdata.valueHB) & 0x08) ? "ON" : "OFF"));
sendMQTTData(F("air_pressure_fault"), (((OTdata.valueHB) & 0x10) ? "ON" : "OFF"));
sendMQTTData(F("water_over_temperature"),(((OTdata.valueHB) & 0x20) ? "ON" : "OFF"));
value = OTdata.u16();
}
}
void print_RBPflags(uint16_t& value)
{
AddLogf("%s = M[%s] OEM fault code [%3d]", OTlookupitem.label, byte_to_binary(OTdata.valueHB), OTdata.valueLB);
if (is_value_valid(OTdata, OTlookupitem)){
//Build string for MQTT
//Remote Boiler Paramaters
sendMQTTData(F("RBP_flags_transfer_enable"), byte_to_binary(OTdata.valueHB));
sendMQTTData(F("RBP_flags_read_write"), byte_to_binary(OTdata.valueLB));
//bit: [clear/0, set/1]
//0: DHW setpoint
//1: max CH setpoint
//2: reserved
//3: reserved
//4: reserved
//5: reserved
//6: reserved
//7: reserved
sendMQTTData(F("rbp_dhw_setpoint"), (((OTdata.valueHB) & 0x01) ? "ON" : "OFF"));
sendMQTTData(F("rbp_max_ch_setpoint"), (((OTdata.valueHB) & 0x02) ? "ON" : "OFF"));
//bit: [clear/0, set/1]
//0: read write DHW setpoint
//1: read write max CH setpoint
//2: reserved
//3: reserved
//4: reserved
//5: reserved
//6: reserved
//7: reserved
sendMQTTData(F("rbp_rw_dhw_setpoint"), (((OTdata.valueLB) & 0x01) ? "ON" : "OFF"));
sendMQTTData(F("rbp_rw_max_ch_setpoint"), (((OTdata.valueLB) & 0x02) ? "ON" : "OFF"));
value = OTdata.u16();
}
}
void print_slavememberid(uint16_t& value)
{
AddLogf("%s = Slave Config[%s] MemberID code [%3d]", OTlookupitem.label, byte_to_binary(OTdata.valueHB), OTdata.valueLB);
if (is_value_valid(OTdata, OTlookupitem)){
//Build string for SendMQTT
sendMQTTData(F("slave_configuration"), byte_to_binary(OTdata.valueHB));
char _msg[15] {0};
utoa(OTdata.valueLB, _msg, 10);
sendMQTTData(F("slave_memberid_code"), _msg);
// bit: description [ clear/0, set/1]
// 0: DHW present [ dhw not present, dhw is present ]
// 1: Control type [ modulating, on/off ]
// 2: Cooling config [ cooling not supported,
// cooling supported]
// 3: DHW config [instantaneous or not-specified,
// storage tank]
// 4: Master low-off&pump control function [allowed,
// not allowed]
// 5: CH2 present [CH2 not present, CH2 present]
// 6: Remote water filling function
// 7: Heat/cool mode control
sendMQTTData(F("dhw_present"), (((OTdata.valueHB) & 0x01) ? "ON" : "OFF"));
sendMQTTData(F("control_type_modulation"), (((OTdata.valueHB) & 0x02) ? "ON" : "OFF"));
sendMQTTData(F("cooling_config"), (((OTdata.valueHB) & 0x04) ? "ON" : "OFF"));
sendMQTTData(F("dhw_config"), (((OTdata.valueHB) & 0x08) ? "ON" : "OFF"));
sendMQTTData(F("master_low_off_pump_control_function"), (((OTdata.valueHB) & 0x10) ? "ON" : "OFF"));
sendMQTTData(F("ch2_present"), (((OTdata.valueHB) & 0x20) ? "ON" : "OFF"));
sendMQTTData(F("remote_water_filling_function"), (((OTdata.valueHB) & 0x40) ? "ON" : "OFF"));
sendMQTTData(F("heat_cool_mode_control"), (((OTdata.valueHB) & 0x80) ? "ON" : "OFF"));
value = OTdata.u16();
}
}
void print_mastermemberid(uint16_t& value)
{
AddLogf("%s = Master Config[%s] MemberID code [%3d]", OTlookupitem.label, byte_to_binary(OTdata.valueHB), OTdata.valueLB);
if (is_value_valid(OTdata, OTlookupitem)){
//Build string for MQTT
char _msg[15] {0};
sendMQTTData(F("master_configuration"), byte_to_binary(OTdata.valueHB));
sendMQTTData(F("master_configuration_smart_power"), (((OTdata.valueHB) & 0x01) ? "ON" : "OFF"));
utoa(OTdata.valueLB, _msg, 10);
sendMQTTData(F("master_memberid_code"), _msg);
value = OTdata.u16();
}
}
void print_vh_configmemberid(uint16_t& value)
{
AddLogf("%s = VH Config[%s] MemberID code [%3d]", OTlookupitem.label, byte_to_binary(OTdata.valueHB), OTdata.valueLB);
if (is_value_valid(OTdata, OTlookupitem)){
//Build string for MQTT
char _msg[15] {0};
sendMQTTData(F("vh_configuration"), byte_to_binary(OTdata.valueHB));
sendMQTTData(F("vh_configuration_system_type"), (((OTdata.valueHB) & 0x01) ? "ON" : "OFF"));
sendMQTTData(F("vh_configuration_bypass"), (((OTdata.valueHB) & 0x02) ? "ON" : "OFF"));
sendMQTTData(F("vh_configuration_speed_control"), (((OTdata.valueHB) & 0x04) ? "ON" : "OFF"));
utoa(OTdata.valueLB, _msg, 10);
sendMQTTData(F("vh_memberid_code"), _msg);
value = OTdata.u16();
}
}
void print_solarstorage_slavememberid(uint16_t& value)
{
AddLogf("%s = Solar Storage Slave Config[%s] MemberID code [%3d]", OTlookupitem.label, byte_to_binary(OTdata.valueHB), OTdata.valueLB);
if (is_value_valid(OTdata, OTlookupitem)){
//Build string for SendMQTT
sendMQTTData(F("solar_storage_slave_configuration"), byte_to_binary(OTdata.valueHB));
char _msg[15] {0};
utoa(OTdata.valueLB, _msg, 10);
sendMQTTData(F("solar_storage_slave_memberid_code"), _msg);
//ID103:HB0: Slave Configuration Solar Storage: System type1
sendMQTTData(F("solar_storage_system_type"), (((OTdata.valueHB) & 0x01) ? "ON" : "OFF"));
value = OTdata.u16();
}
}
void print_remoteoverridefunction(uint16_t& value)
{
// MsdID 100 Remote override room setpoint
// LB: Remote override function
// bit: description [ clear/0, set/1]
// 0: Manual change priority [disable overruling remote
// setpoint by manual setpoint change, enable overruling
// remote setpoint by manual setpoint change ]
// 1: Program change priority [disable overruling remote
// setpoint by program setpoint change, enable overruling
// remote setpoint by program setpoint change ]
// 2: reserved
// 3: reserved
// 4: reserved
// 5: reserved
// 6: reserved
// 7: reserved
// HB: reserved
AddLogf("%s = flag8 = [%s] - decimal = [%3d]", OTlookupitem.label, byte_to_binary(OTdata.valueLB), OTdata.valueLB);
if (is_value_valid(OTdata, OTlookupitem)){
//Build string for MQTT
char _topic[50] {0};
//flag8 value
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_flag8", sizeof(_topic));
sendMQTTData(_topic, byte_to_binary(OTdata.valueLB));
//report remote override flags to MQTT
sendMQTTData(F("remote_override_manual_change_priority"), (((OTdata.valueLB) & 0x01) ? "ON" : "OFF"));
sendMQTTData(F("remote_override_program_change_priority"), (((OTdata.valueLB) & 0x02) ? "ON" : "OFF"));
value = OTdata.u16();
}
}
void print_flag8u8(uint16_t& value)
{
AddLogf("%s = M[%s] - [%3d]", OTlookupitem.label, byte_to_binary(OTdata.valueHB), OTdata.valueLB);
if (is_value_valid(OTdata, OTlookupitem)){
//Build string for MQTT
char _topic[50] {0};
//flag8 value
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_flag8", sizeof(_topic));
sendMQTTData(_topic, byte_to_binary(OTdata.valueHB));
//u8 value
char _msg[15] {0};
utoa(OTdata.valueLB, _msg, 10);
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_code", sizeof(_topic));
sendMQTTData(_topic, _msg);
value = OTdata.u16();
}
}
void print_flag8(uint16_t& value)
{
AddLogf("%s = flag8 = [%s] - decimal = [%3d]", OTlookupitem.label, byte_to_binary(OTdata.valueLB), OTdata.valueLB);
if (is_value_valid(OTdata, OTlookupitem)){
//Build string for MQTT
char _topic[50] {0};
//flag8 value
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_flag8", sizeof(_topic));
sendMQTTData(_topic, byte_to_binary(OTdata.valueLB));
value = OTdata.u16();
}
}
void print_flag8flag8(uint16_t& value)
{
//Build string for MQTT
char _topic[50] {0};
//flag8 valueHB
AddLogf("%s = HB flag8[%s] -[%3d] ", OTlookupitem.label, byte_to_binary(OTdata.valueHB), OTdata.valueHB);
if (is_value_valid(OTdata, OTlookupitem)){
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_hb_flag8", sizeof(_topic));
sendMQTTData(_topic, byte_to_binary(OTdata.valueHB));
}
//flag8 valueLB
AddLogf("%s = LB flag8[%s] - [%3d]", OTlookupitem.label, byte_to_binary(OTdata.valueLB), OTdata.valueLB);
if (is_value_valid(OTdata, OTlookupitem)){
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_lb_flag8", sizeof(_topic));
sendMQTTData(_topic, byte_to_binary(OTdata.valueLB));
value = OTdata.u16();
}
}
void print_vh_remoteparametersetting(uint16_t& value)
{
//Build string for MQTT
char _topic[50] {0};
//flag8 valueHB
AddLogf("%s = HB flag8[%s] -[%3d] ", OTlookupitem.label, byte_to_binary(OTdata.valueHB), OTdata.valueHB);
if (is_value_valid(OTdata, OTlookupitem)){
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_hb_flag8", sizeof(_topic));
sendMQTTData(_topic, byte_to_binary(OTdata.valueHB));
sendMQTTData(F("vh_tramfer_enble_nominal_ventlation_value"), (((OTdata.valueHB) & 0x01) ? "ON" : "OFF"));
}
//flag8 valueLB
AddLogf("%s = LB flag8[%s] - [%3d]", OTlookupitem.label, byte_to_binary(OTdata.valueLB), OTdata.valueLB);
if (is_value_valid(OTdata, OTlookupitem)){
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_lb_flag8", sizeof(_topic));
sendMQTTData(_topic, byte_to_binary(OTdata.valueLB));
sendMQTTData(F("vh_rw_nominal_ventlation_value"), (((OTdata.valueLB) & 0x01) ? "ON" : "OFF"));
value = OTdata.u16();
}
}
void print_command(uint16_t& value)
{
//Known Commands
// ID4 (HB=1): Remote Request Boiler Lockout-reset
// ID4 (HB=2): Remote Request Water filling
// ID4 (HB=10): Remote Request Service request reset
AddLogf("%s = %3d / %3d %s", OTlookupitem.label, (uint8_t)OTdata.valueHB, (uint8_t)OTdata.valueLB, OTlookupitem.unit);
if (is_value_valid(OTdata, OTlookupitem)){
//Build string for MQTT
char _topic[50] {0};
char _msg[10] {0};
//flag8 valueHB
utoa((OTdata.valueHB), _msg, 10);
//AddLogf("%s = HB u8[%s] [%3d]", OTlookupitem.label, _msg, OTdata.valueHB);
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_hb_u8", sizeof(_topic));
sendMQTTData(_topic, _msg);
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_remote_command", sizeof(_topic));
switch (OTdata.valueHB) {
case 1: sendMQTTData(_topic, "Remote Request Boiler Lockout-reset"); AddLogf("\r\n%s = remote command [%s]", OTlookupitem.label, "Remote Request Boiler Lockout-reset"); break;
case 2: sendMQTTData(_topic, "Remote Request Water filling"); AddLogf("\r\n%s = remote command [%s]", OTlookupitem.label, "Remote Request Water filling"); break;
case 10: sendMQTTData(_topic, "Remote Request Service request reset"); AddLogf("\r\n%s = remote command [%s]", OTlookupitem.label, "Remote Request Service request reset");break;
default: sendMQTTData(_topic, "Unknown command"); AddLogf("\r\n%s = remote command [%s]", OTlookupitem.label, "Unknown command");break;
}
//flag8 valueLB
utoa((OTdata.valueLB), _msg, 10);
//AddLogf("%s = LB u8[%s] [%3d]", OTlookupitem.label, _msg, OTdata.valueLB);
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_lb_u8", sizeof(_topic));
sendMQTTData(_topic, _msg);
value = OTdata.u16();
}
}
void print_u8u8(uint16_t& value)
{
AddLogf("%s = %3d / %3d %s", OTlookupitem.label, (uint8_t)OTdata.valueHB, (uint8_t)OTdata.valueLB, OTlookupitem.unit);
if (is_value_valid(OTdata, OTlookupitem)){
//Build string for MQTT
char _topic[50] {0};
char _msg[10] {0};
//flag8 valueHB
utoa((OTdata.valueHB), _msg, 10);
//AddLogf("%s = HB u8[%s] [%3d]", OTlookupitem.label, _msg, OTdata.valueHB);
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_hb_u8", sizeof(_topic));
sendMQTTData(_topic, _msg);
//flag8 valueLB
utoa((OTdata.valueLB), _msg, 10);
//AddLogf("%s = LB u8[%s] [%3d]", OTlookupitem.label, _msg, OTdata.valueLB);
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_lb_u8", sizeof(_topic));
sendMQTTData(_topic, _msg);
value = OTdata.u16();
}
}
void print_date(uint16_t& value)
{
AddLogf("%s = %3d / %3d %s", OTlookupitem.label, (uint8_t)OTdata.valueHB, (uint8_t)OTdata.valueLB, OTlookupitem.unit);
if (is_value_valid(OTdata, OTlookupitem)){
//Build string for MQTT
char _topic[50] {0};
char _msg[10] {0};
//flag8 valueHB
utoa((OTdata.valueHB), _msg, 10);
//AddLogf("%s = HB u8[%s] [%3d]", OTlookupitem.label, _msg, OTdata.valueHB);
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_month", sizeof(_topic));
sendMQTTData(_topic, _msg);
//flag8 valueLB
utoa((OTdata.valueLB), _msg, 10);
//AddLogf("%s = LB u8[%s] [%3d]", OTlookupitem.label, _msg, OTdata.valueLB);
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_day_of_month", sizeof(_topic));
sendMQTTData(_topic, _msg);
value = OTdata.u16();
}
}
void print_daytime(uint16_t& value)
{
//function to print data
const char *dayOfWeekName[] { "Unknown", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday", "Unknown" };
AddLogf("%s = %s - %.2d:%.2d", OTlookupitem.label, dayOfWeekName[(OTdata.valueHB >> 5) & 0x7], (OTdata.valueHB & 0x1F), OTdata.valueLB);
if (is_value_valid(OTdata, OTlookupitem)){
//Build string for MQTT
char _topic[50] {0};
char _msg[10] {0};
//dayofweek
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_dayofweek", sizeof(_topic));
sendMQTTData(_topic, dayOfWeekName[(OTdata.valueHB >> 5) & 0x7]);
//hour
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_hour", sizeof(_topic));
sendMQTTData(_topic, itoa((OTdata.valueHB & 0x0F), _msg, 10));
//min
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_minutes", sizeof(_topic));
sendMQTTData(_topic, itoa((OTdata.valueLB), _msg, 10));
value = OTdata.u16();
}
}
//===================[ Command Queue implementatoin ]=============================
#define OTGW_CMD_RETRY 5
#define OTGW_CMD_INTERVAL_MS 5000
#define OTGW_DELAY_SEND_MS 1000
#define MAX_QUEUE_MSGSIZE 127
/*
addOTWGcmdtoqueue adds a command to the queue.
First it checks the queue, if the command is in the queue, it's updated.
Otherwise it's simply added to the queue, unless there are no free queue slots.
*/
//void addOTWGcmdtoqueue(const char* buf, const int len, const bool forceQueue = false, const int16_t delay = OTGW_DELAY_SEND_MS);
void addOTWGcmdtoqueue(const char* buf, const int len, const bool forceQueue, const int16_t delay){
if ((len < 3) || (buf[2] != '=')){
//no valid command of less then 2 bytes
OTGWDebugT("CmdQueue: Error:Not a valid command=[");
for (int i = 0; i < len; i++) {
OTGWDebug((char)buf[i]);
}
OTGWDebugf("] (%d)\r\n", len);
}
//check to see if the cmd is in queue
bool foundcmd = false;
int8_t insertptr = cmdptr; //set insertprt to next empty slot
if (!forceQueue){
char cmd[2]; memset( cmd, 0, sizeof(cmd));
memcpy(cmd, buf, 2);
for (int i=0; i<cmdptr; i++){
if (strstr(cmdqueue[i].cmd, cmd)) {
//found cmd exists, set the inertptr to found slot
foundcmd = true;
insertptr = i;
break;
}
}
}
if (foundcmd) OTGWDebugTf(PSTR("CmdQueue: Found cmd exists in slot [%d]\r\n"), insertptr);
else OTGWDebugTf(PSTR("CmdQueue: Adding cmd end of queue, slot [%d]\r\n"), insertptr);
//insert to the queue
OTGWDebugTf(PSTR("CmdQueue: Insert queue in slot[%d]:"), insertptr);
OTGWDebug("cmd[");
for (int i = 0; i < len; i++) {
OTGWDebug((char)buf[i]);
}
OTGWDebugf("] (%d)\r\n", len);
//copy the command into the queue
int cmdlen = min((int)len , (int)(sizeof(cmdqueue[insertptr].cmd)-1));
memset(cmdqueue[insertptr].cmd, 0, cmdlen+1);
memcpy(cmdqueue[insertptr].cmd, buf, cmdlen);
cmdqueue[insertptr].cmdlen = cmdlen;
cmdqueue[insertptr].retrycnt = 0;
cmdqueue[insertptr].due = millis() + delay; //due right away
//if not found
if (!foundcmd) {
//if not reached max of queue
if (cmdptr < CMDQUEUE_MAX) {
cmdptr++; //next free slot
OTGWDebugTf(PSTR("CmdQueue: Next free queue slot: [%d]\r\n"), cmdptr);
} else OTGWDebugTln(F("CmdQueue: Error: Reached max queue"));
} else OTGWDebugTf(PSTR("CmdQueue: Found command at: [%d] - [%d]\r\n"), insertptr, cmdptr);
OTGWDebugFlush();
}
/*
handleOTGWqueue should be called every second from main loop.
This checks the queue for message are due to be resent.
If retry max is reached the cmd is delete from the queue
*/
void handleOTGWqueue(){
// OTGWDebugTf(PSTR("CmdQueue: Commands in queue [%d]\r\n"), (int)cmdptr);
for (int i = 0; i<cmdptr; i++) {
// OTGWDebugTf(PSTR("CmdQueue: Checking due in queue slot[%d]:[%lu]=>[%lu]\r\n"), (int)i, (unsigned long)millis(), (unsigned long)cmdqueue[i].due);
if (millis() >= cmdqueue[i].due) {
OTGWDebugTf(PSTR("CmdQueue: Queue slot [%d] due\r\n"), i);
sendOTGW(cmdqueue[i].cmd, cmdqueue[i].cmdlen);
cmdqueue[i].retrycnt++;
cmdqueue[i].due = millis() + OTGW_CMD_INTERVAL_MS;
if (cmdqueue[i].retrycnt >= OTGW_CMD_RETRY){
//max retry reached, so delete command from queue
OTGWDebugTf(PSTR("CmdQueue: Delete [%d] from queue\r\n"), i);
for (int j=i; j<cmdptr; j++){
// OTGWDebugTf(PSTR("CmdQueue: Moving [%d] => [%d]\r\n"), j+1, j);
strlcpy(cmdqueue[j].cmd, cmdqueue[j+1].cmd, sizeof(cmdqueue[i].cmd));
cmdqueue[j].cmdlen = cmdqueue[j+1].cmdlen;
cmdqueue[j].retrycnt = cmdqueue[j+1].retrycnt;
cmdqueue[j].due = cmdqueue[j+1].due;
}
cmdptr--;
}
// //exit queue handling, after 1 command
// return;
}
}
OTGWDebugFlush();
}
/*
checkOTGWcmdqueue (buf, len)
This takes response from otgw and checks to see if the command was accepted.
Checks the response, and finds the command (if it's there).
Then checks if incoming response matches what was to be set.
Only then it's deleted from the queue.
*/
void checkOTGWcmdqueue(const char *buf, unsigned int len){
if ((len<3) || (buf[2]!=':')) {
OTGWDebugT("CmdQueue: Error: Not a command response [");
for (unsigned int i = 0; i < len; i++) {
OTGWDebug((char)buf[i]);
}
OTGWDebugf("] (%d)\r\n", len);
return; //not a valid command response
}
OTGWDebugT("CmdQueue: Checking if command is in in queue [");
for (unsigned int i = 0; i < len; i++) {
OTGWDebug((char)buf[i]);
}
OTGWDebugf("] (%d)\r\n", len);
char cmd[3]; memset( cmd, 0, sizeof(cmd));
char value[11]; memset( value, 0, sizeof(value));
memcpy(cmd, buf, 2);
memcpy(value, buf+3, ((len-3)<(sizeof(value)-1))?(len-3):(sizeof(value)-1));
for (int i=0; i<cmdptr; i++){
OTGWDebugTf(PSTR("CmdQueue: Checking [%2s]==>[%d]:[%s] from queue\r\n"), cmd, i, cmdqueue[i].cmd);
if (strstr(cmdqueue[i].cmd, cmd)){
//command found, check value
OTGWDebugTf(PSTR("CmdQueue: Found cmd [%2s]==>[%d]:[%s]\r\n"), cmd, i, cmdqueue[i].cmd);
// if(strstr(cmdqueue[i].cmd, value)){
//value found, thus remove command from queue
OTGWDebugTf(PSTR("CmdQueue: Found value [%s]==>[%d]:[%s]\r\n"), value, i, cmdqueue[i].cmd);
OTGWDebugTf(PSTR("CmdQueue: Remove from queue [%d]:[%s] from queue\r\n"), i, cmdqueue[i].cmd);
for (int j=i; j<cmdptr; j++){
OTGWDebugTf(PSTR("CmdQueue: Moving [%d] => [%d]\r\n"), j+1, j);
strlcpy(cmdqueue[j].cmd, cmdqueue[j+1].cmd, sizeof(cmdqueue[i].cmd));
cmdqueue[j].cmdlen = cmdqueue[j+1].cmdlen;
cmdqueue[j].retrycnt = cmdqueue[j+1].retrycnt;
cmdqueue[j].due = cmdqueue[j+1].due;
}
cmdptr--;
// } else OTGWDebugTf(PSTR("Error: Did not find value [%s]==>[%d]:[%s]\r\n"), value, i, cmdqueue[i].cmd);
}
}
OTGWDebugFlush();
}
//===================[ Send buffer to OTGW ]=============================
/*
sendOTGW(const char* buf, int len) sends a string to the serial OTGW device.
The buffer is send out to OTGW on the serial device instantly, as long as there is space in the buffer.
*/
void sendOTGW(const char* buf, int len)
{
// while (OTGWSerial.availableForWrite() < (len+2)) {
// //cannot write, buffer full, wait for some space in serial out buffer
// feedWatchDog(); //this yields for other processes
// }
//Send the buffer to OTGW when the Serial interface is available
if (OTGWSerial.availableForWrite()>=len+2) {
//check the write buffer
//OTGWDebugf("Serial Write Buffer space = [%d] - needed [%d]\r\n",OTGWSerial.availableForWrite(), (len+2));
OTGWDebugT("Sending to Serial [");
for (int i = 0; i < len; i++) {
OTGWDebug((char)buf[i]);
}
OTGWDebug("] ("); OTGWDebug(len); OTGWDebug(")"); OTGWDebugln();
//write buffer to serial
OTGWSerial.write(buf, len);
OTGWSerial.write('\r');
OTGWSerial.write('\n');
OTGWSerial.flush();
} else OTGWDebugln("Error: Write buffer not big enough!");
}
/*
This function checks if the string received is a valid "raw OT message".
Raw OTmessages are 9 chars long and start with TBARE when talking to OTGW PIC.
Message is not an OTmessage if length is not 9 long OR 3th char is ':' (= OTGW command response)
*/
bool isvalidotmsg(const char *buf, int len){
const char *chk = "TBARE";
bool _ret = (len==9); //check 9 chars long
_ret &= (buf[2]!=':'); //not a otgw command response
_ret &= (strchr(chk, buf[0])!=NULL); //1 char matches any of 'B', 'T', 'A', 'R' or 'E'
return _ret;
}
/*
Process OTGW messages coming from the PIC.
It knows about:
- raw OTmsg format
- error format
- ...
*/
void processOT(const char *buf, int len){
static time_t epochBoilerlastseen = 0;
static time_t epochThermostatlastseen = 0;
static time_t epochGatewaylastseen = 0;
static bool bOTGWboilerpreviousstate = false;
static bool bOTGWthermostatpreviousstate = false;
static bool bOTGWgatewaypreviousstate = false;
static bool bOTGWpreviousstate = false;
time_t now = time(nullptr);
if (isvalidotmsg(buf, len)) {
//OT protocol messages are 9 chars long
if (settingMQTTOTmessage) sendMQTTData(F("otmessage"), buf);
// counter of number of OT messages processed
static int32_t cntOTmessagesprocessed = 0;
cntOTmessagesprocessed++;
// char _msg[15] {0};
// sendMQTTData(F("otmsg_count"), itoa(cntOTmessagesprocessed, _msg, 10));
// source of otmsg
if (buf[0]=='B'){
epochBoilerlastseen = now;
OTdata.rsptype = OTGW_BOILER;
} else if (buf[0]=='T'){
epochThermostatlastseen = now;
OTdata.rsptype = OTGW_THERMOSTAT;
} else if (buf[0]=='R') {
epochGatewaylastseen = now;
OTdata.rsptype = OTGW_REQUEST_BOILER;
} else if (buf[0]=='A') {
epochGatewaylastseen = now;
OTdata.rsptype = OTGW_ANSWER_THERMOSTAT;
} else if (buf[0]=='E') {
OTdata.rsptype = OTGW_PARITY_ERROR;
}
//If the Boiler messages have not been seen for 30 seconds, then set the state to false.
bOTGWboilerstate = (now < (epochBoilerlastseen+30));
if ((bOTGWboilerstate != bOTGWboilerpreviousstate) || (cntOTmessagesprocessed==1)) {
sendMQTTData(F("otgw-pic/boiler_connected"), CCONOFF(bOTGWboilerstate));
bOTGWboilerpreviousstate = bOTGWboilerstate;
}
//If the Thermostat messages have not been seen for 30 seconds, then set the state to false.
bOTGWthermostatstate = (now < (epochThermostatlastseen+30));
if ((bOTGWthermostatstate != bOTGWthermostatpreviousstate) || (cntOTmessagesprocessed==1)){
sendMQTTData(F("otgw-pic/thermostat_connected"), CCONOFF(bOTGWthermostatstate));
bOTGWthermostatpreviousstate = bOTGWthermostatstate;
}
//If the Gateway (A or R) messages have not been seen for 30 seconds, then set the state to false.
//If the Thermostat is NOT connected (so false), then the Gateway will be continuously sending R messages to the boiler, in face the Gateway the acts as the Thermostat
bOTGWgatewaystate = (now < (epochGatewaylastseen+30));
if ((bOTGWgatewaystate != bOTGWgatewaypreviousstate) || (cntOTmessagesprocessed==1)){
sendMQTTData(F("otgw-pic/gateway_mode"), CCONOFF(bOTGWgatewaystate));
bOTGWgatewaypreviousstate = bOTGWgatewaystate;
}
//If both (Boiler and Thermostat and Gateway) are offline, then the OTGW is considered offline as a whole.
bOTGWonline = (bOTGWboilerstate && bOTGWthermostatstate) || (bOTGWboilerstate && bOTGWgatewaystate);
if ((bOTGWonline != bOTGWpreviousstate) || (cntOTmessagesprocessed==1)){
sendMQTTData(F("otgw-pic/otgw_connected"), CCONOFF(bOTGWonline));
sendMQTT(CSTR(MQTTPubNamespace), CONLINEOFFLINE(bOTGWonline));
// nodeMCU online/offline zelf naar 'otgw-firmware/' pushen
bOTGWpreviousstate = bOTGWonline; //remember state, so we can detect statechanges
}
//clear ot log buffer
ClrLog();
//process the OTGW message
const char *bufval = buf + 1; //skip the first char
uint32_t value = 0;
//split 32bit value into the relevant OT protocol parts
memset(OTdata.buf, 0, sizeof(OTdata.buf)); // clear buffer
memcpy(OTdata.buf, buf, len); // copy the raw message to the buffer
OTdata.len = len; // set the length of the message
sscanf(bufval, "%8x", &value); // extract the value
OTdata.value = value; // store the value
OTdata.type = (value >> 28) & 0x7; // byte 1 = take 3 bits that define msg msgType
OTdata.masterslave = (OTdata.type >> 2) & 0x1; // MSB from type --> 0 = master and 1 = slave
OTdata.id = (value >> 16) & 0xFF; // byte 2 = message id 8 bits
OTdata.valueHB = (value >> 8) & 0xFF; // byte 3 = high byte
OTdata.valueLB = value & 0xFF; // byte 4 = low byte
OTdata.time = millis(); // time of reception
OTdata.skipthis = false; // default: do not skip this message (will be sent to MQTT and not stored in state data object)
if (cntOTmessagesprocessed == 1) { //first message needs to be put in the buffer
//just store current message and delay processing
delayedOTdata = OTdata; //store current msg
OTGWDebugln("delaying first message!");
} else { //any other message will be processed
//when the gateway overrides the boiler or thermostat, then do not use the results for decoding anywhere (skip this)
//if B --> A, then gateway tells the thermostat what it needs to hear, then use current A message, and skip B value.
//if T --> R, then gateway overrides the thermostat, and tells the boiler what to do, then use current R message, and skip T value.
bool skipthis = (delayedOTdata.id == OTdata.id) && (OTdata.time - delayedOTdata.time < 500) &&
(((OTdata.rsptype == OTGW_ANSWER_THERMOSTAT) && (delayedOTdata.rsptype == OTGW_BOILER)) ||
((OTdata.rsptype == OTGW_REQUEST_BOILER) && (delayedOTdata.rsptype == OTGW_THERMOSTAT)));
//delay message processing by 1 message, to make sure detection of value decoding is done correctly with R and A message.
tmpOTdata = delayedOTdata; //fetch delayed msg
delayedOTdata = OTdata; //store current msg
OTdata = tmpOTdata; //then process delayed msg
OTdata.skipthis = skipthis; //skip if needed
//when parity error in OTGW then skip data to MQTT nor store it local in data object
OTdata.skipthis |= (OTdata.rsptype == OTGW_PARITY_ERROR);
//keep track of last update time of each message id
msglastupdated[OTdata.id] = now;
//Read information from this OT message ready for use...
PROGMEM_readAnything (&OTmap[OTdata.id], OTlookupitem);
// check wheter MQTT topic needs to be configuered
if (is_value_valid(OTdata, OTlookupitem) && settingMQTTenable ) {
if(getMQTTConfigDone(OTdata.id)==false) {
MQTTDebugTf(PSTR("Need to set MQTT config for message %s (%d)\r\n"), OTlookupitem.label, OTdata.id);
bool success = doAutoConfigureMsgid(OTdata.id, NodeId);
if(success) {
MQTTDebugTf(PSTR("Successfully sent MQTT config for message %s (%d)\r\n"), OTlookupitem.label, OTdata.id);
setMQTTConfigDone(OTdata.id);
} else {
MQTTDebugTf(PSTR("Not able to complete MQTT configuration for message %s (%d)\r\n"), OTlookupitem.label, OTdata.id);
}
} else {
// MQTTDebugTf(PSTR("No need to set MQTT config for message %s (%d)\r\n"), OTlookupitem.label, OTdata.id);
}
}
// Decode and print OpenTherm Gateway Message
switch (OTdata.rsptype){
case OTGW_BOILER:
AddLog("Boiler ");
break;
case OTGW_THERMOSTAT:
AddLog("Thermostat ");
break;
case OTGW_REQUEST_BOILER:
AddLog("Request Boiler ");
break;
case OTGW_ANSWER_THERMOSTAT:
AddLog("Answer Thermostat ");
break;
case OTGW_PARITY_ERROR:
AddLog("Parity Error ");
break;
default:
AddLog("Unknown ");
break;
}
//print OTmessage to debug
AddLogf("%s (%d)", OTdata.buf, OTdata.len);
//OTGWDebugf("[%08x]", OTdata.value); //print message frame
//OTGWDebugf("\ttype[%3d] id[%3d] hb[%3d] lb[%3d]\t", OTdata.type, OTdata.id, OTdata.valueHB, OTdata.valueLB);
//print message Type and ID
AddLogf("[MsgID=%3d]", OTdata.id);
AddLogf("[%-16s]", messageTypeToString(static_cast<OpenThermMessageType>(OTdata.type)));
//OTGWDebugf("[%-30s]", messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)));
//OTGWDebugf("[M=%d]",OTdata.master);
if (OTdata.skipthis){
if (OTdata.rsptype == OTGW_PARITY_ERROR) {
AddLog("P"); //skipped due to parity error
} else {
AddLog("-"); //skipped due to R or A message
}
} else {
if (is_value_valid(OTdata, OTlookupitem)) {
AddLog(">");
} else {
AddLog(" ");
}
}
//next step interpret the OT protocol
//#define OTprint(data, value, text, format) ({ data= value; OTGWDebugf("[%37s]", text); OTGWDebugf("= [format]", data)})
//interpret values f8.8
switch (static_cast<OpenThermMessageID>(OTdata.id)) {
case OT_Statusflags: print_status(OTcurrentSystemState.Statusflags); break;
case OT_TSet: print_f88(OTcurrentSystemState.TSet); break;
case OT_CoolingControl: print_f88(OTcurrentSystemState.CoolingControl); break;
case OT_TsetCH2: print_f88(OTcurrentSystemState.TsetCH2); break;
case OT_TrOverride: print_f88(OTcurrentSystemState.TrOverride); break;
case OT_MaxRelModLevelSetting: print_f88(OTcurrentSystemState.MaxRelModLevelSetting); break;
case OT_TrSet: print_f88(OTcurrentSystemState.TrSet); break;
case OT_TrSetCH2: print_f88(OTcurrentSystemState.TrSetCH2); break;
case OT_RelModLevel: print_f88(OTcurrentSystemState.RelModLevel); break;
case OT_CHPressure: print_f88(OTcurrentSystemState.CHPressure); break;
case OT_DHWFlowRate: print_f88(OTcurrentSystemState.DHWFlowRate); break;
case OT_Tr: print_f88(OTcurrentSystemState.Tr); break;
case OT_Tboiler: print_f88(OTcurrentSystemState.Tboiler);break;
case OT_Tdhw: print_f88(OTcurrentSystemState.Tdhw); break;
case OT_Toutside: print_f88(OTcurrentSystemState.Toutside); break;
case OT_Tret: print_f88(OTcurrentSystemState.Tret); break;
case OT_Tsolarstorage: print_f88(OTcurrentSystemState.Tsolarstorage); break;
case OT_Tsolarcollector: print_s16(OTcurrentSystemState.Tsolarcollector); break;
case OT_TflowCH2: print_f88(OTcurrentSystemState.TflowCH2); break;
case OT_Tdhw2: print_f88(OTcurrentSystemState.Tdhw2 ); break;
case OT_Texhaust: print_s16(OTcurrentSystemState.Texhaust); break;
case OT_Theatexchanger: print_f88(OTcurrentSystemState.Theatexchanger); break;
case OT_TdhwSet: print_f88(OTcurrentSystemState.TdhwSet); break;
case OT_MaxTSet: print_f88(OTcurrentSystemState.MaxTSet); break;
case OT_Hcratio: print_f88(OTcurrentSystemState.Hcratio); break;
case OT_Remoteparameter4: print_f88(OTcurrentSystemState.Remoteparameter4); break;
case OT_Remoteparameter5: print_f88(OTcurrentSystemState.Remoteparameter5); break;
case OT_Remoteparameter6: print_f88(OTcurrentSystemState.Remoteparameter6); break;
case OT_Remoteparameter7: print_f88(OTcurrentSystemState.Remoteparameter7); break;
case OT_Remoteparameter8: print_f88(OTcurrentSystemState.Remoteparameter8); break;
case OT_OpenThermVersionMaster: print_f88(OTcurrentSystemState.OpenThermVersionMaster); break;
case OT_OpenThermVersionSlave: print_f88(OTcurrentSystemState.OpenThermVersionSlave); break;
case OT_ASFflags: print_ASFflags(OTcurrentSystemState.ASFflags); break;
case OT_MasterConfigMemberIDcode: print_mastermemberid(OTcurrentSystemState.MasterConfigMemberIDcode); break;
case OT_SlaveConfigMemberIDcode: print_slavememberid(OTcurrentSystemState.SlaveConfigMemberIDcode); break;
case OT_Command: print_command(OTcurrentSystemState.Command ); break;
case OT_RBPflags: print_RBPflags(OTcurrentSystemState.RBPflags); break;
case OT_TSP: print_u8u8(OTcurrentSystemState.TSP); break;
case OT_TSPindexTSPvalue: print_u8u8(OTcurrentSystemState.TSPindexTSPvalue); break;
case OT_FHBsize: print_u8u8(OTcurrentSystemState.FHBsize); break;
case OT_FHBindexFHBvalue: print_u8u8(OTcurrentSystemState.FHBindexFHBvalue); break;
case OT_MaxCapacityMinModLevel: print_u8u8(OTcurrentSystemState.MaxCapacityMinModLevel); break;
case OT_DayTime: print_daytime(OTcurrentSystemState.DayTime); break;
case OT_Date: print_date(OTcurrentSystemState.Date); break;
case OT_Year: print_u16(OTcurrentSystemState.Year); break;
case OT_TdhwSetUBTdhwSetLB: print_s8s8(OTcurrentSystemState.TdhwSetUBTdhwSetLB ); break;
case OT_MaxTSetUBMaxTSetLB: print_s8s8(OTcurrentSystemState.MaxTSetUBMaxTSetLB); break;
case OT_HcratioUBHcratioLB: print_s8s8(OTcurrentSystemState.HcratioUBHcratioLB); break;
case OT_Remoteparameter4boundaries: print_s8s8(OTcurrentSystemState.Remoteparameter4boundaries); break;
case OT_Remoteparameter5boundaries: print_s8s8(OTcurrentSystemState.Remoteparameter5boundaries); break;
case OT_Remoteparameter6boundaries: print_s8s8(OTcurrentSystemState.Remoteparameter6boundaries); break;
case OT_Remoteparameter7boundaries: print_s8s8(OTcurrentSystemState.Remoteparameter7boundaries); break;
case OT_Remoteparameter8boundaries: print_s8s8(OTcurrentSystemState.Remoteparameter8boundaries); break;
case OT_RemoteOverrideFunction: print_remoteoverridefunction(OTcurrentSystemState.RemoteOverrideFunction); break;
case OT_OEMDiagnosticCode: print_u16(OTcurrentSystemState.OEMDiagnosticCode); break;
case OT_BurnerStarts: print_u16(OTcurrentSystemState.BurnerStarts); break;
case OT_CHPumpStarts: print_u16(OTcurrentSystemState.CHPumpStarts); break;
case OT_DHWPumpValveStarts: print_u16(OTcurrentSystemState.DHWPumpValveStarts); break;
case OT_DHWBurnerStarts: print_u16(OTcurrentSystemState.DHWBurnerStarts); break;
case OT_BurnerOperationHours: print_u16(OTcurrentSystemState.BurnerOperationHours); break;
case OT_CHPumpOperationHours: print_u16(OTcurrentSystemState.CHPumpOperationHours); break;
case OT_DHWPumpValveOperationHours: print_u16(OTcurrentSystemState.DHWPumpValveOperationHours); break;
case OT_DHWBurnerOperationHours: print_u16(OTcurrentSystemState.DHWBurnerOperationHours); break;
case OT_MasterVersion: print_u8u8(OTcurrentSystemState.MasterVersion ); break;
case OT_SlaveVersion: print_u8u8(OTcurrentSystemState.SlaveVersion); break;
case OT_StatusVH: print_statusVH(OTcurrentSystemState.StatusVH); break;
case OT_ControlSetpointVH: print_u8u8(OTcurrentSystemState.ControlSetpointVH); break;
case OT_ASFFaultCodeVH: print_flag8u8(OTcurrentSystemState.ASFFaultCodeVH); break;
case OT_DiagnosticCodeVH: print_u16(OTcurrentSystemState.DiagnosticCodeVH); break;
case OT_ConfigMemberIDVH: print_vh_configmemberid(OTcurrentSystemState.ConfigMemberIDVH); break;
case OT_OpenthermVersionVH: print_f88(OTcurrentSystemState.OpenthermVersionVH); break;
case OT_VersionTypeVH: print_u8u8(OTcurrentSystemState.VersionTypeVH ); break;
case OT_RelativeVentilation: print_u8u8(OTcurrentSystemState.RelativeVentilation); break;
case OT_RelativeHumidityExhaustAir: print_u8u8(OTcurrentSystemState.RelativeHumidityExhaustAir); break;
case OT_CO2LevelExhaustAir: print_u16(OTcurrentSystemState.CO2LevelExhaustAir); break;
case OT_SupplyInletTemperature: print_f88(OTcurrentSystemState.SupplyInletTemperature); break;
case OT_SupplyOutletTemperature: print_f88(OTcurrentSystemState.SupplyOutletTemperature); break;
case OT_ExhaustInletTemperature: print_f88(OTcurrentSystemState.ExhaustInletTemperature); break;
case OT_ExhaustOutletTemperature: print_f88(OTcurrentSystemState.ExhaustOutletTemperature); break;
case OT_ActualExhaustFanSpeed: print_u16(OTcurrentSystemState.ActualExhaustFanSpeed); break;
case OT_ActualSupplyFanSpeed: print_u16(OTcurrentSystemState.ActualSupplyFanSpeed); break;
case OT_RemoteParameterSettingVH: print_vh_remoteparametersetting(OTcurrentSystemState.RemoteParameterSettingVH); break;
case OT_NominalVentilationValue: print_u8u8(OTcurrentSystemState.NominalVentilationValue); break;
case OT_TSPNumberVH: print_u8u8(OTcurrentSystemState.TSPNumberVH); break;
case OT_TSPEntryVH: print_u8u8(OTcurrentSystemState.TSPEntryVH); break;
case OT_FaultBufferSizeVH: print_u8u8(OTcurrentSystemState.FaultBufferSizeVH); break;
case OT_FaultBufferEntryVH: print_u8u8(OTcurrentSystemState.FaultBufferEntryVH); break;
case OT_FanSpeed: print_u16(OTcurrentSystemState.FanSpeed); break;
case OT_ElectricalCurrentBurnerFlame: print_f88(OTcurrentSystemState.ElectricalCurrentBurnerFlame); break;
case OT_TRoomCH2: print_f88(OTcurrentSystemState.TRoomCH2); break;
case OT_RelativeHumidity: print_u8u8(OTcurrentSystemState.RelativeHumidity); break;
case OT_RFstrengthbatterylevel: print_u8u8(OTcurrentSystemState.RFstrengthbatterylevel); break;
case OT_OperatingMode_HC1_HC2_DHW: print_u8u8(OTcurrentSystemState.OperatingMode_HC1_HC2_DHW ); break;
case OT_ElectricityProducerStarts: print_u16(OTcurrentSystemState.ElectricityProducerStarts); break;
case OT_ElectricityProducerHours: print_u16(OTcurrentSystemState.ElectricityProducerHours); break;
case OT_ElectricityProduction: print_u16(OTcurrentSystemState.ElectricityProduction); break;
case OT_CumulativElectricityProduction: print_u16(OTcurrentSystemState.CumulativElectricityProduction); break;
case OT_RemehadFdUcodes: print_u8u8(OTcurrentSystemState.RemehadFdUcodes); break;
case OT_RemehaServicemessage: print_u8u8(OTcurrentSystemState.RemehaServicemessage); break;
case OT_RemehaDetectionConnectedSCU: print_u8u8(OTcurrentSystemState.RemehaDetectionConnectedSCU); break;
case OT_SolarStorageMaster: print_solar_storage_status(OTcurrentSystemState.SolarStorageStatus ); break;
case OT_SolarStorageASFflags: print_flag8u8(OTcurrentSystemState.SolarStorageASFflags); break;
case OT_SolarStorageSlaveConfigMemberIDcode: print_solarstorage_slavememberid(OTcurrentSystemState.SolarStorageSlaveConfigMemberIDcode); break;
case OT_SolarStorageVersionType: print_u8u8(OTcurrentSystemState.SolarStorageVersionType); break;
case OT_SolarStorageTSP: print_u8u8(OTcurrentSystemState.SolarStorageTSP ); break;
case OT_SolarStorageTSPindexTSPvalue: print_u8u8(OTcurrentSystemState.SolarStorageTSPindexTSPvalue ); break;
case OT_SolarStorageFHBsize: print_u8u8(OTcurrentSystemState.SolarStorageFHBsize ); break;
case OT_SolarStorageFHBindexFHBvalue: print_u8u8(OTcurrentSystemState.SolarStorageFHBindexFHBvalue ); break;
case OT_BurnerUnsuccessfulStarts: print_u16(OTcurrentSystemState.BurnerUnsuccessfulStarts); break;
case OT_FlameSignalTooLow: print_u16(OTcurrentSystemState.FlameSignalTooLow); break;
default:
AddLogf("Unknown message [%02d] value [%04X] f8.8 [%3.2f] u16 [%d] s16 [%d]", OTdata.id, OTdata.value, OTdata.f88(), OTdata.u16(), OTdata.s16());
break;
}
if (OTdata.skipthis) AddLog(" <ignored> ");
AddLogln();
OTGWDebugT(ot_log_buffer);
OTGWDebugFlush();
ClrLog();
}
} else if (buf[2]==':') { //seems to be a response to a command, so check to verify if it was
checkOTGWcmdqueue(buf, len);
} else if (strstr(buf, "\r\nError 01")!= NULL) {
OTcurrentSystemState.error01++;
OTGWDebugTf(PSTR("\r\nError 01 = %d\r\n"),OTcurrentSystemState.error01);
sendMQTTData(F("Error 01"), String(OTcurrentSystemState.error01));
} else if (strstr(buf, "Error 02")!= NULL) {
OTcurrentSystemState.error02++;
OTGWDebugTf(PSTR("\r\nError 02 = %d\r\n"),OTcurrentSystemState.error02);
sendMQTTData(F("Error 02"), String(OTcurrentSystemState.error02));
} else if (strstr(buf, "Error 03")!= NULL) {
OTcurrentSystemState.error03++;
OTGWDebugTf(PSTR("\r\nError 03 = %d\r\n"),OTcurrentSystemState.error03);
sendMQTTData(F("Error 03"), String(OTcurrentSystemState.error03));
} else if (strstr(buf, "Error 04")!= NULL){
OTcurrentSystemState.error04++;
OTGWDebugTf(PSTR("\r\nError 04 = %d\r\n"),OTcurrentSystemState.error04);
sendMQTTData(F("Error 04"), String(OTcurrentSystemState.error04));
} else if (strstr(buf, OTGW_BANNER)!=NULL){
//found a banner, so get the version of PIC
sPICfwversion = String(OTGWSerial.firmwareVersion());
OTGWDebugTf(PSTR("Current firmware version: %s\r\n"), CSTR(sPICfwversion));
sPICdeviceid = OTGWSerial.processorToString();
OTGWDebugTf(PSTR("Current device id: %s\r\n"), CSTR(sPICdeviceid));
sPICtype = OTGWSerial.firmwareToString();
OTGWDebugTf(PSTR("Current firmware type: %s\r\n"), CSTR(sPICtype));
} else {
OTGWDebugTf(PSTR("Not processed, received from OTGW => (%s) [%d]\r\n"), buf, len);
}
}
//====================[ HandleOTGW ]====================
/*
** This is the core of the OTGW firmware.
** This code basically reads from serial, connected to the OTGW hardware, and
** processes each OT message coming. It can also be used to send data into the
** OpenTherm Gateway.
**
** The main purpose is to read each OT Msg (9 bytes), or anything that comes
** from the OTGW hardware firmware. Default it assumes raw OT messages, however
** it can handle the other messages to, like PS=1/PS=0 etc.
**
** Also, this code bit implements the serial 2 network (port 25238). The serial port
** is forwarded to port 25238, and visavera. So you can use it with OTmonitor (the
** original OpenTherm program that comes with the hardware). The serial port and
** ser2net port 25238 are both "line read" into the read buffer (coming from OTGW
** thru serial) and write buffer (coming from 25238 going to serial).
**
** The write buffer (incoming from port 25238) is also line printed to the Debug (port 23).
** The read line buffer is per line parsed by the proces OT parser code (processOT (buf, len)).
*/
void handleOTGW()
{
//handle serial communication and line processing
#define MAX_BUFFER_READ 256 //need to be 256 because of long PS=1 responses
#define MAX_BUFFER_WRITE 128
static char sRead[MAX_BUFFER_READ];
static char sWrite[MAX_BUFFER_WRITE];
static size_t bytes_read = 0;
static size_t bytes_write = 0;
static uint8_t outByte;
//Handle incoming data from OTGW through serial port (READ BUFFER)
if (OTGWSerial.hasOverrun()) {
DebugT(F("Serial Overrun\r\n"));
}
if (OTGWSerial.hasRxError()){
DebugT(F("Serial Rx Error\r\n"));
}
while (OTGWSerial.available()) {
outByte = OTGWSerial.read();
OTGWstream.write(outByte);
if (outByte == '\r' || outByte == '\n') {
if (bytes_read == 0) continue;
blinkLEDnow(LED2);
sRead[bytes_read] = '\0';
processOT(sRead, bytes_read);
bytes_read = 0;
} else if (bytes_read < (MAX_BUFFER_READ-1))
sRead[bytes_read++] = outByte;
}
//handle incoming data from network (port 25238) sent to serial port OTGW (WRITE BUFFER)
while (OTGWstream.available()){
outByte = OTGWstream.read(); // read from port 25238
OTGWSerial.write(outByte); // write to serial port
if (outByte == '\r')
{ //on CR, do something...
sWrite[bytes_write] = 0;
OTGWDebugTf(PSTR("Net2Ser: Sending to OTGW: [%s] (%d)\r\n"), sWrite, bytes_write);
//check for reset command
if (strcmp(sWrite, "GW=R")==0){
//detected [GW=R], then reset the gateway the gpio way
OTGWDebugTln(F("Detected: GW=R. Reset gateway command executed."));
resetOTGW();
} else if (strcasecmp(sWrite, "PS=1")==0) {
//detected [PS=1], then PrintSummary mode = true --> From this point on you need to ask for summary.
bPSmode = true;
//reset all msglastupdated in webui
for(int i = 0; i <= OT_MSGID_MAX; i++){
msglastupdated[i] = 0; //clear epoch values
}
sMessage = "PS=1 mode; No UI updates.";
} else if (strcasecmp(sWrite, "PS=0")==0) {
//detected [PS=0], then PrintSummary mode = OFF --> Raw mode is turned on again.
bPSmode = false;
sMessage = "";
}
bytes_write = 0; //start next line
} else if (outByte == '\n')
{
// on LF, skip
}
else
{
if (bytes_write < (MAX_BUFFER_WRITE-1))
sWrite[bytes_write++] = outByte;
}
}
}// END of handleOTGW
//====================[ functions for REST API ]====================
String getOTGWValue(int msgid)
{
switch (static_cast<OpenThermMessageID>(msgid)) {
case OT_TSet: return String(OTcurrentSystemState.TSet); break;
case OT_CoolingControl: return String(OTcurrentSystemState.CoolingControl); break;
case OT_TsetCH2: return String(OTcurrentSystemState.TsetCH2); break;
case OT_TrOverride: return String(OTcurrentSystemState.TrOverride); break;
case OT_MaxRelModLevelSetting: return String(OTcurrentSystemState.MaxRelModLevelSetting); break;
case OT_TrSet: return String(OTcurrentSystemState.TrSet); break;
case OT_TrSetCH2: return String(OTcurrentSystemState.TrSetCH2); break;
case OT_RelModLevel: return String(OTcurrentSystemState.RelModLevel); break;
case OT_CHPressure: return String(OTcurrentSystemState.CHPressure); break;
case OT_DHWFlowRate: return String(OTcurrentSystemState.DHWFlowRate); break;
case OT_Tr: return String(OTcurrentSystemState.Tr); break;
case OT_Tboiler: return String(OTcurrentSystemState.Tboiler); break;
case OT_Tdhw: return String(OTcurrentSystemState.Tdhw); break;
case OT_Toutside: return String(OTcurrentSystemState.Toutside); break;
case OT_Tret: return String(OTcurrentSystemState.Tret); break;
case OT_Tsolarstorage: return String(OTcurrentSystemState.Tsolarstorage); break;
case OT_Tsolarcollector: return String(OTcurrentSystemState.Tsolarcollector); break;
case OT_TflowCH2: return String(OTcurrentSystemState.TflowCH2); break;
case OT_Tdhw2: return String(OTcurrentSystemState.Tdhw2); break;
case OT_Texhaust: return String(OTcurrentSystemState.Texhaust); break;
case OT_Theatexchanger: return String(OTcurrentSystemState.Theatexchanger); break;
case OT_TdhwSet: return String(OTcurrentSystemState.TdhwSet); break;
case OT_MaxTSet: return String(OTcurrentSystemState.MaxTSet); break;
case OT_Hcratio: return String(OTcurrentSystemState.Hcratio); break;
case OT_Remoteparameter4: return String(OTcurrentSystemState.Remoteparameter4); break;
case OT_Remoteparameter5: return String(OTcurrentSystemState.Remoteparameter5); break;
case OT_Remoteparameter6: return String(OTcurrentSystemState.Remoteparameter6); break;
case OT_Remoteparameter7: return String(OTcurrentSystemState.Remoteparameter7); break;
case OT_Remoteparameter8: return String(OTcurrentSystemState.Remoteparameter8); break;
case OT_OpenThermVersionMaster: return String(OTcurrentSystemState.OpenThermVersionMaster); break;
case OT_OpenThermVersionSlave: return String(OTcurrentSystemState.OpenThermVersionSlave); break;
case OT_Statusflags: return String(OTcurrentSystemState.Statusflags); break;
case OT_ASFflags: return String(OTcurrentSystemState.ASFflags); break;
case OT_MasterConfigMemberIDcode: return String(OTcurrentSystemState.MasterConfigMemberIDcode); break;
case OT_SlaveConfigMemberIDcode: return String(OTcurrentSystemState.SlaveConfigMemberIDcode); break;
case OT_Command: return String(OTcurrentSystemState.Command); break;
case OT_RBPflags: return String(OTcurrentSystemState.RBPflags); break;
case OT_TSP: return String(OTcurrentSystemState.TSP); break;
case OT_TSPindexTSPvalue: return String(OTcurrentSystemState.TSPindexTSPvalue); break;
case OT_FHBsize: return String(OTcurrentSystemState.FHBsize); break;
case OT_FHBindexFHBvalue: return String(OTcurrentSystemState.FHBindexFHBvalue); break;
case OT_MaxCapacityMinModLevel: return String(OTcurrentSystemState.MaxCapacityMinModLevel); break;
case OT_DayTime: return String(OTcurrentSystemState.DayTime); break;
case OT_Date: return String(OTcurrentSystemState.Date); break;
case OT_Year: return String(OTcurrentSystemState.Year); break;
case OT_TdhwSetUBTdhwSetLB: return String(OTcurrentSystemState.TdhwSetUBTdhwSetLB); break;
case OT_MaxTSetUBMaxTSetLB: return String(OTcurrentSystemState.MaxTSetUBMaxTSetLB); break;
case OT_HcratioUBHcratioLB: return String(OTcurrentSystemState.HcratioUBHcratioLB); break;
case OT_Remoteparameter4boundaries: return String(OTcurrentSystemState.Remoteparameter4boundaries); break;
case OT_Remoteparameter5boundaries: return String(OTcurrentSystemState.Remoteparameter5boundaries); break;
case OT_Remoteparameter6boundaries: return String(OTcurrentSystemState.Remoteparameter6boundaries); break;
case OT_Remoteparameter7boundaries: return String(OTcurrentSystemState.Remoteparameter7boundaries); break;
case OT_Remoteparameter8boundaries: return String(OTcurrentSystemState.Remoteparameter8boundaries); break;
case OT_RemoteOverrideFunction: return String(OTcurrentSystemState.RemoteOverrideFunction); break;
case OT_OEMDiagnosticCode: return String(OTcurrentSystemState.OEMDiagnosticCode); break;
case OT_BurnerStarts: return String(OTcurrentSystemState.BurnerStarts); break;
case OT_CHPumpStarts: return String(OTcurrentSystemState.CHPumpStarts); break;
case OT_DHWPumpValveStarts: return String(OTcurrentSystemState.DHWPumpValveStarts); break;
case OT_DHWBurnerStarts: return String(OTcurrentSystemState.DHWBurnerStarts); break;
case OT_BurnerOperationHours: return String(OTcurrentSystemState.BurnerOperationHours); break;
case OT_CHPumpOperationHours: return String(OTcurrentSystemState.CHPumpOperationHours); break;
case OT_DHWPumpValveOperationHours: return String(OTcurrentSystemState.DHWPumpValveOperationHours); break;
case OT_DHWBurnerOperationHours: return String(OTcurrentSystemState.DHWBurnerOperationHours); break;
case OT_MasterVersion: return String(OTcurrentSystemState.MasterVersion); break;
case OT_SlaveVersion: return String(OTcurrentSystemState.SlaveVersion); break;
case OT_StatusVH: return String(OTcurrentSystemState.StatusVH); break;
case OT_ControlSetpointVH: return String(OTcurrentSystemState.ControlSetpointVH); break;
case OT_ASFFaultCodeVH: return String(OTcurrentSystemState.ASFFaultCodeVH); break;
case OT_DiagnosticCodeVH: return String(OTcurrentSystemState.DiagnosticCodeVH); break;
case OT_ConfigMemberIDVH: return String(OTcurrentSystemState.ConfigMemberIDVH); break;
case OT_OpenthermVersionVH: return String(OTcurrentSystemState.OpenthermVersionVH); break;
case OT_VersionTypeVH: return String(OTcurrentSystemState.VersionTypeVH); break;
case OT_RelativeVentilation: return String(OTcurrentSystemState.RelativeVentilation); break;
case OT_RelativeHumidityExhaustAir: return String(OTcurrentSystemState.RelativeHumidityExhaustAir); break;
case OT_CO2LevelExhaustAir: return String(OTcurrentSystemState.CO2LevelExhaustAir); break;
case OT_SupplyInletTemperature: return String(OTcurrentSystemState.SupplyInletTemperature); break;
case OT_SupplyOutletTemperature: return String(OTcurrentSystemState.SupplyOutletTemperature); break;
case OT_ExhaustInletTemperature: return String(OTcurrentSystemState.ExhaustInletTemperature); break;
case OT_ExhaustOutletTemperature: return String(OTcurrentSystemState.ExhaustOutletTemperature); break;
case OT_ActualExhaustFanSpeed: return String(OTcurrentSystemState.ActualExhaustFanSpeed); break;
case OT_ActualSupplyFanSpeed: return String(OTcurrentSystemState.ActualSupplyFanSpeed); break;
case OT_RemoteParameterSettingVH: return String(OTcurrentSystemState.RemoteParameterSettingVH); break;
case OT_NominalVentilationValue: return String(OTcurrentSystemState.NominalVentilationValue); break;
case OT_TSPNumberVH: return String(OTcurrentSystemState.TSPNumberVH); break;
case OT_TSPEntryVH: return String(OTcurrentSystemState.TSPEntryVH); break;
case OT_FaultBufferSizeVH: return String(OTcurrentSystemState.FaultBufferSizeVH); break;
case OT_FaultBufferEntryVH: return String(OTcurrentSystemState.FaultBufferEntryVH); break;
case OT_FanSpeed: return String(OTcurrentSystemState.FanSpeed); break;
case OT_ElectricalCurrentBurnerFlame: return String(OTcurrentSystemState.ElectricalCurrentBurnerFlame); break;
case OT_TRoomCH2: return String(OTcurrentSystemState.TRoomCH2); break;
case OT_RelativeHumidity: return String(OTcurrentSystemState.RelativeHumidity); break;
case OT_RFstrengthbatterylevel: return String(OTcurrentSystemState.RFstrengthbatterylevel); break;
case OT_OperatingMode_HC1_HC2_DHW: return String(OTcurrentSystemState.OperatingMode_HC1_HC2_DHW); break;
case OT_ElectricityProducerStarts: return String(OTcurrentSystemState.ElectricityProducerStarts); break;
case OT_ElectricityProducerHours: return String(OTcurrentSystemState.ElectricityProducerHours); break;
case OT_ElectricityProduction: return String(OTcurrentSystemState.ElectricityProduction); break;
case OT_CumulativElectricityProduction: return String(OTcurrentSystemState.CumulativElectricityProduction); break;
case OT_RemehadFdUcodes: return String(OTcurrentSystemState.RemehadFdUcodes); break;
case OT_RemehaServicemessage: return String(OTcurrentSystemState.RemehaServicemessage); break;
case OT_RemehaDetectionConnectedSCU: return String(OTcurrentSystemState.RemehaDetectionConnectedSCU); break;
case OT_SolarStorageMaster: return String(OTcurrentSystemState.SolarStorageStatus); break;
case OT_SolarStorageASFflags: return String(OTcurrentSystemState.SolarStorageASFflags); break;
case OT_SolarStorageSlaveConfigMemberIDcode: return String(OTcurrentSystemState.SolarStorageSlaveConfigMemberIDcode); break;
case OT_SolarStorageVersionType: return String(OTcurrentSystemState.SolarStorageVersionType); break;
case OT_SolarStorageTSP: return String(OTcurrentSystemState.SolarStorageTSP); break;
case OT_SolarStorageTSPindexTSPvalue: return String(OTcurrentSystemState.SolarStorageTSPindexTSPvalue); break;
case OT_SolarStorageFHBsize: return String(OTcurrentSystemState.SolarStorageFHBsize); break;
case OT_SolarStorageFHBindexFHBvalue: return String(OTcurrentSystemState.SolarStorageFHBindexFHBvalue); break;
default: return "Error: not implemented yet!\r\n";
}
}
void startOTGWstream()
{
OTGWstream.begin();
}
//---------[ Upgrade PIC stuff taken from Schelte Bron's NodeMCU Firmware ]---------
void upgradepicnow(const char *filename) {
if (OTGWSerial.busy()) return; // if already in programming mode, never call it twice
DebugTf(PSTR("Start PIC upgrade now: %s\r\n"), filename);
fwupgradestart(filename);
while (OTGWSerial.busy()){
feedWatchDog();
//blink the led during flash...
DECLARE_TIMER_MS(timerUpgrade, 500);
if (DUE(timerUpgrade)) {
blinkLEDnow(LED2);
}
}
// When you are done, then reset the PIC one more time, to capture the actual fwversion of the OTGW
resetOTGW();
}
void fwupgradedone(OTGWError result, short errors = 0, short retries = 0) {
switch (result) {
case OTGWError::OTGW_ERROR_NONE: errorupgrade = F("PIC upgrade was succesful"); break;
case OTGWError::OTGW_ERROR_MEMORY: errorupgrade = F("Not enough memory available"); break;
case OTGWError::OTGW_ERROR_INPROG: errorupgrade = F("Firmware upgrade in progress"); break;
case OTGWError::OTGW_ERROR_HEX_ACCESS: errorupgrade = F("Could not open hex file"); break;
case OTGWError::OTGW_ERROR_HEX_FORMAT: errorupgrade = F("Invalid format of hex file"); break;
case OTGWError::OTGW_ERROR_HEX_DATASIZE: errorupgrade = F("Wrong data size in hex file"); break;
case OTGWError::OTGW_ERROR_HEX_CHECKSUM: errorupgrade = F("Bad checksum in hex file"); break;
case OTGWError::OTGW_ERROR_MAGIC: errorupgrade = F("Hex file does not contain expected data"); break;
case OTGWError::OTGW_ERROR_RESET: errorupgrade = F("PIC reset failed"); break;
case OTGWError::OTGW_ERROR_RETRIES: errorupgrade = F("Too many retries"); break;
case OTGWError::OTGW_ERROR_MISMATCHES: errorupgrade = F("Too many mismatches"); break;
case OTGWError::OTGW_ERROR_DEVICE: errorupgrade = F("Wrong PIC (16F88 <=> 16F1847)"); break;
default: errorupgrade = F("Unknown state"); break;
}
OTGWDebugTf(PSTR("Upgrade finished: Errorcode = %d - %s - %d retries, %d errors\r\n"), result, CSTR(errorupgrade), retries, errors);
}
void fwupgradestep(int pct) {
OTGWDebugTf(PSTR("Upgrade: %d%%\n\r"), pct);
}
void fwreportinfo(OTGWFirmware fw, const char *version) {
DebugTln(F("Callback: fwreportinfo"));
sPICfwversion = String(version);
//sPICfwversion = String(OTGWSerial.firmwareVersion());
DebugTf(PSTR("Current firmware version: %s\r\n"), CSTR(sPICfwversion));
sPICdeviceid = OTGWSerial.processorToString();
DebugTf(PSTR("Current device id: %s\r\n"), CSTR(sPICdeviceid));
//instead of using the firmware string
sPICtype = OTGWSerial.firmwareToString(fw);
OTGWDebugTf(PSTR("Current firmware type: %s\r\n"), CSTR(sPICtype));
}
void fwupgradestart(const char *hexfile) {
DebugTf(PSTR("Start PIC upgrade with hexfile: %s\n\r"), hexfile);
OTGWError result;
digitalWrite(LED1, LOW);
result = OTGWSerial.startUpgrade(hexfile);
if (result!= OTGWError::OTGW_ERROR_NONE) {
fwupgradedone(result);
} else {
OTGWSerial.registerFinishedCallback(fwupgradedone);
OTGWSerial.registerProgressCallback(fwupgradestep);
}
}
String checkforupdatepic(String filename){
WiFiClient client;
HTTPClient http;
String latest = "";
int code;
http.begin(client, "http://otgw.tclcode.com/download/" + sPICdeviceid + "/" + filename);
char useragent[40] = "esp8266-otgw-firmware/";
strlcat(useragent, _SEMVER_CORE, sizeof(useragent));
http.setUserAgent(useragent);
http.collectHeaders(hexheaders, 2);
code = http.sendRequest("HEAD");
if (code == HTTP_CODE_OK) {
for (int i = 0; i< http.headers(); i++) {
DebugTf(PSTR("%s: %s\r\n"), hexheaders[i], http.header(i).c_str());
}
latest = http.header(1);
DebugTf(PSTR("Update %s -> [%s]\r\n"), filename.c_str(), latest.c_str());
http.end();
} else OTGWDebugln(F("Failed to fetch version from Schelte Bron website"));
return latest;
}
void refreshpic(String filename, String version) {
if (sPICdeviceid=="unknown") return; // no pic version found, don't upgrade
WiFiClient client;
HTTPClient http;
String latest;
int code;
latest = checkforupdatepic(filename);
if (latest != version) {
OTGWDebugTf(PSTR("Update (%s)%s: %s -> %s\r\n"), sPICdeviceid.c_str(), filename.c_str(), version.c_str(), latest.c_str());
http.begin(client, "http://otgw.tclcode.com/download/" + sPICdeviceid + "/" + filename);
char useragent[40] = "esp8266-otgw-firmware/";
strlcat(useragent, _SEMVER_CORE, sizeof(useragent));
http.setUserAgent(useragent);
code = http.GET();
if (code == HTTP_CODE_OK) {
File f = LittleFS.open("/" + sPICdeviceid + "/" + filename, "w");
if (f) {
http.writeToStream(&f);
f.close();
String verfile = "/" + sPICdeviceid + "/" + filename;
verfile.replace(".hex", ".ver");
f = LittleFS.open(verfile, "w");
if (f) {
f.print(latest + "\n");
f.close();
OTGWDebugTln(F("Update successful"));
}
}
}
}
http.end();
}
void upgradepic() {
String action = httpServer.arg("action");
String filename = httpServer.arg("name");
String version = httpServer.arg("version");
DebugTf(PSTR("Action: %s %s %s\r\n"), action.c_str(), filename.c_str(), version.c_str());
if (sPICdeviceid=="unknown") {
DebugTln(F("No PIC device id is unknown, don't upgrade"));
return; // no pic version found, don't upgrade
}
if (action == "upgrade") {
DebugTf(PSTR("Upgrade /%s/%s\r\n"), sPICdeviceid.c_str(), filename.c_str());
upgradepicnow(String(filename).c_str());
} else if (action == "refresh") {
DebugTf(PSTR("Refresh %s/%s\r\n"), sPICdeviceid.c_str(), filename.c_str());
refreshpic(filename, version);
} else if (action == "delete") {
DebugTf(PSTR("Delete %s/%s\r\n"), sPICdeviceid.c_str(), filename.c_str());
String path = "/" + sPICdeviceid + "/" + filename;
LittleFS.remove(path);
path.replace(".hex", ".ver");
LittleFS.remove(path);
}
httpServer.sendHeader("Location", "index.html#tabPICflash", true);
httpServer.send(303, "text/html", "<a href='index.html#tabPICflash'>Return</a>");
}
/***************************************************************************
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
* THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
***************************************************************************/