1
mirror of https://github.com/rvdbreemen/OTGW-firmware synced 2024-11-16 04:33:49 +01:00
OTGW-firmware/OTGW-Core.ino

1288 lines
57 KiB
Arduino
Raw Normal View History

/*
***************************************************************************
2021-01-30 15:24:54 +01:00
** Program : OTGW-Core.ino
** Version : v0.8.0
**
** Copyright (c) 2021 Robert van den Breemen
** Borrowed from OpenTherm library from:
** https://github.com/jpraus/arduino-opentherm
**
** TERMS OF USE: MIT License. See bottom of file.
***************************************************************************
*/
2021-01-30 15:24:54 +01:00
//define Nodoshop OTGW hardware
#define OTGW_BUTTON 0 //D3
#define OTGW_RESET 14 //D5
#define OTGW_LED1 2 //D4
#define OTGW_LED2 16 //D0
2021-01-30 15:24:54 +01:00
//external watchdog
2021-02-02 00:22:46 +01:00
#define EXT_WD_I2C_ADDRESS 0x26
#define PIN_I2C_SDA 4 //D2
#define PIN_I2C_SCL 5 //D1
2021-01-30 15:24:54 +01:00
2021-02-14 14:31:21 +01:00
//used by update firmware functions
const char *hexheaders[] = {
"Last-Modified",
"X-Version"
};
2021-01-30 15:24:54 +01:00
//Macro to Feed the Watchdog
2021-02-02 00:22:46 +01:00
#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')
2021-01-30 15:24:54 +01:00
#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 --- */
2020-11-03 01:28:05 +01:00
//some variable's
2020-11-02 08:09:26 +01:00
OpenthermData OTdata;
2021-02-08 01:46:11 +01:00
#define OTGW_BANNER "OpenTherm Gateway"
2021-01-18 21:53:53 +01:00
//===================[ Reset OTGW ]===============================
void resetOTGW() {
2021-02-08 23:44:19 +01:00
OTGWSerial.resetPic();
2021-02-09 22:01:16 +01:00
//then read the first response of the firmware to make sure it reads it
2021-02-08 23:44:19 +01:00
String resp = OTGWSerial.readStringUntil('\n');
2021-03-07 00:55:53 +01:00
resp.trim();
2021-02-08 23:44:19 +01:00
DebugTf("Received firmware version: [%s] [%s] (%d)\r\n", CSTR(resp), OTGWSerial.firmwareVersion(), strlen(OTGWSerial.firmwareVersion()));
bOTGWonline = (strlen(OTGWSerial.firmwareVersion())>0);
if (bOTGWonline){
2021-02-08 23:44:19 +01:00
if (resp.length()>0) {
sPICfwversion = String(OTGWSerial.firmwareVersion());
} else sPICfwversion ="No version found";
2021-02-09 23:29:39 +01:00
} else sPICfwversion = "No OTGW connected!";
DebugTf("Current firmware version: %s\r\n", CSTR(sPICfwversion));
2021-01-18 21:53:53 +01:00
}
2021-01-31 23:43:52 +01:00
//===================[ getpicfwversion ]===========================
String getpicfwversion(){
String _ret="";
2021-02-01 00:02:46 +01:00
String line = executeCommand("PR=A");
int p = line.indexOf(OTGW_BANNER);
2021-01-31 23:43:52 +01:00
if (p >= 0) {
p += sizeof(OTGW_BANNER);
2021-01-31 23:43:52 +01:00
_ret = line.substring(p);
2021-02-02 21:21:46 +01:00
} else _ret ="No version found";
DebugTf("Current firmware version: %s\r\n", CSTR(_ret));
2021-01-31 23:43:52 +01:00
_ret.trim();
return _ret;
}
2021-03-06 10:52:51 +01:00
//===================[ checkOTWGpicforupdate ]=====================
void checkOTWGpicforupdate(){
DebugTf("OTGW PIC firmware version = [%s]\r\n", CSTR(sPICfwversion));
String latest = checkforupdatepic("gateway.hex");
if (!bOTGWonline) {
sMessage = sPICfwversion;
} else if (latest != sPICfwversion) {
sMessage = "New PIC version " + latest + " available!";
}
}
2021-01-31 23:43:52 +01:00
//===================[ OTGW Command & Response ]===================
2021-01-31 23:44:58 +01:00
String executeCommand(const String sCmd){
2021-02-01 23:03:07 +01:00
//send command to OTGW
2021-02-02 00:56:38 +01:00
DebugTf("OTGW Send Cmd [%s]\r\n", CSTR(sCmd));
OTGWSerial.setTimeout(1000);
DECLARE_TIMER_MS(tmrWaitForIt, 1000);
while((OTGWSerial.availableForWrite() < sCmd.length()+2) && !DUE(tmrWaitForIt)){
2021-02-01 23:03:07 +01:00
feedWatchDog();
}
OTGWSerial.write(CSTR(sCmd));
OTGWSerial.write("\r\n");
OTGWSerial.flush();
2021-02-01 23:03:07 +01:00
//wait for response
2021-02-02 11:32:49 +01:00
RESTART_TIMER(tmrWaitForIt);
while(!OTGWSerial.available() && !DUE(tmrWaitForIt)) {
2021-02-01 23:03:07 +01:00
feedWatchDog();
}
2021-02-02 21:21:46 +01:00
String _cmd = sCmd.substring(0,2);
2021-02-01 23:03:07 +01:00
DebugTf("Send command: [%s]\r\n", CSTR(_cmd));
//fetch a line
String line = OTGWSerial.readStringUntil('\n');
line.trim();
2021-02-01 23:03:07 +01:00
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.";
2021-02-02 21:21:46 +01:00
} else if (line.length()==0) {
//just an empty line... most likely it's a timeout situation
_ret = "TO - Timeout. No response.";
2021-02-01 23:03:07 +01:00
} else {
2021-02-02 22:50:25 +01:00
_ret = line; //some commands return a string, just return that.
2021-02-01 23:03:07 +01:00
}
2021-02-02 21:21:46 +01:00
DebugTf("Command send [%s]-[%s] - Response line: [%s] - Returned value: [%s]\r\n", CSTR(sCmd), CSTR(_cmd), CSTR(line), CSTR(_ret));
2021-02-01 23:03:07 +01:00
return _ret;
}
2021-01-31 23:43:52 +01:00
//===================[ OTGW PS=1 Command ]===============================
void getOTGW_PS_1(){
DebugTln("PS=1");
OTGWSerial.write("PS=1\r\n");
OTGWSerial.flush();
2021-01-31 23:43:52 +01:00
while(!OTGWSerial.available()) {
2021-01-31 23:43:52 +01:00
feedWatchDog();
}
String line = OTGWSerial.readStringUntil('\n');
2021-01-31 23:43:52 +01:00
line.trim(); //remove LF and CR (and whitespaces)
DebugTln(line);
DebugTln("PS=0");
OTGWSerial.write("PS=0\r\n");
OTGWSerial.flush();
2021-01-31 23:43:52 +01:00
}
//===================[ OTGW PS=1 Command ]===============================
//===================[ Watchdog OTGW ]===============================
String initWatchDog() {
2021-01-30 15:24:54 +01:00
// 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.
2021-02-02 00:22:46 +01:00
2021-01-30 15:24:54 +01:00
// configure hardware pins according to eeprom settings.
2021-02-19 01:48:31 +01:00
DebugTln("Setup Watchdog");
2021-02-02 00:22:46 +01:00
DebugTln(F("INIT : I2C"));
Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL); //configure the I2C bus
//=============================================
// I2C Watchdog boot status check
String ReasonReset = "";
delay(500);
2021-02-02 00:22:46 +01:00
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();
2021-02-02 00:22:46 +01:00
Wire.requestFrom((uint8_t)EXT_WD_I2C_ADDRESS, (uint8_t)1);
if (Wire.available())
{
byte status = Wire.read();
if (status & 0x1)
{
DebugTln(F("INIT : Reset by WD!"));
ReasonReset = "Reset by External WD";
//lastReset = BOOT_CAUSE_EXT_WD;
}
}
return ReasonReset;
2021-02-02 00:22:46 +01:00
//===========================================
}
2021-02-02 00:22:46 +01:00
//===[ 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
2021-02-02 00:22:46 +01:00
DECLARE_TIMER_MS(timerWD, 1000, CATCH_UP_MISSED_TICKS);
2020-11-02 08:09:26 +01:00
if DUE(timerWD)
{
2021-02-02 00:22:46 +01:00
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...
2021-03-04 22:54:22 +01:00
if (settingLEDblink) blinkLEDnow(LED1);
2020-11-02 08:09:26 +01:00
}
yield();
//==== feed the WD over I2C ====
}
2020-11-09 00:05:06 +01:00
//===================[ END Watchdog OTGW ]===============================
//=======================================================================
float OpenthermData::f88() {
float value = (int8_t) valueHB;
return value + (float)valueLB / 256.0;
}
void OpenthermData::f88(float value) {
if (value >= 0) {
valueHB = (byte) value;
float fraction = (value - valueHB);
valueLB = fraction * 256.0;
}
else {
valueHB = (byte)(value - 1);
float fraction = (value - valueHB - 1);
valueLB = fraction * 256.0;
}
}
uint16_t OpenthermData::u16() {
uint16_t value = valueHB;
return (value << 8) | valueLB;
}
void OpenthermData::u16(uint16_t value) {
valueLB = value & 0xFF;
valueHB = (value >> 8) & 0xFF;
}
int16_t OpenthermData::s16() {
int16_t value = valueHB;
return (value << 8) | valueLB;
}
void OpenthermData::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";
2020-10-26 00:00:06 +01:00
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) {
2020-10-26 00:00:06 +01:00
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) {
2020-12-12 00:20:31 +01:00
return OTmap[message_id].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);
}
2021-01-03 23:12:19 +01:00
//parsing responses - helper functions
// 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 OTdataObject.Status & 0x0100;
}
bool isDomesticHotWaterEnabled() {
return OTdataObject.Status & 0x0200;
}
bool isCoolingEnabled() {
return OTdataObject.Status & 0x0400;
}
bool isOutsideTemperatureCompensationActive() {
return OTdataObject.Status & 0x0800;
}
bool isCentralHeating2enabled() {
return OTdataObject.Status & 0x1000;
}
2021-01-03 23:12:19 +01:00
//Slave
// 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 OTdataObject.Status & 0x001;
}
2021-01-03 23:12:19 +01:00
bool isCentralHeatingActive() {
return OTdataObject.Status & 0x002;
}
2021-01-03 23:12:19 +01:00
bool isDomesticHotWaterActive() {
return OTdataObject.Status & 0x004;
}
2021-01-03 23:12:19 +01:00
bool isFlameStatus() {
return OTdataObject.Status & 0x008;
}
2021-01-03 23:12:19 +01:00
bool isCoolingActive() {
return OTdataObject.Status & 0x0010;
}
2021-02-10 00:56:33 +01:00
bool isCentralHeating2Active() {
return OTdataObject.Status & 0x0020;
}
2021-01-03 23:12:19 +01:00
bool isDiagnosticIndicator() {
return OTdataObject.Status & 0x0040;
}
2021-02-10 00:56:33 +01:00
//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 OTdataObject.ASFflags & 0x0100;
2021-02-10 00:56:33 +01:00
}
bool isLockoutReset() {
return OTdataObject.ASFflags & 0x0200;
2021-02-10 00:56:33 +01:00
}
bool isLowWaterPressure() {
return OTdataObject.ASFflags & 0x0400;
2021-02-10 00:56:33 +01:00
}
bool isGasFlameFault() {
return OTdataObject.ASFflags & 0x0800;
2021-02-10 00:56:33 +01:00
}
bool isAirTemperature() {
return OTdataObject.ASFflags & 0x1000;
2021-02-10 00:56:33 +01:00
}
bool isWaterOverTemperature() {
return OTdataObject.ASFflags & 0x2000;
2021-02-10 00:56:33 +01:00
}
2020-11-06 21:13:09 +01:00
const char *byte_to_binary(int x)
{
static char b[9];
b[0] = '\0';
2020-11-06 21:13:09 +01:00
int z;
for (z = 128; z > 0; z >>= 1) {
strcat(b, ((x & z) == z) ? "1" : "0");
}
2020-11-06 21:13:09 +01:00
return b;
}
2021-03-09 00:32:27 +01:00
void print_f88(float *value)
{
//function to print data
2020-12-12 00:20:31 +01:00
float _value = round(OTdata.f88()*100.0) / 100.0; // round float 2 digits, like this: x.xx
// Debugf("%-37s = %3.2f %s\r\n", OTmap[OTdata.id].label, _value , OTmap[OTdata.id].unit);
2020-11-03 01:28:05 +01:00
char _msg[15] {0};
2020-12-12 00:20:31 +01:00
dtostrf(_value, 3, 2, _msg);
Debugf("%-37s = %s %s\r\n", OTmap[OTdata.id].label, _msg , OTmap[OTdata.id].unit);
//SendMQTT
2021-03-09 00:32:27 +01:00
if ((*value)!=_value) sendMQTTData(messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), _msg);
(*value) = _value;
}
2021-03-09 00:32:27 +01:00
void print_s16(int16_t *value)
2020-12-12 00:20:31 +01:00
{
int16_t _value = OTdata.s16();
// Debugf("%-37s = %5d %s\r\n", OTmap[OTdata.id].label, _value, OTmap[OTdata.id].unit);
2020-11-03 01:28:05 +01:00
//Build string for MQTT
char _msg[15] {0};
2020-12-12 00:20:31 +01:00
itoa(_value, _msg, 10);
Debugf("%-37s = %s %s\r\n", OTmap[OTdata.id].label, _msg, OTmap[OTdata.id].unit);
//SendMQTT
2021-03-09 00:32:27 +01:00
if ((*value)!=_value) sendMQTTData(messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), _msg);
(*value) = _value;
}
2021-03-09 00:32:27 +01:00
void print_s8s8(uint16_t *value)
{
2021-03-09 00:32:27 +01:00
uint16_t _value = OTdata.u16();
2020-12-12 00:20:31 +01:00
Debugf("%-37s = %3d / %3d %s\r\n", OTmap[OTdata.id].label, (int8_t)OTdata.valueHB, (int8_t)OTdata.valueLB, OTmap[OTdata.id].unit);
2020-11-03 01:28:05 +01:00
//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));
2020-11-06 21:58:29 +01:00
strlcat(_topic, "_value_hb", sizeof(_topic));
2020-12-12 00:20:31 +01:00
Debugf("%-37s = %s %s\r\n", OTmap[OTdata.id].label, _msg, OTmap[OTdata.id].unit);
2021-03-09 00:32:27 +01:00
if (((*value)>>8 & 0xF0)!=OTdata.valueLB) sendMQTTData(_topic, _msg);
2020-11-03 01:28:05 +01:00
//Build string for MQTT
itoa((int8_t)OTdata.valueLB, _msg, 10);
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
2020-11-06 21:58:29 +01:00
strlcat(_topic, "_value_lb", sizeof(_topic));
2020-12-12 00:20:31 +01:00
Debugf("%-37s = %s %s\r\n", OTmap[OTdata.id].label, _msg, OTmap[OTdata.id].unit);
2021-03-09 00:32:27 +01:00
if (((*value) & 0x0F)!=OTdata.valueLB) sendMQTTData(_topic, _msg);
(*value)=_value;
}
2021-03-09 00:32:27 +01:00
void print_u16(uint16_t *value)
2020-12-12 00:20:31 +01:00
{
uint16_t _value = OTdata.u16();
2020-11-03 01:28:05 +01:00
//Build string for MQTT
char _msg[15] {0};
2020-12-12 00:20:31 +01:00
utoa(_value, _msg, 10);
Debugf("%-37s = %s %s\r\n", OTmap[OTdata.id].label, _msg, OTmap[OTdata.id].unit);
//SendMQTT
2021-03-09 00:32:27 +01:00
if ((*value)!= _value) sendMQTTData(messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), _msg);
(*value) = _value;
}
2021-03-09 00:32:27 +01:00
void print_status(uint16_t *value)
{
2021-03-09 00:32:27 +01:00
uint16_t _value=OTdata.u16();
2020-11-06 21:13:09 +01:00
char _flag8_master[8] {0};
char _flag8_slave[8] {0};
//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: reserved
// 6: reserved
// 7: reserved
2020-11-03 01:28:05 +01:00
_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) ? '.' : '-');
_flag8_master[6] = (((OTdata.valueHB) & 0x40) ? '.' : '-');
_flag8_master[7] = (((OTdata.valueHB) & 0x80) ? '.' : '-');
2020-11-06 21:13:09 +01:00
_flag8_master[8] = '\0';
2020-12-12 00:20:31 +01:00
Debugf("%-37s = M[%s] \r\n", OTmap[OTdata.id].label, _flag8_master);
2020-11-06 21:13:09 +01:00
//Master Status
2021-03-09 00:32:27 +01:00
if (((*value)>>8)!=OTdata.valueHB) sendMQTTData("status_master", _flag8_master);
2020-11-06 21:13:09 +01:00
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"));
2020-11-06 21:13:09 +01:00
//Slave
// 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
2020-11-03 01:28:05 +01:00
_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) ? '.' : '-');
2020-11-06 21:13:09 +01:00
_flag8_slave[8] = '\0';
2020-11-03 01:28:05 +01:00
2020-12-12 00:20:31 +01:00
DebugTf("%-37s = S[%s] \r\n", OTmap[OTdata.id].label, _flag8_slave);
2020-11-03 01:28:05 +01:00
2020-11-06 21:58:29 +01:00
//Slave Status
2021-03-09 00:32:27 +01:00
if (((*value) & 0x0F)!=OTdata.valueLB) sendMQTTData("status_slave", _flag8_slave);
2020-11-03 01:28:05 +01:00
sendMQTTData("fault", (((OTdata.valueLB) & 0x01) ? "ON" : "OFF"));
sendMQTTData("centralheating", (((OTdata.valueLB) & 0x02) ? "ON" : "OFF"));
sendMQTTData("domestichotwater", (((OTdata.valueLB) & 0x04) ? "ON" : "OFF"));
sendMQTTData("flame", (((OTdata.valueLB) & 0x08) ? "ON" : "OFF"));
sendMQTTData("cooling", (((OTdata.valueLB) & 0x10) ? "ON" : "OFF"));
sendMQTTData("centralheating2", (((OTdata.valueLB) & 0x20) ? "ON" : "OFF"));
sendMQTTData("diagnostic_indicator", (((OTdata.valueLB) & 0x40) ? "ON" : "OFF"));
2021-03-09 00:32:27 +01:00
(*value)=_value;
}
2021-03-09 00:32:27 +01:00
void print_ASFflags(uint16_t *value)
{
2021-03-09 00:32:27 +01:00
uint16_t _value=OTdata.u16();
2020-12-12 00:20:31 +01:00
Debugf("%-37s = M[%s] OEM fault code [%3d]\r\n", OTmap[OTdata.id].label, byte_to_binary(OTdata.valueHB), OTdata.valueLB);
2020-11-06 21:13:09 +01:00
//Build string for MQTT
2020-11-06 21:58:29 +01:00
char _msg[15] {0};
2020-11-06 21:13:09 +01:00
//Application Specific Fault
2020-11-06 21:58:29 +01:00
sendMQTTData("ASF_flags", byte_to_binary(OTdata.valueHB));
2020-11-06 21:13:09 +01:00
//OEM fault code
2021-03-07 00:44:28 +01:00
utoa(OTdata.valueLB, _msg, 10);
2020-11-06 21:58:29 +01:00
sendMQTTData("ASF_oemfaultcode", _msg);
2020-11-06 21:13:09 +01:00
//bit: [clear/0, set/1]
2020-11-06 21:13:09 +01:00
//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
2020-11-06 21:13:09 +01:00
sendMQTTData("service_request", (((OTdata.valueHB) & 0x01) ? "ON" : "OFF"));
sendMQTTData("lockout_reset", (((OTdata.valueHB) & 0x02) ? "ON" : "OFF"));
sendMQTTData("low_water_pressure", (((OTdata.valueHB) & 0x04) ? "ON" : "OFF"));
sendMQTTData("gas_flame_fault", (((OTdata.valueHB) & 0x08) ? "ON" : "OFF"));
sendMQTTData("air_pressure_fault", (((OTdata.valueHB) & 0x10) ? "ON" : "OFF"));
sendMQTTData("water_over-temperature",(((OTdata.valueHB) & 0x20) ? "ON" : "OFF"));
2021-03-09 00:32:27 +01:00
(*value)=_value;
2020-11-06 21:13:09 +01:00
}
2021-03-09 00:32:27 +01:00
void print_slavememberid(uint16_t *value)
2020-11-06 21:13:09 +01:00
{
2021-03-09 00:32:27 +01:00
uint16_t _value=OTdata.u16();
2020-12-12 00:20:31 +01:00
Debugf("%-37s = Slave Config[%s] MemberID code [%3d]\r\n", OTmap[OTdata.id].label, byte_to_binary(OTdata.valueHB), OTdata.valueLB);
2020-11-06 21:58:29 +01:00
//Build string for SendMQTT
2021-03-09 00:32:27 +01:00
if (((*value) >> 8)!=OTdata.valueHB) sendMQTTData("slave_configuration", byte_to_binary(OTdata.valueHB));
2020-11-06 21:13:09 +01:00
char _msg[15] {0};
utoa(OTdata.valueLB, _msg, 10);
2021-03-09 00:32:27 +01:00
if ((*value & 0x0F)!=OTdata.valueLB) sendMQTTData("slave_memberid_code", _msg);
2020-11-06 21:13:09 +01:00
// 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: reserved
// 7: reserved
sendMQTTData("dhw_present", (((OTdata.valueHB) & 0x01) ? "ON" : "OFF"));
sendMQTTData("control_type", (((OTdata.valueHB) & 0x02) ? "ON" : "OFF"));
sendMQTTData("cooling_config", (((OTdata.valueHB) & 0x04) ? "ON" : "OFF"));
sendMQTTData("dhw_config", (((OTdata.valueHB) & 0x08) ? "ON" : "OFF"));
2021-03-08 02:38:03 +01:00
sendMQTTData("master_low_off_pump_control_function", (((OTdata.valueHB) & 0x10) ? "ON" : "OFF"));
2020-11-06 21:13:09 +01:00
sendMQTTData("ch2_present", (((OTdata.valueHB) & 0x20) ? "ON" : "OFF"));
2021-03-09 00:32:27 +01:00
(*value)=_value;
2020-11-06 21:13:09 +01:00
}
2021-03-09 00:32:27 +01:00
void print_mastermemberid(uint16_t *value)
2020-11-06 21:13:09 +01:00
{
2021-03-09 00:32:27 +01:00
uint16_t _value=OTdata.u16();
2020-12-12 00:20:31 +01:00
Debugf("%-37s = Master Config[%s] MemberID code [%3d]\r\n", OTmap[OTdata.id].label, byte_to_binary(OTdata.valueHB), OTdata.valueLB);
2020-11-03 01:28:05 +01:00
//Build string for MQTT
char _msg[15] {0};
2021-03-09 00:32:27 +01:00
if (((*value) >> 8)!=OTdata.valueHB) sendMQTTData("master_configuration", byte_to_binary(OTdata.valueHB));
2020-11-03 01:28:05 +01:00
utoa(OTdata.valueLB, _msg, 10);
2021-03-09 00:32:27 +01:00
if ((*value & 0x0F)!=OTdata.valueLB) sendMQTTData("master_memberid_code", _msg);
(*value)=_value;
}
2021-03-09 00:32:27 +01:00
void print_flag8u8(uint16_t *value)
{
2021-03-09 00:32:27 +01:00
uint16_t _value = OTdata.u16();
2020-12-12 00:20:31 +01:00
Debugf("%-37s = M[%s] - [%3d]\r\n", OTmap[OTdata.id].label, byte_to_binary(OTdata.valueHB), OTdata.valueLB);
2020-11-03 01:28:05 +01:00
//Build string for MQTT
char _topic[50] {0};
//flag8 value
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
2020-11-06 21:58:29 +01:00
strlcat(_topic, "_flag8", sizeof(_topic));
2021-03-09 00:32:27 +01:00
if (((*value) >> 8)!=OTdata.valueHB) sendMQTTData(_topic, byte_to_binary(OTdata.valueHB));
2020-11-03 01:28:05 +01:00
//u8 value
char _msg[15] {0};
utoa(OTdata.valueLB, _msg, 10);
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
2020-11-06 21:58:29 +01:00
strlcat(_topic, "_code", sizeof(_topic));
2021-03-09 00:32:27 +01:00
if ((*value & 0x0F)!=OTdata.valueLB) sendMQTTData(_topic, _msg);
(*value)=_value;
}
2021-03-09 00:32:27 +01:00
void print_flag8(uint16_t *value)
{
2021-03-09 00:32:27 +01:00
uint16_t _value=OTdata.u16();
2020-12-12 00:20:31 +01:00
Debugf("%-37s = flag8 = [%s] - decimal = [%3d]\r\n", OTmap[OTdata.id].label, byte_to_binary(OTdata.valueLB), OTdata.valueLB);
2020-11-03 01:28:05 +01:00
//Build string for MQTT
char _topic[50] {0};
//flag8 value
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
2020-11-06 21:58:29 +01:00
strlcat(_topic, "_flag8", sizeof(_topic));
2021-03-09 00:32:27 +01:00
if ((*value & 0x0F)!=OTdata.valueLB) sendMQTTData(_topic, byte_to_binary(OTdata.valueLB));
(*value)=_value;
}
2021-03-09 00:32:27 +01:00
void print_flag8flag8(uint16_t *value)
{
uint16_t _value=OTdata.u16();
2020-11-03 01:28:05 +01:00
//Build string for MQTT
char _topic[50] {0};
//flag8 valueHB
2020-12-12 00:20:31 +01:00
Debugf("%-37s = HB flag8[%s] -[%3d]\r\n", OTmap[OTdata.id].label, byte_to_binary(OTdata.valueHB), OTdata.valueHB);
2020-11-03 01:28:05 +01:00
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
2020-11-06 21:58:29 +01:00
strlcat(_topic, "_hb_flag8", sizeof(_topic));
2021-03-09 00:32:27 +01:00
if (((*value) >> 8)!=OTdata.valueHB) sendMQTTData(_topic, byte_to_binary(OTdata.valueHB));
2020-11-03 01:28:05 +01:00
//flag8 valueLB
2020-12-12 00:20:31 +01:00
Debugf("%-37s = LB flag8[%s] - [%3d]\r\n", OTmap[OTdata.id].label, byte_to_binary(OTdata.valueLB), OTdata.valueLB);
2020-11-03 01:28:05 +01:00
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
2020-11-06 21:58:29 +01:00
strlcat(_topic, "_lb_flag8", sizeof(_topic));
2021-03-09 00:32:27 +01:00
if ((*value & 0x0F)!=OTdata.valueLB) sendMQTTData(_topic, byte_to_binary(OTdata.valueLB));
(*value)=_value;
}
2021-03-09 00:32:27 +01:00
void print_u8u8(uint16_t *value)
{
uint16_t _value=OTdata.u16();
2020-12-12 00:20:31 +01:00
Debugf("%-37s = %3d / %3d %s\r\n", OTmap[OTdata.id].label, (uint8_t)OTdata.valueHB, (uint8_t)OTdata.valueLB, OTmap[OTdata.id].unit);
2020-11-03 01:28:05 +01:00
//Build string for MQTT
char _topic[50] {0};
char _msg[10] {0};
//flag8 valueHB
utoa((OTdata.valueHB), _msg, 10);
2020-12-12 00:20:31 +01:00
Debugf("%-37s = HB u8[%s] [%3d]\r\n", OTmap[OTdata.id].label, _msg, OTdata.valueHB);
2020-11-03 01:28:05 +01:00
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
2020-11-06 21:58:29 +01:00
strlcat(_topic, "_hb_u8", sizeof(_topic));
2021-03-09 00:32:27 +01:00
if (((*value)>>8)!=OTdata.valueHB) sendMQTTData(_topic, _msg);
2020-11-03 01:28:05 +01:00
//flag8 valueLB
utoa((OTdata.valueLB), _msg, 10);
2020-12-12 00:20:31 +01:00
Debugf("%-37s = LB u8[%s] [%3d]\r\n", OTmap[OTdata.id].label, _msg, OTdata.valueLB);
2020-11-03 01:28:05 +01:00
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
2020-11-06 21:58:29 +01:00
strlcat(_topic, "_lb_u8", sizeof(_topic));
2021-03-09 00:32:27 +01:00
if (((*value)&0x0F)!=OTdata.valueLB) sendMQTTData(_topic, _msg);
(*value)=_value;
}
2021-03-09 18:52:58 +01:00
void print_date(uint16_t *value)
{
uint16_t _value=OTdata.u16();
Debugf("%-37s = %3d / %3d %s\r\n", OTmap[OTdata.id].label, (uint8_t)OTdata.valueHB, (uint8_t)OTdata.valueLB, OTmap[OTdata.id].unit);
//Build string for MQTT
char _topic[50] {0};
char _msg[10] {0};
//flag8 valueHB
utoa((OTdata.valueHB), _msg, 10);
Debugf("%-37s = HB u8[%s] [%3d]\r\n", OTmap[OTdata.id].label, _msg, OTdata.valueHB);
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_month", sizeof(_topic));
if (((*value)>>8)!=OTdata.valueHB) sendMQTTData(_topic, _msg);
//flag8 valueLB
utoa((OTdata.valueLB), _msg, 10);
Debugf("%-37s = LB u8[%s] [%3d]\r\n", OTmap[OTdata.id].label, _msg, OTdata.valueLB);
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
strlcat(_topic, "_day_of_month", sizeof(_topic));
if (((*value)&0x0F)!=OTdata.valueLB) sendMQTTData(_topic, _msg);
(*value)=_value;
}
2021-03-09 00:32:27 +01:00
void print_daytime(uint16_t *value)
{
2021-03-09 00:32:27 +01:00
uint16_t _value = OTdata.u16();
//function to print data
const char *dayOfWeekName[] { "Unknown", "Maandag", "Dinsdag", "Woensdag", "Donderdag", "Vrijdag", "Zaterdag", "Zondag", "Unknown" };
2020-12-12 00:20:31 +01:00
Debugf("%-37s = %s - %2d:%2d\r\n", OTmap[OTdata.id].label, dayOfWeekName[(OTdata.valueHB >> 5) & 0x7], (OTdata.valueHB & 0x1F), OTdata.valueLB);
2020-11-03 01:28:05 +01:00
//Build string for MQTT
char _topic[50] {0};
char _msg[10] {0};
//dayofweek
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
2020-11-06 21:58:29 +01:00
strlcat(_topic, "_dayofweek", sizeof(_topic));
2021-03-09 00:32:27 +01:00
if ((((*value) & 0xE0)>>8)!=(OTdata.valueHB >> 5) & 0x7) sendMQTTData(_topic, dayOfWeekName[(OTdata.valueHB >> 5) & 0x7]);
//hour
2020-11-03 01:28:05 +01:00
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
2020-11-06 21:58:29 +01:00
strlcat(_topic, "_hour", sizeof(_topic));
2021-03-09 00:32:27 +01:00
if (((*value)&0x0F)!=(OTdata.valueHB & 0x0F)) sendMQTTData(_topic, itoa((OTdata.valueHB & 0x0F), _msg, 10));
//min
2020-11-03 01:28:05 +01:00
strlcpy(_topic, messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)), sizeof(_topic));
2020-11-06 21:58:29 +01:00
strlcat(_topic, "_minutes", sizeof(_topic));
2021-03-09 00:32:27 +01:00
if (((*value)&0x0F)!=OTdata.valueLB) sendMQTTData(_topic, itoa((OTdata.valueLB), _msg, 10));
(*value) = _value;
}
2021-01-31 23:43:52 +01:00
2020-11-09 00:05:06 +01:00
//===================[ Send buffer to OTGW ]=============================
2020-11-09 00:05:06 +01:00
int sendOTGW(const char* buf, int len)
{
//Send the buffer to OTGW when the Serial interface is available
if (OTGWSerial.availableForWrite()>=len+2) {
2020-11-09 00:05:06 +01:00
//check the write buffer
//Debugf("Serial Write Buffer space = [%d] - needed [%d]\r\n",OTGWSerial.availableForWrite(), (len+2));
DebugT("Sending to Serial [");
for (int i = 0; i < len; i++) {
Debug((char)buf[i]);
}
Debug("] ("); Debug(len); Debug(")"); Debugln();
while (OTGWSerial.availableForWrite()==(len+2)) {
//cannot write, buffer full, wait for some space in serial out buffer
feedWatchDog(); //this yields for other processes
}
if (OTGWSerial.availableForWrite()>= (len+2)) {
2020-11-09 00:05:06 +01:00
//write buffer to serial
OTGWSerial.write(buf, len);
// OTGWSerial.write("PS=0\r\n");
OTGWSerial.write('\r');
OTGWSerial.write('\n');
OTGWSerial.flush();
2020-11-09 00:05:06 +01:00
} else Debugln("Error: Write buffer not big enough!");
} else Debugln("Error: Serial device not found!");
}
/*
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.
*/
bool isvalidotmsg(const char *buf, int len){
char *chk = "TBARE";
bool _ret = (len==9);
_ret &= (strchr(chk, buf[0])!=NULL);
return _ret;
}
/*
Process OTGW messages coming from the PIC.
It knows about:
- raw OTmsg format
- error format
- ...
*/
void processOTGW(const char * buf, int len){
if (isvalidotmsg(buf, len)) {
//OT protocol messages are 9 chars long
2021-03-09 00:45:45 +01:00
if (settingMQTTOTmessage) sendMQTTData("otmessage", buf);
2021-02-08 02:06:04 +01:00
// source of otmsg
if (buf[0]=='B')
{
DebugT("Boiler ");
} else if (buf[0]=='T')
{
DebugT("Thermostat ");
} else if (buf[0]=='R')
2021-02-08 02:06:04 +01:00
{
DebugT("Request Boiler ");
} else if (buf[0]=='A')
{
DebugT("Answer Themostat ");
} else if (buf[0]=='E')
{
DebugT("Parity error ");
}
2021-02-08 02:06:04 +01:00
const char *bufval = buf + 1;
uint32_t value = strtoul(bufval, NULL, 16);
2021-02-08 02:06:04 +01:00
Debugf("msg=[%s] value=[%08x]", bufval, value);
//split 32bit value into the relevant OT protocol parts
OTdata.type = (value >> 28) & 0x7; // byte 1 = take 3 bits that define msg msgType
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
//print message frame
Debugf("\ttype[%3d] id[%3d] hb[%3d] lb[%3d]\t", OTdata.type, OTdata.id, OTdata.valueHB, OTdata.valueLB);
//print message Type and ID
Debugf("[%-16s]\t", messageTypeToString(static_cast<OpenThermMessageType>(OTdata.type)));
Debugf("[%-30s]\t", messageIDToString(static_cast<OpenThermMessageID>(OTdata.id)));
DebugFlush();
//keep track of update
msglastupdated[OTdata.id] = now();
//next step interpret the OT protocol
if (static_cast<OpenThermMessageType>(OTdata.type) == OT_READ_ACK || static_cast<OpenThermMessageType>(OTdata.type) == OT_WRITE_DATA) {
//#define OTprint(data, value, text, format) ({ data= value; Debugf("[%37s]", text); Debugf("= [format]", data)})
//interpret values f8.8
switch (static_cast<OpenThermMessageID>(OTdata.id)) {
2021-03-09 00:32:27 +01:00
case TSet: print_f88(&OTdataObject.TSet); break;
case CoolingControl: print_f88(&OTdataObject.CoolingControl ); break;
case TsetCH2: print_f88(&OTdataObject.TsetCH2); break;
case TrOverride: print_f88(&OTdataObject.TrOverride); break;
case MaxRelModLevelSetting: print_f88(&OTdataObject.MaxRelModLevelSetting); break;
case TrSet: print_f88(&OTdataObject.TrSet); break;
case TrSetCH2: print_f88(&OTdataObject.TrSetCH2); break;
case RelModLevel: print_f88(&OTdataObject.RelModLevel); break;
case CHPressure: print_f88(&OTdataObject.CHPressure); break;
case DHWFlowRate: print_f88(&OTdataObject.DHWFlowRate); break;
case Tr: print_f88(&OTdataObject.Tr); break;
case Tboiler: print_f88(&OTdataObject.Tboiler); break;
case Tdhw: print_f88(&OTdataObject.Tdhw); break;
case Toutside: print_f88(&OTdataObject.Toutside); break;
case Tret: print_f88(&OTdataObject.Tret); break;
case Tstorage: print_f88(&OTdataObject.Tstorage); break;
case Tcollector: print_f88(&OTdataObject.Tcollector ); break;
case TflowCH2: print_f88(&OTdataObject.TflowCH2); break;
case Tdhw2: print_f88(&OTdataObject.Tdhw2); break;
case Texhaust: print_s16(&OTdataObject.Texhaust); break;
case TdhwSet: print_f88(&OTdataObject.TdhwSet); break;
case MaxTSet: print_f88(&OTdataObject.MaxTSet); break;
case Hcratio: print_f88(&OTdataObject.Hcratio); break;
case OpenThermVersionMaster: print_f88(&OTdataObject.OpenThermVersionMaster); break;
case OpenThermVersionSlave: print_f88(&OTdataObject.OpenThermVersionSlave); break;
case Status: print_status(&OTdataObject.Status); break;
case ASFflags: print_ASFflags(&OTdataObject.ASFflags); break;
case MConfigMMemberIDcode: print_mastermemberid(&OTdataObject.MConfigMMemberIDcode); break;
case SConfigSMemberIDcode: print_slavememberid(&OTdataObject.SConfigSMemberIDcode); break;
case Command: print_u8u8(&OTdataObject.Command); break;
case RBPflags: print_flag8flag8(&OTdataObject.RBPflags); break;
case TSP: print_u8u8(&OTdataObject.TSP); break;
case TSPindexTSPvalue: print_u8u8(&OTdataObject.TSPindexTSPvalue); break;
case FHBsize: print_u8u8(&OTdataObject.FHBsize); break;
case FHBindexFHBvalue: print_u8u8(&OTdataObject.FHBindexFHBvalue); break;
case MaxCapacityMinModLevel: print_u8u8(&OTdataObject.MaxCapacityMinModLevel); break;
case DayTime: print_daytime(&OTdataObject.DayTime); break;
2021-03-09 18:52:58 +01:00
case Date: print_date(&OTdataObject.Date); break;
2021-03-09 00:32:27 +01:00
case Year: print_u16(&OTdataObject.Year); break;
case TdhwSetUBTdhwSetLB: print_s8s8(&OTdataObject.TdhwSetUBTdhwSetLB); break;
case MaxTSetUBMaxTSetLB: print_s8s8(&OTdataObject.MaxTSetUBMaxTSetLB); break;
case HcratioUBHcratioLB: print_s8s8(&OTdataObject.HcratioUBHcratioLB ); break;
case RemoteOverrideFunction: print_flag8(&OTdataObject.RemoteOverrideFunction); break;
case OEMDiagnosticCode: print_u16(&OTdataObject.OEMDiagnosticCode); break;
case BurnerStarts: print_u16(&OTdataObject.BurnerStarts); break;
case CHPumpStarts: print_u16(&OTdataObject.CHPumpStarts ); break;
case DHWPumpValveStarts: print_u16(&OTdataObject.DHWPumpValveStarts); break;
case DHWBurnerStarts: print_u16(&OTdataObject.DHWBurnerStarts ); break;
case BurnerOperationHours: print_u16(&OTdataObject.BurnerOperationHours); break;
case CHPumpOperationHours: print_u16(&OTdataObject.CHPumpOperationHours); break;
case DHWPumpValveOperationHours: print_u16(&OTdataObject.DHWPumpValveOperationHours); break;
case DHWBurnerOperationHours: print_u16(&OTdataObject.DHWBurnerOperationHours); break;
case MasterVersion: print_u8u8(&OTdataObject.MasterVersion); break;
case SlaveVersion: print_u8u8(&OTdataObject.SlaveVersion); break;
case StatusVH: print_flag8flag8(&OTdataObject.StatusVH); break;
case ControlSetpointVH: print_u8u8(&OTdataObject.ControlSetpointVH); break;
case FaultFlagsCodeVH: print_flag8u8(&OTdataObject.FaultFlagsCodeVH ); break;
case DiagnosticCodeVH: print_u16(&OTdataObject.DiagnosticCodeVH); break;
case ConfigMemberIDVH: print_flag8u8(&OTdataObject.ConfigMemberIDVH); break;
case OpenthermVersionVH: print_f88(&OTdataObject.OpenthermVersionVH); break;
case VersionTypeVH: print_u8u8(&OTdataObject.VersionTypeVH); break;
case RelativeVentilation: print_u8u8(&OTdataObject.RelativeVentilation); break;
case RelativeHumidityVH: print_u8u8(&OTdataObject.RelativeHumidityVH ); break;
case CO2LevelVH: print_u16(&OTdataObject.CO2LevelVH ); break;
case SupplyInletTemperature: print_f88(&OTdataObject.SupplyInletTemperature); break;
case SupplyOutletTemperature: print_f88(&OTdataObject.SupplyOutletTemperature); break;
case ExhaustInletTemperature: print_f88(&OTdataObject.ExhaustInletTemperature); break;
case ExhaustOutletTemperature: print_f88(&OTdataObject.ExhaustOutletTemperature); break;
case ActualExhaustFanSpeed: print_u16(&OTdataObject.ActualExhaustFanSpeed); break;
case ActualInletFanSpeed: print_u16(&OTdataObject.ActualInletFanSpeed); break;
case RemoteParameterSettingVH: print_flag8flag8(&OTdataObject.RemoteParameterSettingVH); break;
case NominalVentilationValue: print_u8u8(&OTdataObject.NominalVentilationValue); break;
case TSPNumberVH: print_u8u8(&OTdataObject.TSPNumberVH); break;
case TSPEntryVH: print_u8u8(&OTdataObject.TSPEntryVH ); break;
case FaultBufferSizeVH: print_u8u8(&OTdataObject.FaultBufferSizeVH); break;
case FaultBufferEntryVH: print_u8u8(&OTdataObject.FaultBufferEntryVH); break;
case FanSpeed: print_u16(&OTdataObject.FanSpeed); break;
case ElectricalCurrentBurnerFlame: print_f88(&OTdataObject.ElectricalCurrentBurnerFlame ); break;
case TRoomCH2: print_f88(&OTdataObject.TRoomCH2); break;
case RelativeHumidity: print_u8u8(&OTdataObject.RelativeHumidity); break;
case RFstrengthbatterylevel: print_u8u8(&OTdataObject.RFstrengthbatterylevel ); break;
case OperatingMode_HC1_HC2_DHW: print_u8u8(&OTdataObject.OperatingMode_HC1_HC2_DHW ); break;
case ElectricityProducerStarts: print_u16(&OTdataObject.ElectricityProducerStarts); break;
case ElectricityProducerHours: print_u16(&OTdataObject.ElectricityProducerHours ); break;
case ElectricityProduction: print_u16(&OTdataObject.ElectricityProduction); break;
case CumulativElectricityProduction:print_u16(&OTdataObject.CumulativElectricityProduction); break;
case RemehadFdUcodes: print_u8u8(&OTdataObject.RemehadFdUcodes); break;
case RemehaServicemessage: print_u8u8(&OTdataObject.RemehaServicemessage); break;
case RemehaDetectionConnectedSCU: print_u8u8(&OTdataObject.RemehaDetectionConnectedSCU); break;
}
} else Debugln(); //next line
} else if (strstr(buf, "Error 01")!= NULL) {
OTdataObject.error01++;
DebugTf("Error 01 = %d\r\n",OTdataObject.error01);
sendMQTTData("Error 01", String(OTdataObject.error01));
} else if (strstr(buf, "Error 02")!= NULL) {
OTdataObject.error02++;
DebugTf("Error 02 = %d\r\n",OTdataObject.error02);
sendMQTTData("Error 02", String(OTdataObject.error02));
} else if (strstr(buf, "Error 03")!= NULL) {
OTdataObject.error03++;
DebugTf("Error 03 = %d\r\n",OTdataObject.error03);
sendMQTTData("Error 03", String(OTdataObject.error03));
} else if (strstr(buf, "Error 04")!= NULL){
OTdataObject.error04++;
DebugTf("Error 04 = %d\r\n",OTdataObject.error04);
sendMQTTData("Error 04", String(OTdataObject.error04));
} else DebugTf("Unexpected received from OTGW => [%s] [%d]\r\n", buf, len);
}
2020-12-28 21:02:37 +01:00
//====================[ 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
2020-12-28 21:02:37 +01:00
** 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).
2020-12-28 21:02:37 +01:00
**
** The write buffer (incoming from port 25238) is also line printed to the Debug (port 23).
2020-12-28 21:02:37 +01:00
** The read line buffer is per line parsed by the proces OT parser code (processOTGW (buf, len)).
*/
void handleOTGW()
{
//handle serial communication and line processing
2020-12-28 21:02:37 +01:00
#define MAX_BUFFER_READ 256
2021-01-24 22:10:57 +01:00
#define MAX_BUFFER_WRITE 128
2020-12-28 21:02:37 +01:00
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 inByte;
static uint8_t outByte;
//handle incoming data from network (port 25238) sent to serial port OTGW (WRITE BUFFER)
while (OTGWstream.available()){
//OTGWSerial.write(OTGWstream.read()); //just forward it directly to Serial
outByte = OTGWstream.read(); // read from port 25238
while (OTGWSerial.availableForWrite()==0) {
//cannot write, buffer full, wait for some space in serial out buffer
feedWatchDog(); //this yields for other processes
}
OTGWSerial.write(outByte); // write to serial port
OTGWSerial.flush(); // wait for write to serial
if (outByte == '\n')
2021-01-24 23:47:26 +01:00
{ //on newline, do something...
sWrite[bytes_write] = 0;
DebugTf("Net2Ser: Sending to OTGW: [%s] (%d)\r\n", sWrite, bytes_write);
2021-01-31 23:43:52 +01:00
//check for reset command
if (stricmp(sWrite, "GW=R")==0){
//detect [GW=R], then reset the gateway the gpio way
DebugTln("Detected: GW=R. Reset gateway command executed.");
resetOTGW();
}
bytes_write = 0; //start next line
} else if (outByte == '\r')
{
// skip LF
}
else
{
2020-12-28 21:02:37 +01:00
if (bytes_write < (MAX_BUFFER_WRITE-1))
sWrite[bytes_write++] = outByte;
}
2020-11-19 16:47:10 +01:00
}
2020-12-28 21:02:37 +01:00
//Handle incoming data from OTGW through serial port (READ BUFFER)
while(OTGWSerial.available())
{
inByte = OTGWSerial.read(); // read from serial port
OTGWstream.write(inByte); // write to port 25238
if (inByte== '\n')
{ //line terminator, continue to process incoming message
sRead[bytes_read] = 0;
2021-03-04 22:54:22 +01:00
blinkLEDnow(LED2);
processOTGW(sRead, bytes_read);
bytes_read = 0;
break; // to continue processing incoming message
}
else if (inByte == '\r')
{ // just ignore LF...
}
else
{
2020-12-28 21:02:37 +01:00
if (bytes_read < (MAX_BUFFER_READ-1))
sRead[bytes_read++] = inByte;
}
}
}// END of handleOTGW
2020-12-28 21:02:37 +01:00
//====================[ functions for REST API ]====================
2020-12-12 00:20:31 +01:00
String getOTGWValue(int msgid)
2020-12-11 13:38:56 +01:00
{
2020-12-12 00:20:31 +01:00
switch (static_cast<OpenThermMessageID>(msgid)) {
case TSet: return String(OTdataObject.TSet); break;
case CoolingControl: return String(OTdataObject.CoolingControl); break;
case TsetCH2: return String(OTdataObject.TsetCH2); break;
case TrOverride: return String(OTdataObject.TrOverride); break;
case MaxRelModLevelSetting: return String(OTdataObject.MaxRelModLevelSetting); break;
case TrSet: return String(OTdataObject.TrSet); break;
case TrSetCH2: return String(OTdataObject.TrSetCH2); break;
case RelModLevel: return String(OTdataObject.RelModLevel); break;
case CHPressure: return String(OTdataObject.CHPressure); break;
case DHWFlowRate: return String(OTdataObject.DHWFlowRate); break;
case Tr: return String(OTdataObject.Tr); break;
case Tboiler: return String(OTdataObject.Tboiler); break;
case Tdhw: return String(OTdataObject.Tdhw); break;
case Toutside: return String(OTdataObject.Toutside); break;
case Tret: return String(OTdataObject.Tret); break;
case Tstorage: return String(OTdataObject.Tstorage); break;
case Tcollector: return String(OTdataObject.Tcollector); break;
case TflowCH2: return String(OTdataObject.TflowCH2); break;
case Tdhw2: return String(OTdataObject.Tdhw2); break;
case Texhaust: return String(OTdataObject.Texhaust); break;
case TdhwSet: return String(OTdataObject.TdhwSet); break;
case MaxTSet: return String(OTdataObject.MaxTSet); break;
case Hcratio: return String(OTdataObject.Hcratio); break;
case OpenThermVersionMaster: return String(OTdataObject.OpenThermVersionMaster); break;
case OpenThermVersionSlave: return String(OTdataObject.OpenThermVersionSlave); break;
case Status: return String(OTdataObject.Status); break;
case ASFflags: return String(OTdataObject.ASFflags); break;
case MConfigMMemberIDcode: return String(OTdataObject.MConfigMMemberIDcode); break;
case SConfigSMemberIDcode: return String(OTdataObject.SConfigSMemberIDcode); break;
case Command: return String(OTdataObject.Command); break;
case RBPflags: return String(OTdataObject.RBPflags); break;
case TSP: return String(OTdataObject.TSP); break;
case TSPindexTSPvalue: return String(OTdataObject.TSPindexTSPvalue); break;
case FHBsize: return String(OTdataObject.FHBsize); break;
case FHBindexFHBvalue: return String(OTdataObject.FHBindexFHBvalue); break;
case MaxCapacityMinModLevel: return String(OTdataObject.MaxCapacityMinModLevel); break;
case DayTime: return String(OTdataObject.DayTime); break;
case Date: return String(OTdataObject.Date); break;
case Year: return String(OTdataObject.Year); break;
case TdhwSetUBTdhwSetLB: return String(OTdataObject.TdhwSetUBTdhwSetLB); break;
case MaxTSetUBMaxTSetLB: return String(OTdataObject.MaxTSetUBMaxTSetLB); break;
case HcratioUBHcratioLB: return String(OTdataObject.HcratioUBHcratioLB); break;
case RemoteOverrideFunction: return String(OTdataObject.RemoteOverrideFunction); break;
case OEMDiagnosticCode: return String(OTdataObject.OEMDiagnosticCode); break;
case BurnerStarts: return String(OTdataObject.BurnerStarts); break;
case CHPumpStarts: return String(OTdataObject.CHPumpStarts); break;
case DHWPumpValveStarts: return String(OTdataObject.DHWPumpValveStarts); break;
case DHWBurnerStarts: return String(OTdataObject.DHWBurnerStarts); break;
case BurnerOperationHours: return String(OTdataObject.BurnerOperationHours); break;
case CHPumpOperationHours: return String(OTdataObject.CHPumpOperationHours); break;
case DHWPumpValveOperationHours: return String(OTdataObject.DHWPumpValveOperationHours); break;
case DHWBurnerOperationHours: return String(OTdataObject.DHWBurnerOperationHours); break;
case MasterVersion: return String(OTdataObject.MasterVersion); break;
case SlaveVersion: return String(OTdataObject.SlaveVersion); break;
case StatusVH: return String(OTdataObject.StatusVH); break;
case ControlSetpointVH: return String(OTdataObject.ControlSetpointVH); break;
case FaultFlagsCodeVH: return String(OTdataObject.FaultFlagsCodeVH); break;
case DiagnosticCodeVH: return String(OTdataObject.DiagnosticCodeVH); break;
case ConfigMemberIDVH: return String(OTdataObject.ConfigMemberIDVH); break;
case OpenthermVersionVH: return String(OTdataObject.OpenthermVersionVH); break;
case VersionTypeVH: return String(OTdataObject.VersionTypeVH); break;
case RelativeVentilation: return String(OTdataObject.RelativeVentilation); break;
case RelativeHumidityVH: return String(OTdataObject.RelativeHumidityVH); break;
case CO2LevelVH: return String(OTdataObject.CO2LevelVH); break;
case SupplyInletTemperature: return String(OTdataObject.SupplyInletTemperature); break;
case SupplyOutletTemperature: return String(OTdataObject.SupplyOutletTemperature); break;
case ExhaustInletTemperature: return String(OTdataObject.ExhaustInletTemperature); break;
case ExhaustOutletTemperature: return String(OTdataObject.ExhaustOutletTemperature); break;
case ActualExhaustFanSpeed: return String(OTdataObject.ActualExhaustFanSpeed); break;
case ActualInletFanSpeed: return String(OTdataObject.ActualInletFanSpeed); break;
case RemoteParameterSettingVH: return String(OTdataObject.RemoteParameterSettingVH); break;
case NominalVentilationValue: return String(OTdataObject.NominalVentilationValue); break;
case TSPNumberVH: return String(OTdataObject.TSPNumberVH); break;
case TSPEntryVH: return String(OTdataObject.TSPEntryVH); break;
case FaultBufferSizeVH: return String(OTdataObject.FaultBufferSizeVH); break;
case FaultBufferEntryVH: return String(OTdataObject.FaultBufferEntryVH); break;
case FanSpeed: return String(OTdataObject.FanSpeed); break;
case ElectricalCurrentBurnerFlame: return String(OTdataObject.ElectricalCurrentBurnerFlame); break;
case TRoomCH2: return String(OTdataObject.TRoomCH2); break;
case RelativeHumidity: return String(OTdataObject.RelativeHumidity); break;
case RFstrengthbatterylevel: return String(OTdataObject.RFstrengthbatterylevel); break;
case OperatingMode_HC1_HC2_DHW: return String(OTdataObject.OperatingMode_HC1_HC2_DHW); break;
case ElectricityProducerStarts: return String(OTdataObject.ElectricityProducerStarts); break;
case ElectricityProducerHours: return String(OTdataObject.ElectricityProducerHours); break;
case ElectricityProduction: return String(OTdataObject.ElectricityProduction); break;
case CumulativElectricityProduction: return String(OTdataObject.CumulativElectricityProduction); break;
case RemehadFdUcodes: return String(OTdataObject.RemehadFdUcodes); break;
case RemehaServicemessage: return String(OTdataObject.RemehaServicemessage); break;
case RemehaDetectionConnectedSCU: return String(OTdataObject.RemehaDetectionConnectedSCU); break;
2020-12-12 00:20:31 +01:00
default: return "not implemented yet!";
}
2020-12-11 13:38:56 +01:00
}
2020-12-10 23:55:22 +01:00
2020-11-16 23:20:30 +01:00
void startOTGWstream()
{
OTGWstream.begin();
}
void upgradepicnow(const char *filename) {
if (OTGWSerial.busy()) return; // if already in programming mode, never call it twice
DebugTln("Start PIC upgrade now.");
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) {
DebugTf("Upgrade finished: Errorcode = %d - %d retries, %d errors\n", result, retries, errors);
switch (result) {
case OTGW_ERROR_NONE: errorupgrade = "PIC upgrade was succesful."; break;
case OTGW_ERROR_MEMORY: errorupgrade = "Not enough memory available."; break;
case OTGW_ERROR_INPROG: errorupgrade = "Firmware upgrade in progress."; break;
case OTGW_ERROR_HEX_ACCESS: errorupgrade = "Could not open hex file."; break;
case OTGW_ERROR_HEX_FORMAT: errorupgrade = "Invalid format of hex file."; break;
case OTGW_ERROR_HEX_DATASIZE: errorupgrade = "Wrong data size in hex file."; break;
case OTGW_ERROR_HEX_CHECKSUM: errorupgrade = "Bad checksum in hex file."; break;
case OTGW_ERROR_MAGIC: errorupgrade = "Hex file does not contain expected data."; break;
case OTGW_ERROR_RESET: errorupgrade = "PIC reset failed."; break;
case OTGW_ERROR_RETRIES: errorupgrade = "Too many retries."; break;
case OTGW_ERROR_MISMATCHES: errorupgrade = "Too many mismatches."; break;
default: errorupgrade = "Unknown state."; break;
}
}
2021-02-14 14:31:21 +01:00
// Schelte's firmware integration
void fwupgradestart(const char *hexfile) {
OTGWError result;
digitalWrite(LED1, LOW);
result = OTGWSerial.startUpgrade(hexfile);
if (result!= OTGW_ERROR_NONE) {
fwupgradedone(result);
} else {
OTGWSerial.registerFinishedCallback(fwupgradedone);
}
}
2021-02-20 17:49:56 +01:00
String checkforupdatepic(String filename){
2021-02-19 12:44:45 +01:00
WiFiClient client;
2021-02-19 01:48:31 +01:00
HTTPClient http;
2021-02-20 17:49:56 +01:00
String latest = "";
2021-02-19 01:48:31 +01:00
int code;
http.begin(client, "http://otgw.tclcode.com/download/" + filename);
http.collectHeaders(hexheaders, 2);
code = http.sendRequest("HEAD");
if (code == HTTP_CODE_OK) {
for (int i = 0; i< http.headers(); i++) {
DebugTf("%s: %s\r\n", hexheaders[i], http.header(i).c_str());
2021-02-19 01:48:31 +01:00
}
latest = http.header(1);
DebugTf("Update %s -> %s\r\n", filename.c_str(), latest.c_str());
2021-02-19 01:48:31 +01:00
http.end();
}
2021-02-20 17:49:56 +01:00
return latest;
2021-02-19 01:48:31 +01:00
}
2021-02-14 14:31:21 +01:00
void refreshpic(String filename, String version) {
2021-02-14 14:31:21 +01:00
WiFiClient client;
HTTPClient http;
String latest;
int code;
2021-02-19 12:44:45 +01:00
2021-02-20 17:49:56 +01:00
if (latest=checkforupdatepic(filename) != "") {
2021-02-14 14:31:21 +01:00
if (latest != version) {
DebugTf("Update %s: %s -> %s\r\n", filename.c_str(), version.c_str(), latest.c_str());
2021-02-14 14:31:21 +01:00
http.begin(client, "http://otgw.tclcode.com/download/" + filename);
code = http.GET();
if (code == HTTP_CODE_OK) {
File f = LittleFS.open("/" + filename, "w");
if (f) {
http.writeToStream(&f);
f.close();
String verfile = "/" + filename;
verfile.replace(".hex", ".ver");
f = LittleFS.open(verfile, "w");
if (f) {
f.print(latest + "\n");
f.close();
DebugTf("Update successful\n");
}
}
}
}
}
http.end();
}
void upgradepic() {
2021-02-14 14:31:21 +01:00
String action = httpServer.arg("action");
String filename = httpServer.arg("name");
String version = httpServer.arg("version");
DebugTf("Action: %s %s %s\r\n", action.c_str(), filename.c_str(), version.c_str());
if (action == "upgrade") {
upgradepicnow(String("/" + filename).c_str());
} else if (action == "refresh") {
refreshpic(filename, version);
2021-02-14 14:31:21 +01:00
} else if (action == "delete") {
String path = "/" + filename;
LittleFS.remove(path);
path.replace(".hex", ".ver");
LittleFS.remove(path);
}
2021-02-19 00:28:21 +01:00
httpServer.sendHeader("Location", "index.html#tabPICflash", true);
httpServer.send(303, "text/html", "<a href='index.html#tabPICflash'>Return</a>");
2021-02-14 14:31:21 +01:00
}
/***************************************************************************
*
* 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.
*
***************************************************************************/