Test new tx_mode 2, small changes to MM100, SM100 and RC300, see #397

This commit is contained in:
MichaelDvP
2020-06-13 10:29:03 +02:00
parent ee29a39140
commit b75eea44a1
16 changed files with 250 additions and 101 deletions

View File

@@ -49,8 +49,9 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
LOG_DEBUG(F("Registering new Boiler with device ID 0x%02X"), device_id);
// the telegram handlers...
register_telegram_type(0x10, F("UBAErrorMessage1"), false, nullptr);
register_telegram_type(0x11, F("UBAErrorMessage2"), false, nullptr);
register_telegram_type(0x10, F("UBAErrorMessage1"), false, std::bind(&Boiler::process_UBAErrorMessage, this, _1));
register_telegram_type(0x11, F("UBAErrorMessage2"), false, std::bind(&Boiler::process_UBAErrorMessage, this, _1));
register_telegram_type(0x12, F("UBAErrorMessage3"), false, std::bind(&Boiler::process_UBAErrorMessage, this, _1));
register_telegram_type(0x18, F("UBAMonitorFast"), false, std::bind(&Boiler::process_UBAMonitorFast, this, _1));
register_telegram_type(0x19, F("UBAMonitorSlow"), true, std::bind(&Boiler::process_UBAMonitorSlow, this, _1));
register_telegram_type(0x34, F("UBAMonitorWW"), false, std::bind(&Boiler::process_UBAMonitorWW, this, _1));
@@ -679,6 +680,12 @@ void Boiler::process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegr
void Boiler::process_UBAMaintenanceSettings(std::shared_ptr<const Telegram> telegram) {
}
// 0x10, 0x11, 0x12
// not yet implemented
void Boiler::process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram) {
// data: displaycode(2), errornumner(2), year, month, hour, day, minute, duration(2), src-addr
}
#pragma GCC diagnostic pop
// Set the warm water temperature 0x33

View File

@@ -141,6 +141,7 @@ class Boiler : public EMSdevice {
void process_MC10Status(std::shared_ptr<const Telegram> telegram);
void process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegram);
void process_UBAMaintenanceSettings(std::shared_ptr<const Telegram> telegram);
void process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram);
void process_UBADHWStatus(std::shared_ptr<const Telegram> telegram);
void process_HPMonitor1(std::shared_ptr<const Telegram> telegram);

View File

@@ -1,307 +0,0 @@
/*
* EMS-ESP - https://github.com/proddy/EMS-ESP
* Copyright 2019 Paul Derbyshire
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "helpers.h"
#include "telegram.h" // for EMS_VALUE_* settings
namespace emsesp {
// like itoa but for hex, and quicker
char * Helpers::hextoa(char * result, const uint8_t value) {
char * p = result;
uint8_t nib1 = (value >> 4) & 0x0F;
uint8_t nib2 = (value >> 0) & 0x0F;
*p++ = nib1 < 0xA ? '0' + nib1 : 'A' + nib1 - 0xA;
*p++ = nib2 < 0xA ? '0' + nib2 : 'A' + nib2 - 0xA;
*p = '\0'; // null terminate just in case
return result;
}
/*
* itoa for 2 byte integers
* written by Lukás Chmela, Released under GPLv3. http://www.strudel.org.uk/itoa/ version 0.4
*/
char * Helpers::itoa(char * result, int16_t value, const uint8_t base) {
// check that the base if valid
if (base < 2 || base > 36) {
*result = '\0';
return result;
}
char * ptr = result, *ptr1 = result, tmp_char;
int16_t tmp_value;
do {
tmp_value = value;
value /= base;
*ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + (tmp_value - value * base)];
} while (value);
// Apply negative sign
if (tmp_value < 0) {
*ptr++ = '-';
}
*ptr-- = '\0';
while (ptr1 < ptr) {
tmp_char = *ptr;
*ptr-- = *ptr1;
*ptr1++ = tmp_char;
}
return result;
}
// for decimals 0 to 99, printed as a 2 char string
char * Helpers::smallitoa(char * result, const uint8_t value) {
result[0] = ((value / 10) == 0) ? '0' : (value / 10) + '0';
result[1] = (value % 10) + '0';
result[2] = '\0';
return result;
}
// for decimals 0 to 999, printed as a string
char * Helpers::smallitoa(char * result, const uint16_t value) {
result[0] = ((value / 100) == 0) ? '0' : (value / 100) + '0';
result[1] = (((value % 100) / 10) == 0) ? '0' : ((value % 100) / 10) + '0';
result[2] = (value % 10) + '0';
result[3] = '\0';
return result;
}
// convert unsigned int (single byte) to text value and returns it
// format: 2=divide by 2, 10=divide by 10, 255=handle as a Boolean
char * Helpers::render_value(char * result, uint8_t value, uint8_t format) {
result[0] = '\0';
// check if its a boolean
if (format == EMS_VALUE_BOOL) {
if (value == EMS_VALUE_BOOL_OFF) {
strlcpy(result, "off", 5);
} else if (value == EMS_VALUE_BOOL_NOTSET) {
strlcpy(result, "?", 5);
} else {
strlcpy(result, "on", 5); // assume on. could have value 0x01 or 0xFF
}
return result;
}
if (value == EMS_VALUE_UINT_NOTSET) {
strlcpy(result, "?", 5);
return (result);
}
static char s2[5] = {0};
switch (format) {
case 2:
strlcpy(result, itoa(s2, value >> 1, 10), 5);
strlcat(result, ".", 5);
strlcat(result, ((value & 0x01) ? "5" : "0"), 5);
break;
case 10:
strlcpy(result, itoa(s2, value / 10, 10), 5);
strlcat(result, ".", 5);
strlcat(result, itoa(s2, value % 10, 10), 5);
break;
default:
itoa(result, value, 10);
break;
}
return result;
}
// convert float to char
// format is the precision
char * Helpers::render_value(char * result, const float value, const uint8_t format) {
long p[] = {0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
char * ret = result;
long whole = (long)value;
Helpers::itoa(result, whole, 10);
while (*result != '\0') {
result++;
}
*result++ = '.';
long decimal = abs((long)((value - whole) * p[format]));
itoa(result, decimal, 10);
return ret;
}
// convert short (two bytes) to text string and returns string
// decimals: 0 = no division, 10=divide value by 10, 2=divide by 2, 100=divide value by 100
// negative values are assumed stored as 1-compliment (https://medium.com/@LeeJulija/how-integers-are-stored-in-memory-using-twos-complement-5ba04d61a56c)
char * Helpers::render_value(char * result, const int16_t value, const uint8_t format) {
result[0] = '\0';
// remove errors or invalid values, 0x7D00 and higher
if ((value == EMS_VALUE_SHORT_NOTSET) || (value == EMS_VALUE_SHORT_INVALID) || (value == EMS_VALUE_USHORT_NOTSET)) {
strlcpy(result, "?", 10);
return result;
}
// just print it if mo conversion required
if ((format == 0) || (format == 1)) {
itoa(result, value, 10);
return result;
}
int16_t new_value = value;
// check for negative values
if (new_value < 0) {
strlcpy(result, "-", 10);
new_value *= -1; // convert to positive
} else {
strlcpy(result, "", 10);
}
// do floating point
char s2[10] = {0};
if (format == 2) {
// divide by 2
strlcat(result, itoa(s2, new_value / 2, 10), 10);
strlcat(result, ".", 10);
strlcat(result, ((new_value & 0x01) ? "5" : "0"), 10);
} else {
strlcat(result, itoa(s2, new_value / format, 10), 10);
strlcat(result, ".", 10);
strlcat(result, itoa(s2, new_value % format, 10), 10);
}
return result;
}
// convert unsigned short (two bytes) to text string and prints it
// format: 0 = no division, 10=divide value by 10, 2=divide by 2, 100=divide value by 100
char * Helpers::render_value(char * result, const uint16_t value, const uint8_t format) {
result[0] = '\0';
if ((value == EMS_VALUE_USHORT_NOTSET) || (value == EMS_VALUE_USHORT_INVALID)) {
strlcpy(result, "?", 10);
return result;
}
return (render_value(result, (int16_t)value, format)); // use same code, force it to a signed int
}
// convert signed byte to text string and prints it
// format: 0 = no division, 10=divide value by 10, 2=divide by 2, 100=divide value by 100
char * Helpers::render_value(char * result, const int8_t value, const uint8_t format) {
result[0] = '\0';
if (value == EMS_VALUE_INT_NOTSET) {
strlcpy(result, "?", 10);
return result;
}
return (render_value(result, (int16_t)value, format)); // use same code, force it to a signed int
}
// render long (4 byte) unsigned values
// format = 0 for normal, any other value for divide by format
char * Helpers::render_value(char * result, const uint32_t value, const uint8_t format) {
result[0] = '\0';
if ((value == EMS_VALUE_ULONG_NOTSET) || (value == EMS_VALUE_ULONG_INVALID)) {
strlcpy(result, "?", 10);
return (result);
}
static char s[20] = {0};
#ifndef EMSESP_STANDALONE
if (format <= 1) {
strlcat(result, ltoa(value, s, 10), 20);
} else {
strlcat(result, ltoa(value / format, s, 10), 20);
strlcat(result, ".", 2);
strlcat(result, ltoa(value % format, s, 10), 20);
}
#else
strncat(result, itoa(s, value / format, 10), 20);
#endif
return result;
}
// creates string of hex values from an arrray of bytes
std::string Helpers::data_to_hex(const uint8_t * data, const uint8_t length) {
if (length == 0) {
return uuid::read_flash_string(F("<empty>"));
}
std::string str(160, '\0');
char buffer[4];
char * p = &str[0];
for (uint8_t i = 0; i < length; i++) {
Helpers::hextoa(buffer, data[i]);
*p++ = buffer[0];
*p++ = buffer[1];
*p++ = ' '; // space
}
*--p = '\0'; // null terminate just in case, loosing the trailing space
return str;
}
// takes a hex string and convert it to an unsigned 32bit number (max 8 hex digits)
// works with only positive numbers
uint32_t Helpers::hextoint(const char * hex) {
uint32_t val = 0;
while (*hex) {
// get current character then increment
char byte = *hex++;
// transform hex character to the 4bit equivalent number, using the ascii table indexes
if (byte >= '0' && byte <= '9')
byte = byte - '0';
else if (byte >= 'a' && byte <= 'f')
byte = byte - 'a' + 10;
else if (byte >= 'A' && byte <= 'F')
byte = byte - 'A' + 10;
else
return 0; // error
// shift 4 to make space for new digit, and add the 4 bits of the new digit
val = (val << 4) | (byte & 0xF);
}
return val;
}
// quick char to long
uint16_t Helpers::atoint(const char * value) {
unsigned int x = 0;
while (*value != '\0') {
x = (x * 10) + (*value - '0');
++value;
}
return x;
}
// rounds a number to 2 decimal places
// example: round2(3.14159) -> 3.14
double Helpers::round2(double value) {
return (int)(value * 100 + 0.5) / 100.0;
}
} // namespace emsesp

View File

@@ -40,9 +40,9 @@ Mixing::Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
}
// EMS 1.0
if (flags == EMSdevice::EMS_DEVICE_FLAG_MM10) {
register_telegram_type(0x00AA, F("MMConfigMessage"), false, nullptr);
register_telegram_type(0x00AA, F("MMConfigMessage"), false, std::bind(&Mixing::process_MMConfigMessage, this, _1));
register_telegram_type(0x00AB, F("MMStatusMessage"), true, std::bind(&Mixing::process_MMStatusMessage, this, _1));
register_telegram_type(0x00AC, F("MMSetMessage"), false, nullptr);
register_telegram_type(0x00AC, F("MMSetMessage"), false, std::bind(&Mixing::process_MMSetMessage, this, _1));
}
// MQTT callbacks
@@ -131,8 +131,9 @@ void Mixing::process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> tele
type_ = Type::HC;
hc_ = telegram->type_id - 0x02D7 + 1; // determine which circuit this is
telegram->read_value(flowTemp_, 3); // is * 10
telegram->read_value(pumpMod_, 5);
telegram->read_value(status_, 2); // valve status
telegram->read_value(flowSetTemp_, 5);
telegram->read_value(pumpMod_, 2);
telegram->read_value(status_, 1); // valve status
}
// Mixing module warm water loading/DHW - 0x0331, 0x0332
@@ -161,4 +162,19 @@ void Mixing::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(flowSetTemp_, 0);
}
// Mixing on a MM10 - 0xAA
// e.g. Thermostat -> Mixing Module, type 0xAA, telegram: 10 21 AA 00 FF 0C 0A 11 0A 32 xx
void Mixing::process_MMConfigMessage(std::shared_ptr<const Telegram> telegram) {
hc_ = device_id() - 0x20 + 1;
// pos 0: active FF = on
// pos 1: valve runtime 0C = 120 sec in units of 10 sec
}
// Mixing on a MM10 - 0xAC
// e.g. Thermostat -> Mixing Module, type 0xAC, telegram: 10 21 AC 00 1E 64 01 AB
void Mixing::process_MMSetMessage(std::shared_ptr<const Telegram> telegram) {
hc_ = device_id() - 0x20 + 1;
// pos 0: flowtemp setpoint 1E = 30°C
// pos 1: position in %
}
} // namespace emsesp

View File

@@ -49,6 +49,8 @@ class Mixing : public EMSdevice {
void process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram);
void process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram);
void process_MMStatusMessage(std::shared_ptr<const Telegram> telegram);
void process_MMConfigMessage(std::shared_ptr<const Telegram> telegram);
void process_MMSetMessage(std::shared_ptr<const Telegram> telegram);
enum class Type {
NONE,

View File

@@ -37,9 +37,9 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
register_telegram_type(0x0362, F("SM100Monitor"), true, std::bind(&Solar::process_SM100Monitor, this, _1));
register_telegram_type(0x0364, F("SM100Status"), false, std::bind(&Solar::process_SM100Status, this, _1));
register_telegram_type(0x036A, F("SM100Status2"), false, std::bind(&Solar::process_SM100Status2, this, _1));
register_telegram_type(0x038E, F("SM100Energy"), false, std::bind(&Solar::process_SM100Energy, this, _1));
register_telegram_type(0x0003, F("ISM1StatusMessage"), true, std::bind(&Solar::process_ISM1StatusMessage, this, _1));
register_telegram_type(0x0001, F("ISM1Set"), false, std::bind(&Solar::process_ISM1Set, this, _1));
register_telegram_type(0x038E, F("SM100Energy"), true, std::bind(&Solar::process_SM100Energy, this, _1));
register_telegram_type(0x0103, F("ISM1StatusMessage"), true, std::bind(&Solar::process_ISM1StatusMessage, this, _1));
register_telegram_type(0x0101, F("ISM1Set"), false, std::bind(&Solar::process_ISM1Set, this, _1));
// MQTT callbacks
// register_mqtt_topic("cmd", std::bind(&Solar::cmd, this, _1));
@@ -130,10 +130,11 @@ void Solar::process_SM10Monitor(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(bottomTemp_, 5); // bottom temp from SM10, is *10
telegram->read_value(pumpModulation_, 4); // modulation solar pump
telegram->read_value(pump_, 7, 1);
telegram->read_value(pumpWorkMin_, 8);
}
/*
* SM100Monitor - type 0x0162 EMS+ - for SM100 and SM200
* SM100Monitor - type 0x0262 EMS+ - for SM100 and SM200
* e.g. B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80
* e.g, 30 00 FF 00 02 62 01 AC
* 30 00 FF 18 02 62 80 00

View File

@@ -31,7 +31,6 @@ MAKE_PSTR(master_thermostat_fmt, "Master Thermostat device ID = %s")
namespace emsesp {
REGISTER_FACTORY(Thermostat, EMSdevice::DeviceType::THERMOSTAT);
MAKE_PSTR(logger_name, "thermostat")
uuid::log::Logger Thermostat::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
@@ -239,7 +238,6 @@ void Thermostat::thermostat_cmd(const char * message) {
strlcpy(hc_name, "hc", 6);
uint8_t hc_num = hc->hc_num();
strlcat(hc_name, Helpers::itoa(s, hc_num), 6);
if (nullptr != doc[hc_name]["mode"]) {
std::string mode = doc[hc_name]["mode"]; // first check mode
set_mode(mode, hc_num);
@@ -279,29 +277,29 @@ void Thermostat::thermostat_cmd(const char * message) {
uint8_t ctrl = doc[hc_name]["control"];
set_control(ctrl, hc_num);
}
if (float ct = doc["calinttemp"]) {
set_settings_calinttemp((int8_t)(ct * 10));
}
if (nullptr != doc["minexttemp"]) {
int8_t mt = doc["minexttemp"];
set_settings_minexttemp(mt);
}
if (nullptr != doc["building"]) {
uint8_t bd = doc["building"];
set_settings_building(bd);
}
if (nullptr != doc["language"]) {
uint8_t lg = doc["language"];
set_settings_language(lg);
}
if (nullptr != doc["display"]) {
uint8_t dp = doc["display"];
set_settings_display(dp);
}
if (nullptr != doc["clockoffset"]) {
int8_t co = doc["clockoffset"];
set_settings_clockoffset(co);
}
}
if (float ct = doc["calinttemp"]) {
set_settings_calinttemp((int8_t)(ct * 10));
}
if (nullptr != doc["minexttemp"]) {
int8_t mt = doc["minexttemp"];
set_settings_minexttemp(mt);
}
if (nullptr != doc["building"]) {
uint8_t bd = doc["building"];
set_settings_building(bd);
}
if (nullptr != doc["language"]) {
uint8_t lg = doc["language"];
set_settings_language(lg);
}
if (nullptr != doc["display"]) {
uint8_t dp = doc["display"];
set_settings_display(dp);
}
if (nullptr != doc["clockoffset"]) {
int8_t co = doc["clockoffset"];
set_settings_clockoffset(co);
}
const char * command = doc["cmd"];
@@ -1023,17 +1021,17 @@ void Thermostat::process_JunkersMonitor(std::shared_ptr<const Telegram> telegram
// type 0x02A5 - data from the Nefit RC1010/3000 thermostat (0x18) and RC300/310s on 0x10
void Thermostat::process_RC300Monitor(std::shared_ptr<const Telegram> telegram) {
if (telegram->message_data[2] == 0x00) {
return;
}
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->curr_roomTemp, 0); // is * 10
telegram->read_value(hc->mode_type, 10, 1);
telegram->read_value(hc->mode, 10, 0); // bit 1, mode (auto=1 or manual=0)
// setpoint is in offset 3 and also 7. We're sticking to 3 for now.
// also ignore if its 0 - see https://github.com/proddy/EMS-ESP/issues/256#issuecomment-585171426
if (telegram->message_data[3] != 0) {
telegram->read_value8(hc->setpoint_roomTemp, 3); // is * 2, force as single byte
}
telegram->read_value8(hc->setpoint_roomTemp, 3); // is * 2, force as single byte
}
// type 0x02B9 EMS+ for reading from RC300/RC310 thermostat
@@ -1044,11 +1042,12 @@ void Thermostat::process_RC300Set(std::shared_ptr<const Telegram> telegram) {
// manual is position 10
// comfort is position 2
// I think auto is position 8?
telegram->read_value8(hc->setpoint_roomTemp, 8); // single byte conversion, value is * 2 - auto?
telegram->read_value8(hc->setpoint_roomTemp, 10); // single byte conversion, value is * 2 - manual
// actual setpoint taken from RC300Monitor (Michael 12.06.2020)
// telegram->read_value8(hc->setpoint_roomTemp, 8); // single byte conversion, value is * 2 - auto?
// telegram->read_value8(hc->setpoint_roomTemp, 10); // single byte conversion, value is * 2 - manual
// check why mode is both in the Monitor and Set for the RC300. It'll be read twice!
telegram->read_value(hc->mode, 0); // Auto = xFF, Manual = x00 eg. 10 00 FF 08 01 B9 FF
// telegram->read_value(hc->mode, 0); // Auto = xFF, Manual = x00 eg. 10 00 FF 08 01 B9 FF
telegram->read_value(hc->daytemp, 2); // is * 2
telegram->read_value(hc->nighttemp, 4); // is * 2
@@ -1182,7 +1181,7 @@ void Thermostat::set_settings_building(const uint8_t bg) {
// 0xA5 Set the language settings
void Thermostat::set_settings_language(const uint8_t lg) {
if ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) {
LOG_INFO(F("Setting building to %d"), lg);
LOG_INFO(F("Setting language to %d"), lg);
write_command(EMS_TYPE_IBASettings, 1, lg);
}
}
@@ -1199,8 +1198,10 @@ void Thermostat::set_control(const uint8_t ctrl, const uint8_t hc_num) {
return;
}
if ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC35 || (flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) {
LOG_INFO(F("Setting Circuit-control for hc%d to %d"), hc_num, ctrl);
LOG_INFO(F("Setting circuit-control for hc%d to %d"), hc_num, ctrl);
write_command(set_typeids[hc->hc_num() - 1], 26, ctrl);
} else {
LOG_INFO(F("Setting circuit-control not possible"));
}
}

View File

@@ -29,7 +29,6 @@
#include "emsesp.h"
#include "helpers.h"
#include "mqtt.h"
#include "roomcontrol.h"
#include <vector>