feat: add Modbus support

This commit is contained in:
mheyse
2024-06-30 15:24:46 +02:00
parent 217d90629a
commit 0c76a249e3
71 changed files with 8941 additions and 12 deletions

View File

@@ -121,6 +121,22 @@
#define EMSESP_DEFAULT_TELNET_ENABLED true
#endif
#ifndef EMSESP_DEFAULT_MODBUS_ENABLED
#define EMSESP_DEFAULT_MODBUS_ENABLED false
#endif
#ifndef EMSESP_DEFAULT_MODBUS_PORT
#define EMSESP_DEFAULT_MODBUS_PORT 502
#endif
#ifndef EMSESP_DEFAULT_MODBUS_MAX_CLIENTS
#define EMSESP_DEFAULT_MODBUS_MAX_CLIENTS 10
#endif
#ifndef EMSESP_DEFAULT_MODBUS_TIMEOUT
#define EMSESP_DEFAULT_MODBUS_TIMEOUT 10000
#endif
#ifndef EMSESP_DEFAULT_BOARD_PROFILE
#define EMSESP_DEFAULT_BOARD_PROFILE "default"
#endif

View File

@@ -1374,6 +1374,33 @@ void EMSdevice::dump_value_info() {
Serial.print(entityid);
}
Serial.print(",");
// modbus specific infos
Serial.print(device_type());
Serial.print(',');
Serial.print(dv.tag);
Serial.print(',');
// numeric operator -> scale factor
if (dv.numeric_operator == 0)
Serial.print("1");
else if (dv.numeric_operator > 0)
Serial.printf("1/%d", dv.numeric_operator);
else
Serial.print(-dv.numeric_operator);
Serial.print(",");
Serial.printf("%d", EMSESP::modbus_->getRegisterOffset(dv));
Serial.print(",");
Serial.printf("%d", EMSESP::modbus_->getRegisterCount(dv));
// /modbus specific infos
Serial.println();
}
}
@@ -1901,4 +1928,155 @@ std::string EMSdevice::name() {
return custom_name_;
}
// copy a raw value (i.e. without applying the numeric_operator) to the output buffer.
// returns true on success.
int EMSdevice::get_modbus_value(uint8_t tag, const std::string & shortname, std::vector<uint16_t> & result) {
// find device value by shortname
// TODO linear search is inefficient
const auto & it = std::find_if(devicevalues_.begin(), devicevalues_.end(), [&](const DeviceValue & x) { return x.tag == tag && x.short_name == shortname; });
if (it == devicevalues_.end())
return -1;
auto & dv = *it;
// check if it exists, there is a value for the entity. Set the flag to ACTIVE
// not that this will override any previously removed states
(dv.hasValue()) ? dv.add_state(DeviceValueState::DV_ACTIVE) : dv.remove_state(DeviceValueState::DV_ACTIVE);
if (!dv.has_state(DeviceValueState::DV_ACTIVE))
return -2;
// handle Booleans
if (dv.type == DeviceValueType::BOOL) {
if (result.size() != 1)
return -3;
auto value_bool = *(uint8_t *)(dv.value_p);
if (!Helpers::hasValue(value_bool, EMS_VALUE_BOOL))
return -4;
result[0] = value_bool ? 1 : 0;
}
// handle TEXT strings
else if (dv.type == DeviceValueType::STRING) {
auto value_s = (char *)dv.value_p;
size_t length_s = strlen(value_s) + 1; // length including terminating zero in bytes
size_t register_length_s = (length_s + 1) / 2; // length including terminating zero in uint16_t-registers
if ((long)result.size() < (long)register_length_s) {
return -5;
}
for (auto i = 0; i < register_length_s; i++) {
auto hi = (uint8_t)value_s[2 * i];
auto lo = (uint8_t)(2 * i + 1 < length_s ? value_s[2 * i + 1] : 0);
result[i] = ((uint16_t)hi << 8) | lo;
}
}
// handle ENUMs
else if (dv.type == DeviceValueType::ENUM) {
if (result.size() != 1)
return -6;
auto value_enum = *(uint8_t *)(dv.value_p);
if (value_enum >= dv.options_size)
return -7;
result[0] = (uint16_t)value_enum;
}
// handle Numbers
else if (dv.type == DeviceValueType::INT8) {
if (result.size() != 1)
return -8;
result[0] = (uint16_t)(uint8_t)(*(int8_t *)(dv.value_p));
} else if (dv.type == DeviceValueType::UINT8) {
if (result.size() != 1)
return -9;
result[0] = (uint16_t)(*(uint8_t *)(dv.value_p));
} else if (dv.type == DeviceValueType::INT16) {
if (result.size() != 1)
return -10;
result[0] = (uint16_t)(*(int16_t *)(dv.value_p));
} else if (dv.type == DeviceValueType::UINT16) {
if (result.size() != 1)
return -11;
result[0] = *(uint16_t *)(dv.value_p);
} else if (dv.type == DeviceValueType::UINT24 || dv.type == DeviceValueType::UINT32 || dv.type == DeviceValueType::TIME) {
if (result.size() != 2)
return -12;
auto value_uint32 = *(uint32_t *)(dv.value_p);
result[0] = (uint16_t)(value_uint32 >> 16);
result[1] = (uint16_t)(value_uint32 & 0xffff);
}
else {
return -13;
}
return 0;
}
bool EMSdevice::modbus_value_to_json(uint8_t tag, const std::string & shortname, const std::vector<uint8_t> & modbus_data, JsonObject & jsonValue) {
//Serial.printf("modbus_value_to_json(%d,%s,[%d bytes])\n", tag, shortname.c_str(), modbus_data.size());
// find device value by shortname
const auto & it = std::find_if(devicevalues_.begin(), devicevalues_.end(), [&](const DeviceValue & x) { return x.tag == tag && x.short_name == shortname; });
if (it == devicevalues_.end()) {
return false;
}
auto & dv = *it;
// handle Booleans
if (dv.type == DeviceValueType::BOOL) {
// bools are 1 16 bit register
if (modbus_data.size() != 2) {
return false;
}
jsonValue["value"] = modbus_data[0] || modbus_data[1];
}
// handle TEXT strings
else if (dv.type == DeviceValueType::STRING) {
// text is optionally nul terminated
// check if the data contains a null char
auto nul_or_end = std::find(modbus_data.begin(), modbus_data.end(), 0);
jsonValue["value"] = std::string(modbus_data.begin(), nul_or_end);
}
// handle ENUMs
else if (dv.type == DeviceValueType::ENUM) {
// these data types are 1 16 bit register
if (modbus_data.size() != 2) {
return false;
}
jsonValue["value"] = (uint16_t)modbus_data[0] << 8 | (uint16_t)modbus_data[1];
}
// handle Numbers
else if (dv.type == DeviceValueType::INT8 || dv.type == DeviceValueType::UINT8 || dv.type == DeviceValueType::INT16 || dv.type == DeviceValueType::UINT16) {
// these data types are 1 16 bit register
if (modbus_data.size() != 2) {
return false;
}
jsonValue["value"] = Helpers::numericoperator2scalefactor(dv.numeric_operator) * (float)((uint16_t)modbus_data[0] << 8 | (uint16_t)modbus_data[1]);
} else if (dv.type == DeviceValueType::UINT24 || dv.type == DeviceValueType::UINT32 || dv.type == DeviceValueType::TIME) {
// these data types are 2 16 bit register
if (modbus_data.size() != 4) {
return false;
}
jsonValue["value"] =
Helpers::numericoperator2scalefactor(dv.numeric_operator)
* (float)((uint32_t)modbus_data[0] << 24 | (uint32_t)modbus_data[1] << 16 | (uint32_t)modbus_data[2] << 8 | (uint32_t)modbus_data[3]);
}
else {
return false;
}
return true;
}
} // namespace emsesp

View File

@@ -202,6 +202,9 @@ class EMSdevice {
}
}
int get_modbus_value(uint8_t tag, const std::string & shortname, std::vector<uint16_t> & result);
bool modbus_value_to_json(uint8_t tag, const std::string & shortname, const std::vector<uint8_t> & modbus_data, JsonObject & jsonValue);
const char * brand_to_char();
const std::string to_string();
const std::string to_string_short();

View File

@@ -72,6 +72,7 @@ uuid::syslog::SyslogService System::syslog_;
RxService EMSESP::rxservice_; // incoming Telegram Rx handler
TxService EMSESP::txservice_; // outgoing Telegram Tx handler
Mqtt EMSESP::mqtt_; // mqtt handler
Modbus * EMSESP::modbus_; // modbus handler
System EMSESP::system_; // core system services
TemperatureSensor EMSESP::temperaturesensor_; // Temperature sensors
AnalogSensor EMSESP::analogsensor_; // Analog sensors
@@ -334,8 +335,8 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
void EMSESP::dump_all_values(uuid::console::Shell & shell) {
Serial.println("---- CSV START ----"); // marker use by py script
// add header for CSV
Serial.println(
"device name,device type,product id,shortname,fullname,type [options...] \\| (min/max),uom,writeable,discovery entityid v3.4, discovery entityid");
Serial.println("device name,device type,product id,shortname,fullname,type [options...] \\| (min/max),uom,writeable,discovery entityid v3.4,discovery "
"entityid,modbus unit identifier,modbus block,modbus scale factor,modbus offset,modbus count");
for (const auto & device_class : EMSFactory::device_handlers()) {
// go through each device type so they are sorted
@@ -1655,6 +1656,11 @@ void EMSESP::start() {
#endif
}
if (system_.modbus_enabled()) {
modbus_ = new Modbus;
modbus_->start(1, system_.modbus_port(), system_.modbus_max_clients(), system_.modbus_timeout());
}
mqtt_.start(); // mqtt init
system_.start(); // starts commands, led, adc, button, network (sets hostname), syslog & uart
shower_.start(); // initialize shower timer and shower alert

View File

@@ -56,6 +56,7 @@
#include "emsfactory.h"
#include "telegram.h"
#include "mqtt.h"
#include "modbus.h"
#include "system.h"
#include "temperaturesensor.h"
#include "analogsensor.h"
@@ -216,6 +217,7 @@ class EMSESP {
// services
static Mqtt mqtt_;
static Modbus * modbus_;
static System system_;
static TemperatureSensor temperaturesensor_;
static AnalogSensor analogsensor_;

View File

@@ -837,4 +837,13 @@ uint16_t Helpers::string2minutes(const std::string & str) {
}
}
float Helpers::numericoperator2scalefactor(uint8_t numeric_operator) {
if (numeric_operator == 0)
return 1.0f;
else if (numeric_operator > 0)
return 1.0f / numeric_operator;
else
return -numeric_operator;
}
} // namespace emsesp

View File

@@ -50,6 +50,7 @@ class Helpers {
static bool check_abs(const int32_t i);
static uint32_t abs(const int32_t i);
static uint16_t string2minutes(const std::string & str);
static float numericoperator2scalefactor(uint8_t numeric_operator);
static float transformNumFloat(float value, const int8_t numeric_operator, const uint8_t fahrenheit = 0);

View File

@@ -52,6 +52,7 @@ MAKE_WORD(ems)
MAKE_WORD(devices)
MAKE_WORD(shower)
MAKE_WORD(mqtt)
MAKE_WORD(modbus)
MAKE_WORD(emsesp)
MAKE_WORD(connected)
MAKE_WORD(disconnected)

509
src/modbus.cpp Normal file
View File

@@ -0,0 +1,509 @@
/**
* TODO:
* - verwendete libs in readme hinzufügen
*/
#include "modbus.h"
#include "modbus_entity_parameters.hpp"
#include "emsesp.h"
#include "emsdevice.h"
#include <string>
#include <sstream>
#include <iomanip>
namespace emsesp {
#ifdef EMSESP_STANDALONE
// no eModbus lib in standalone build
enum FunctionCode : uint8_t { WRITE_HOLD_REGISTER = 0x06, WRITE_MULT_REGISTERS = 0x10 };
#endif
uuid::log::Logger Modbus::logger_{F_(modbus), uuid::log::Facility::DAEMON};
void Modbus::start(uint8_t systemServerId, uint16_t port, uint8_t max_clients, uint32_t timeout) {
#ifndef EMSESP_STANDALONE
if (!check_parameter_order()) {
LOG_ERROR("Unable to enable Modbus - the parameter list order is corrupt. This is a firmware bug.");
return;
}
modbusServer_ = new ModbusServerTCPasync();
modbusServer_->registerWorker(systemServerId, READ_INPUT_REGISTER, [this](auto && request) { return handleSystemRead(request); });
for (uint8_t i = EMSdevice::DeviceType::BOILER; i < EMSdevice::DeviceType::UNKNOWN; i++) {
if (i != systemServerId) {
modbusServer_->registerWorker(i, READ_INPUT_REGISTER, [this](auto && request) { return handleRead(request); });
modbusServer_->registerWorker(i, READ_HOLD_REGISTER, [this](auto && request) { return handleRead(request); });
modbusServer_->registerWorker(i, WRITE_HOLD_REGISTER, [this](auto && request) { return handleWrite(request); });
modbusServer_->registerWorker(i, WRITE_MULT_REGISTERS, [this](auto && request) { return handleWrite(request); });
}
}
modbusServer_->start(port, max_clients, timeout);
LOG_INFO("Modbus server with ID %d started on port %d", systemServerId, port);
#else
if (!check_parameter_order()) {
LOG_ERROR("Unable to enable Modbus - the parameter list order is corrupt. This is a firmware bug.");
}
LOG_INFO("Modbus deactivated in standalone build.");
#endif
}
// this is currently never called, just for good measure
void Modbus::stop() {
#ifndef EMSESP_STANDALONE
modbusServer_->stop();
delete modbusServer_;
modbusServer_ = nullptr;
#endif
}
// Check that the Modbus parameters defined in modbus_entity_parameters.cpp are correctly ordered
bool Modbus::check_parameter_order() {
EntityModbusInfo const * prev = nullptr;
bool isFirst = true;
for (const auto & mi : modbus_register_mappings) {
if (isFirst) {
isFirst = false;
} else if (prev == nullptr || !prev->isLessThan(mi)) {
LOG_ERROR("Error in modbus parameters: %s must be listed before %s.", mi.short_name, prev->short_name);
return false;
}
prev = &mi;
}
return true;
}
int8_t Modbus::tag_to_type(int8_t tag) {
// this coulod even be an array
switch (tag) {
case DeviceValue::TAG_NONE:
return TAG_TYPE_NONE;
case DeviceValue::TAG_DEVICE_DATA:
return TAG_TYPE_DEVICE_DATA;
case DeviceValue::TAG_HC1:
return TAG_TYPE_HC;
case DeviceValue::TAG_HC2:
return TAG_TYPE_HC;
case DeviceValue::TAG_HC3:
return TAG_TYPE_HC;
case DeviceValue::TAG_HC4:
return TAG_TYPE_HC;
case DeviceValue::TAG_HC5:
return TAG_TYPE_HC;
case DeviceValue::TAG_HC6:
return TAG_TYPE_HC;
case DeviceValue::TAG_HC7:
return TAG_TYPE_HC;
case DeviceValue::TAG_HC8:
return TAG_TYPE_HC;
case DeviceValue::TAG_DHW1:
return TAG_TYPE_DHW;
case DeviceValue::TAG_DHW2:
return TAG_TYPE_DHW;
case DeviceValue::TAG_DHW3:
return TAG_TYPE_DHW;
case DeviceValue::TAG_DHW4:
return TAG_TYPE_DHW;
case DeviceValue::TAG_DHW5:
return TAG_TYPE_DHW;
case DeviceValue::TAG_DHW6:
return TAG_TYPE_DHW;
case DeviceValue::TAG_DHW7:
return TAG_TYPE_DHW;
case DeviceValue::TAG_DHW8:
return TAG_TYPE_DHW;
case DeviceValue::TAG_DHW9:
return TAG_TYPE_DHW;
case DeviceValue::TAG_DHW10:
return TAG_TYPE_DHW;
case DeviceValue::TAG_AHS1:
return TAG_TYPE_AHS;
case DeviceValue::TAG_HS1:
return TAG_TYPE_HS;
case DeviceValue::TAG_HS2:
return TAG_TYPE_HS;
case DeviceValue::TAG_HS3:
return TAG_TYPE_HS;
case DeviceValue::TAG_HS4:
return TAG_TYPE_HS;
case DeviceValue::TAG_HS5:
return TAG_TYPE_HS;
case DeviceValue::TAG_HS6:
return TAG_TYPE_HS;
case DeviceValue::TAG_HS7:
return TAG_TYPE_HS;
case DeviceValue::TAG_HS8:
return TAG_TYPE_HS;
case DeviceValue::TAG_HS9:
return TAG_TYPE_HS;
case DeviceValue::TAG_HS10:
return TAG_TYPE_HS;
case DeviceValue::TAG_HS11:
return TAG_TYPE_HS;
case DeviceValue::TAG_HS12:
return TAG_TYPE_HS;
case DeviceValue::TAG_HS13:
return TAG_TYPE_HS;
case DeviceValue::TAG_HS14:
return TAG_TYPE_HS;
case DeviceValue::TAG_HS15:
return TAG_TYPE_HS;
case DeviceValue::TAG_HS16:
return TAG_TYPE_HS;
default:
return INVALID_TAG_TYPE;
}
}
/* DEBUG
template <typename TInputIter>
std::string make_hex_string(TInputIter first, TInputIter last) {
std::ostringstream ss;
ss << std::hex << std::setfill('0');
while (first != last)
ss << std::setw(2) << static_cast<int>(*first++);
return ss.str();
}
*/
/**
*
* @param request
* @return
*/
ModbusMessage Modbus::handleSystemRead(const ModbusMessage & request) {
ModbusMessage response;
uint16_t start_address = 0;
uint16_t num_words = 0;
request.get(2, start_address);
request.get(4, num_words);
LOG_DEBUG("Got request for serverId %d, startAddress %d, numWords %d", request.getServerID(), start_address, num_words);
if (start_address < 1000) {
switch (start_address) {
case 1:
response.add(request.getServerID());
response.add(request.getFunctionCode());
response.add((uint8_t)2);
response.add((uint16_t)EMSESP::emsdevices.size());
break;
default:
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_ADDRESS);
}
} else if (start_address < 1000 + EMSESP::emsdevices.size()) {
const auto & dev = EMSESP::emsdevices[start_address - 1000];
response.add(request.getServerID());
response.add(request.getFunctionCode());
response.add((uint8_t)2);
response.add(dev->device_type());
response.add(dev->device_id());
} else {
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_ADDRESS);
}
return response;
}
ModbusMessage Modbus::handleRead(const ModbusMessage & request) {
ModbusMessage response;
uint8_t device_type = request.getServerID(); // the server ID is the same as the device type
uint16_t start_address = 0;
uint16_t num_words = 0;
request.get(2, start_address);
request.get(4, num_words);
LOG_DEBUG("Got read request for serverId %d, startAddress %d, numWords %d", device_type, start_address, num_words);
// each register block corresponds to a device value tag
auto tag = (uint8_t)(start_address / REGISTER_BLOCK_SIZE);
auto tag_type = tag_to_type(tag);
if (tag_type == INVALID_TAG_TYPE) {
// invalid register block, does not correspond to an existing tag
LOG_ERROR("invalid register block, does not correspond to an existing tag");
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_ADDRESS);
return response;
}
auto register_offset = start_address - tag * REGISTER_BLOCK_SIZE;
const auto & dev_it =
std::find_if(EMSESP::emsdevices.begin(), EMSESP::emsdevices.end(), [&](const std::unique_ptr<EMSdevice> & x) { return x->device_type() == device_type; });
if (dev_it == EMSESP::emsdevices.end()) {
// device not found => invalid server ID
LOG_ERROR("device with type %d not found => invalid server ID", device_type);
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_ADDRESS);
return response;
}
const auto & dev = *dev_it;
// binary search in modbus infos
auto key = EntityModbusInfoKey(dev->device_type(), tag_type, register_offset);
const auto & modbusInfo = std::lower_bound(std::begin(modbus_register_mappings),
std::end(modbus_register_mappings),
key,
[](const EntityModbusInfo & a, const EntityModbusInfoKey & b) { return a.isLessThan(b); });
if (modbusInfo == std::end(modbus_register_mappings) || !modbusInfo->equals(key)) {
// combination of device_type/tag_type/register_offset does not exist
LOG_ERROR("combination of device_type/tag_type/register_offset does not exist");
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_ADDRESS);
return response;
}
// only reading a single value at a time is supported for now
if (num_words != modbusInfo->registerCount) {
// number of registers requested does not match actual register count for entity
LOG_ERROR("number of registers requested does not match actual register count for entity");
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_ADDRESS);
return response;
}
auto buf = std::vector<uint16_t>(num_words);
if (dev->get_modbus_value(tag, modbusInfo->short_name, buf) == 0) {
response.add(request.getServerID());
response.add(request.getFunctionCode());
response.add((uint8_t)(num_words * 2));
for (auto & value : buf)
response.add(value);
} else {
LOG_ERROR("Unable to read raw device value %s for tag=%d", modbusInfo->short_name, (int)tag);
response.setError(request.getServerID(), request.getFunctionCode(), SERVER_DEVICE_FAILURE);
}
return response;
}
ModbusMessage Modbus::handleWrite(const ModbusMessage & request) {
ModbusMessage response;
uint8_t device_type = request.getServerID(); // the server ID is the same as the device type
uint8_t function_code = request.getFunctionCode();
uint16_t start_address = 0;
uint16_t num_words = 0;
uint8_t byte_count = 0;
std::vector<uint8_t> data;
if (function_code == WRITE_MULT_REGISTERS) {
request.get(2, start_address);
request.get(4, num_words);
request.get(6, byte_count);
request.get(7, data, byte_count);
} else if (function_code == WRITE_HOLD_REGISTER) {
num_words = 1;
byte_count = 2;
request.get(2, start_address);
request.get(4, data, byte_count);
} else {
LOG_ERROR("Function code %d is not implemented", function_code);
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_FUNCTION);
return response;
}
LOG_DEBUG("Got write request for serverId %d, startAddress %d, numWords %d, byteCount %d", device_type, start_address, num_words, byte_count);
// each register block corresponds to a device value tag
auto tag = (uint8_t)(start_address / REGISTER_BLOCK_SIZE);
auto tag_type = tag_to_type(tag);
if (tag_type == INVALID_TAG_TYPE) {
// invalid register block, does not correspond to an existing tag
LOG_ERROR("invalid register block (%d), does not correspond to an existing tag", tag);
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_ADDRESS);
return response;
}
auto register_offset = start_address - tag * REGISTER_BLOCK_SIZE;
const auto & dev_it =
std::find_if(EMSESP::emsdevices.begin(), EMSESP::emsdevices.end(), [&](const std::unique_ptr<EMSdevice> & x) { return x->device_type() == device_type; });
if (dev_it == EMSESP::emsdevices.end()) {
// device not found => invalid server ID
LOG_ERROR("device_type (%d) not found => invalid server ID", device_type);
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_ADDRESS);
return response;
}
const auto & dev = *dev_it;
// binary search in modbus infos
auto key = EntityModbusInfoKey(dev->device_type(), tag_type, register_offset);
auto modbusInfo = std::lower_bound(std::begin(modbus_register_mappings),
std::end(modbus_register_mappings),
key,
[](const EntityModbusInfo & mi, const EntityModbusInfoKey & k) { return mi.isLessThan(k); });
if (modbusInfo == std::end(modbus_register_mappings) || !modbusInfo->equals(key)) {
// combination of device_type/tag_type/register_offset does not exist
LOG_ERROR("combination of device_type (%d)/tag_type (%d)/register_offset (%d) does not exist",
key.device_type,
key.device_value_tag_type,
key.registerOffset);
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_ADDRESS);
return response;
}
// only writing a single value at a time is supported for now
if (num_words != modbusInfo->registerCount) {
// number of registers requested does not match actual register count for entity
LOG_ERROR("number of registers (%d) requested does not match actual register count (%d) for entity", num_words, modbusInfo->registerCount);
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_ADDRESS);
return response;
}
JsonDocument input_doc;
JsonObject input = input_doc.to<JsonObject>();
if (!dev->modbus_value_to_json(tag, modbusInfo->short_name, data, input)) {
// error getting modbus value as json
LOG_ERROR("error getting modbus value as json");
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_ADDRESS);
return response;
}
std::string path;
if (tag < DeviceValueTAG::TAG_HC1) {
path = std::string("ems-esp/") + std::string(EMSdevice::device_type_2_device_name(dev->device_type())) + "/" + modbusInfo->short_name;
} else {
path = std::string("ems-esp/") + std::string(EMSdevice::device_type_2_device_name(dev->device_type())) + "/" + EMSdevice::tag_to_mqtt(tag) + "/"
+ modbusInfo->short_name;
}
LOG_DEBUG("path: %s\n", path.c_str());
std::string inputStr;
serializeJson(input, inputStr);
LOG_DEBUG("input: %s\n", inputStr.c_str());
JsonDocument output_doc;
JsonObject output = output_doc.to<JsonObject>();
uint8_t return_code = Command::process(path.c_str(), true, input, output); // modbus is always authenticated
if (return_code != CommandRet::OK) {
char error[100];
if (output.size()) {
snprintf(error,
sizeof(error),
"Modbus write command failed with error: %s (%s)",
(const char *)output["message"],
Command::return_code_string(return_code).c_str());
} else {
snprintf(error, sizeof(error), "Modbus write command failed with error code (%s)", Command::return_code_string(return_code).c_str());
}
LOG_ERROR(error);
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_VALUE);
} else {
// all good
response.add(request.getServerID());
response.add(request.getFunctionCode());
response.add(start_address);
response.add(num_words);
}
return response;
}
#if defined(EMSESP_STANDALONE)
// return the relative register start offset for a DeviceValue, i.e. the address within the
// register block corresponding to the value's tag type.
int Modbus::getRegisterOffset(const DeviceValue & dv) {
auto it = std::find_if(std::begin(modbus_register_mappings), std::end(modbus_register_mappings), [&](const EntityModbusInfo & mi) {
return mi.device_type == dv.device_type && mi.device_value_tag_type == tag_to_type(dv.tag) && !strcmp(mi.short_name, dv.short_name);
});
if (it != std::end(modbus_register_mappings)) {
return it->registerOffset;
}
return -1;
}
// return the number of registers
int Modbus::getRegisterCount(const DeviceValue & dv) {
auto it = std::find_if(std::begin(modbus_register_mappings), std::end(modbus_register_mappings), [&](const EntityModbusInfo & mi) {
return mi.device_type == dv.device_type && mi.device_value_tag_type == tag_to_type(dv.tag) && !strcmp(mi.short_name, dv.short_name);
});
if (it != std::end(modbus_register_mappings)) {
// look up actual size
return it->registerCount;
} else {
// guess based on type
switch (dv.type) {
case DeviceValue::BOOL: // 8 bit
case DeviceValue::INT8:
case DeviceValue::UINT8:
case DeviceValue::INT16:
case DeviceValue::UINT16:
case DeviceValue::ENUM: // 8 bit
case DeviceValue::CMD:
return 1;
case DeviceValue::UINT24:
case DeviceValue::UINT32:
case DeviceValue::TIME: // 32 bit
return 2;
case DeviceValue::STRING:
break; // impossible to guess, needs to be hardcoded
}
}
return 0;
}
// return the absolute register start address for a DeviceValue
int Modbus::getRegisterStartAddress(const DeviceValue & dv) {
return dv.tag * REGISTER_BLOCK_SIZE + getRegisterOffset(dv);
}
#endif
} // namespace emsesp

112
src/modbus.h Normal file
View File

@@ -0,0 +1,112 @@
//
// Created by Michael Heyse on 08.02.24.
//
#ifndef EMSESP_MODBUS_H_
#define EMSESP_MODBUS_H_
#include "helpers.h"
#include "emsdevice.h"
#include "emsdevicevalue.h"
#include <string>
#include <map>
#include <utility>
#if defined(EMSESP_STANDALONE) || defined(EMSESP_TEST)
#include <modbus_test.h>
#endif
#ifndef EMSESP_STANDALONE
#include <ModbusServerTCPasync.h>
#endif
namespace emsesp {
class Modbus {
public:
static const int REGISTER_BLOCK_SIZE = 1000;
void start(uint8_t systemServerId, uint16_t port, uint8_t max_clients, uint32_t timeout);
void stop();
#if defined(EMSESP_STANDALONE)
int getRegisterOffset(const DeviceValue & dv);
int getRegisterCount(const DeviceValue & dv);
int getRegisterStartAddress(const DeviceValue & dv);
#endif
private:
static uuid::log::Logger logger_;
struct EntityModbusInfoKey {
const uint8_t device_type;
const uint8_t device_value_tag_type;
const uint16_t registerOffset;
EntityModbusInfoKey(uint8_t deviceType, uint8_t deviceValueTagType, uint16_t registerOffset)
: device_type(deviceType)
, device_value_tag_type(deviceValueTagType)
, registerOffset(registerOffset) {
}
bool equals(const EntityModbusInfoKey & other) const {
return device_type == other.device_type && device_value_tag_type == other.device_value_tag_type && registerOffset == other.registerOffset;
}
};
struct EntityModbusInfo {
const uint8_t device_type;
const uint8_t device_value_tag_type;
const char * const short_name;
const uint16_t registerOffset;
const uint16_t registerCount;
bool equals(const EntityModbusInfoKey & other) const {
return device_type == other.device_type && device_value_tag_type == other.device_value_tag_type && registerOffset == other.registerOffset;
}
bool isLessThan(const EntityModbusInfoKey & other) const {
return device_type < other.device_type || ((device_type == other.device_type) && (device_value_tag_type < other.device_value_tag_type))
|| ((device_type == other.device_type) && (device_value_tag_type == other.device_value_tag_type) && (registerOffset < other.registerOffset));
}
bool isLessThan(const EntityModbusInfo & other) const {
return device_type < other.device_type || ((device_type == other.device_type) && (device_value_tag_type < other.device_value_tag_type))
|| ((device_type == other.device_type) && (device_value_tag_type == other.device_value_tag_type) && (registerOffset < other.registerOffset));
}
};
enum DeviceValueTAGType : int8_t {
TAG_TYPE_NONE = DeviceValue::DeviceValueTAG::TAG_NONE,
TAG_TYPE_DEVICE_DATA = DeviceValue::DeviceValueTAG::TAG_DEVICE_DATA,
TAG_TYPE_HC = DeviceValue::DeviceValueTAG::TAG_HC1,
TAG_TYPE_DHW = DeviceValue::DeviceValueTAG::TAG_DHW1,
TAG_TYPE_AHS = DeviceValue::DeviceValueTAG::TAG_AHS1,
TAG_TYPE_HS = DeviceValue::DeviceValueTAG::TAG_HS1,
INVALID_TAG_TYPE = -2
};
static const std::initializer_list<EntityModbusInfo> modbus_register_mappings;
static int8_t tag_to_type(int8_t tag);
static bool check_parameter_order();
#ifndef EMSESP_STANDALONE
ModbusServerTCPasync * modbusServer_;
#endif
#if defined(EMSESP_STANDALONE) || defined(EMSESP_TEST)
public:
#endif
static ModbusMessage handleSystemRead(const ModbusMessage & request);
static ModbusMessage handleRead(const ModbusMessage & request);
static ModbusMessage handleWrite(const ModbusMessage & request);
};
} // namespace emsesp
#endif //EMSESP_MODBUS_H_

View File

@@ -0,0 +1,515 @@
#include "modbus.h"
#include "emsdevice.h"
namespace emsesp {
using dt = EMSdevice::DeviceType;
#define REGISTER_MAPPING(device_type, device_value_tag_type, long_name, modbus_register_offset, modbus_register_count) \
{ device_type, device_value_tag_type, long_name[0], modbus_register_offset, modbus_register_count }
// IMPORTANT: This list MUST be ordered by keys "device_type", "device_value_tag_type" and "modbus_register_offset" in this order.
const std::initializer_list<Modbus::EntityModbusInfo> Modbus::modbus_register_mappings = {
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(reset), 0, 1), // reset
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(forceHeatingOff), 1, 1), // heatingoff
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatingActive), 2, 1), // heatingactive
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(tapwaterActive), 3, 1), // tapwateractive
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(selFlowTemp), 4, 1), // selflowtemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatingPumpMod), 5, 1), // heatingpumpmod
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(outdoorTemp), 6, 1), // outdoortemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(curFlowTemp), 7, 1), // curflowtemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(retTemp), 8, 1), // rettemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(switchTemp), 9, 1), // switchtemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(sysPress), 10, 1), // syspress
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boilTemp), 11, 1), // boiltemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(headertemp), 12, 1), // headertemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatingActivated), 13, 1), // heatingactivated
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatingTemp), 14, 1), // heatingtemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatingPump), 15, 1), // heatingpump
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pumpModMax), 16, 1), // pumpmodmax
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pumpModMin), 17, 1), // pumpmodmin
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pumpMode), 18, 1), // pumpmode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pumpCharacter), 19, 1), // pumpcharacter
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pumpDelay), 20, 1), // pumpdelay
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(setFlowTemp), 21, 1), // setflowtemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(setBurnPow), 22, 1), // setburnpow
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(selBurnPow), 23, 1), // selburnpow
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(curBurnPow), 24, 1), // curburnpow
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnStarts), 25, 2), // burnstarts
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnWorkMin), 27, 2), // burnworkmin
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burn2WorkMin), 29, 2), // burn2workmin
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatWorkMin), 31, 2), // heatworkmin
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatStarts), 33, 2), // heatstarts
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(UBAuptime), 35, 2), // ubauptime
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(lastCode), 37, 28), // lastcode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(serviceCode), 65, 2), // servicecode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(serviceCodeNumber), 67, 1), // servicecodenumber
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(maintenanceMessage), 68, 2), // maintenancemessage
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(maintenanceType), 70, 1), // maintenance
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(maintenanceTime), 71, 1), // maintenancetime
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(maintenanceDate), 72, 6), // maintenancedate
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(emergencyOps), 78, 1), // emergencyops
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(emergencyTemp), 79, 1), // emergencytemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgTotal), 80, 2), // nrgtotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgHeat), 82, 2), // nrgheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(meterTotal), 84, 2), // metertotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(meterComp), 86, 2), // metercomp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(meterEHeat), 88, 2), // metereheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(meterHeat), 90, 2), // meterheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(upTimeTotal), 92, 2), // uptimetotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(upTimeControl), 94, 2), // uptimecontrol
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(upTimeCompHeating), 96, 2), // uptimecompheating
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(upTimeCompCooling), 98, 2), // uptimecompcooling
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(upTimeCompPool), 100, 2), // uptimecomppool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(totalCompStarts), 102, 2), // totalcompstarts
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatingStarts), 104, 2), // heatingstarts
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(coolingStarts), 106, 2), // coolingstarts
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(poolStarts), 108, 2), // poolstarts
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgConsTotal), 110, 2), // nrgconstotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgConsCompTotal), 112, 2), // nrgconscomptotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgConsCompHeating), 114, 2), // nrgconscompheating
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgConsCompCooling), 116, 2), // nrgconscompcooling
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgConsCompPool), 118, 2), // nrgconscomppool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxElecHeatNrgConsTotal), 120, 2), // auxelecheatnrgconstotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxElecHeatNrgConsHeating), 122, 2), // auxelecheatnrgconsheating
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxElecHeatNrgConsPool), 124, 2), // auxelecheatnrgconspool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgSuppTotal), 126, 2), // nrgsupptotal
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgSuppHeating), 128, 2), // nrgsuppheating
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgSuppCooling), 130, 2), // nrgsuppcooling
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgSuppPool), 132, 2), // nrgsupppool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpPower), 134, 1), // hppower
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpMaxPower), 135, 1), // hpmaxpower
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpSetDiffPress), 136, 1), // hpsetdiffpress
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpCompOn), 137, 1), // hpcompon
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpActivity), 138, 1), // hpactivity
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpBrinePumpSpd), 139, 1), // hpbrinepumpspd
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpSwitchValve), 140, 1), // hpswitchvalve
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpCompSpd), 141, 1), // hpcompspd
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpCircSpd), 142, 1), // hpcircspd
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpBrineIn), 143, 1), // hpbrinein
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpBrineOut), 144, 1), // hpbrineout
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTc0), 145, 1), // hptc0
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTc1), 146, 1), // hptc1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTc3), 147, 1), // hptc3
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr1), 148, 1), // hptr1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr3), 149, 1), // hptr3
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr4), 150, 1), // hptr4
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr5), 151, 1), // hptr5
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr6), 152, 1), // hptr6
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTr7), 153, 1), // hptr7
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTl2), 154, 1), // hptl2
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpPl1), 155, 1), // hppl1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpPh1), 156, 1), // hpph1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTa4), 157, 1), // hpta4
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpTw1), 158, 1), // hptw1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(poolSetTemp), 159, 1), // poolsettemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hp4wayValve), 160, 1), // hp4way
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpInput1), 161, 1), // hpin1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpIn1Opt), 162, 8), // hpin1opt
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpInput2), 170, 1), // hpin2
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpIn2Opt), 171, 8), // hpin2opt
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpInput3), 179, 1), // hpin3
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpIn3Opt), 180, 8), // hpin3opt
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpInput4), 188, 1), // hpin4
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpIn4Opt), 189, 8), // hpin4opt
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(maxHeatComp), 197, 1), // maxheatcomp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(maxHeatHeat), 198, 1), // maxheatheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(manDefrost), 199, 1), // mandefrost
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(pvCooling), 200, 1), // pvcooling
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeaterOnly), 201, 1), // auxheateronly
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeaterOff), 202, 1), // auxheateroff
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeaterStatus), 203, 1), // auxheaterstatus
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeaterDelay), 204, 1), // auxheaterdelay
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxMaxLimit), 205, 1), // auxmaxlimit
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxLimitStart), 206, 1), // auxlimitstart
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeatMode), 207, 1), // auxheatrmode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpHystHeat), 208, 1), // hphystheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpHystCool), 209, 1), // hphystcool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpHystPool), 210, 1), // hphystpool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(silentMode), 211, 1), // silentmode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(silentFrom), 212, 1), // silentfrom
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(silentTo), 213, 1), // silentto
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(minTempSilent), 214, 1), // mintempsilent
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(tempParMode), 215, 1), // tempparmode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(auxHeatMixValve), 216, 1), // auxheatmix
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(tempDiffHeat), 217, 1), // tempdiffheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(tempDiffCool), 218, 1), // tempdiffcool
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(vp_cooling), 219, 1), // vpcooling
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatCable), 220, 1), // heatcable
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(VC0valve), 221, 1), // vc0valve
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(primePump), 222, 1), // primepump
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(primePumpMod), 223, 1), // primepumpmod
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hp3wayValve), 224, 1), // hp3way
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(elHeatStep1), 225, 1), // elheatstep1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(elHeatStep2), 226, 1), // elheatstep2
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(elHeatStep3), 227, 1), // elheatstep3
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpEA0), 228, 1), // hpea0
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpPumpMode), 229, 1), // hppumpmode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpFan), 230, 1), // fan
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(hpShutdown), 231, 1), // shutdown
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(exhaustTemp), 232, 1), // exhausttemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnGas), 233, 1), // burngas
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnGas2), 234, 1), // burngas2
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(flameCurr), 235, 1), // flamecurr
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(fanWork), 236, 1), // fanwork
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(ignWork), 237, 1), // ignwork
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(oilPreHeat), 238, 1), // oilpreheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnMinPower), 239, 1), // burnminpower
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnMaxPower), 240, 1), // burnmaxpower
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnMinPeriod), 241, 1), // burnminperiod
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(absBurnPow), 242, 1), // absburnpow
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatblock), 243, 1), // heatblock
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boilHystOn), 244, 1), // boilhyston
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boilHystOff), 245, 1), // boilhystoff
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boil2HystOn), 246, 1), // boil2hyston
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boil2HystOff), 247, 1), // boil2hystoff
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(curveOn), 248, 1), // curveon
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(curveBase), 249, 1), // curvebase
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(curveEnd), 250, 1), // curveend
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(summertemp), 251, 1), // summertemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nofrostmode), 252, 1), // nofrostmode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nofrosttemp), 253, 1), // nofrosttemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(gasMeterHeat), 254, 2), // gasmeterheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgHeat2), 256, 2), // nrgheat2
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nomPower), 258, 1), // nompower
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(netFlowTemp), 259, 1), // netflowtemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatValve), 260, 1), // heatvalve
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(keepWarmTemp), 261, 1), // keepwarmtemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(setReturnTemp), 262, 1), // setreturntemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatingOn), 263, 1), // heating
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(nrgWw), 0, 2), // nrg
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(meterWw), 2, 2), // meter
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(upTimeCompWw), 4, 2), // uptimecomp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwStarts2), 6, 2), // starts2
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(nrgConsCompWw), 8, 2), // nrgconscomp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(auxElecHeatNrgConsWw), 10, 2), // auxelecheatnrgcons
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(nrgSuppWw), 12, 2), // nrgsupp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(maxHeatDhw), 14, 1), // maxheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwAlternatingOper), 15, 1), // alternatingop
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwAltOpPrioHeat), 16, 1), // altopprioheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwAltOpPrioWw), 17, 1), // altopprio
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwComfOffTemp), 18, 1), // comfoff
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwEcoOffTemp), 19, 1), // ecooff
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwEcoPlusOffTemp), 20, 1), // ecoplusoff
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwComfDiffTemp), 21, 1), // comfdiff
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwEcoDiffTemp), 22, 1), // ecodiff
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwEcoPlusDiffTemp), 23, 1), // ecoplusdiff
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwComfStopTemp), 24, 1), // comfstop
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwEcoStopTemp), 25, 1), // ecostop
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwEcoPlusStopTemp), 26, 1), // ecoplusstop
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(hpCircPumpWw), 27, 1), // hpcircpump
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwtapactivated), 28, 1), // tapactivated
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwSetTemp), 29, 1), // settemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(selRoomTemp), 30, 1), // seltemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwSelTempLow), 31, 1), // seltemplow
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwSelTempEco), 32, 1), // tempecoplus
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwSelTempOff), 33, 1), // seltempoff
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwSelTempSingle), 34, 1), // seltempsingle
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwSolarTemp), 35, 1), // solartemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwType), 36, 1), // type
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwComfort), 37, 1), // comfort
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwComfort1), 38, 1), // comfort1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(flowtempoffset), 39, 1), // flowtempoffset
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwChargeOptimization), 40, 1), // chargeoptimization
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwMaxPower), 41, 1), // maxpower
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwMaxTemp), 42, 1), // maxtemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwCircPump), 43, 1), // circpump
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwChargeType), 44, 1), // chargetype
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwHystOn), 45, 1), // hyston
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwHystOff), 46, 1), // hystoff
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwDisinfectionTemp), 47, 1), // disinfectiontemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwCircMode), 48, 1), // circmode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwCirc), 49, 1), // circ
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwCurTemp), 50, 1), // curtemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwCurTemp2), 51, 1), // curtemp2
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwCurFlow), 52, 1), // curflow
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwStorageTemp1), 53, 1), // storagetemp1
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwStorageTemp2), 54, 1), // storagetemp2
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(activated), 55, 1), // activated
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwOneTime), 56, 1), // onetime
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwDisinfecting), 57, 1), // disinfecting
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwCharging), 58, 1), // charging
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwRecharging), 59, 1), // recharging
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwTempOK), 60, 1), // tempok
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwActive), 61, 1), // active
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(ww3wayValve), 62, 1), // 3wayvalve
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwSetPumpPower), 63, 1), // setpumppower
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwMixerTemp), 64, 1), // mixertemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(cylMiddleTemp), 65, 1), // cylmiddletemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwStarts), 66, 2), // starts
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwWorkM), 68, 2), // workm
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(gasMeterWw), 70, 2), // gasmeter
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(nrgWw2), 72, 2), // nrg2
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(wwValve), 74, 1), // dhwvalve
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(errorCode), 0, 8), // errorcode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(lastCode), 8, 25), // lastcode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(dateTime), 33, 13), // datetime
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(ibaCalIntTemperature), 46, 1), // intoffset
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(floordrystatus), 47, 1), // floordry
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(dampedoutdoortemp), 48, 1), // dampedoutdoortemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(floordrytemp), 49, 1), // floordrytemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(ibaBuildingType), 50, 1), // building
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(ibaMinExtTemperature), 51, 1), // minexttemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(damping), 52, 1), // damping
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(hybridStrategy), 53, 1), // hybridstrategy
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(switchOverTemp), 54, 1), // switchovertemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(energyCostRatio), 55, 1), // energycostratio
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(fossileFactor), 56, 1), // fossilefactor
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(electricFactor), 57, 1), // electricfactor
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(delayBoiler), 58, 1), // delayboiler
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(tempDiffBoiler), 59, 1), // tempdiffboiler
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(pvEnableWw), 60, 1), // pvenabledhw
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(pvRaiseHeat), 61, 1), // pvraiseheat
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(pvLowerCool), 62, 1), // pvlowercool
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(ibaMainDisplay), 63, 1), // display
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(ibaLanguage), 64, 1), // language
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(ibaClockOffset), 65, 1), // clockoffset
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(tempsensor1), 66, 1), // inttemp1
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(tempsensor2), 67, 1), // inttemp2
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(autodst), 68, 1), // autodst
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(backlight), 69, 1), // backlight
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(brightness), 70, 1), // brightness
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(mixingvalves), 71, 1), // mixingvalves
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(heatingPID), 72, 1), // heatingpid
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(preheating), 73, 1), // preheating
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(vacations), 74, 13), // vacations
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(selRoomTemp), 0, 1), // seltemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(roomTemp), 1, 1), // currtemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(haclimate), 2, 1), // haclimate
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(mode), 3, 1), // mode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(modetype), 4, 1), // modetype
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(ecotemp), 5, 1), // ecotemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(manualtemp), 6, 1), // manualtemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(comforttemp), 7, 1), // comforttemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(summertemp), 8, 1), // summertemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(designtemp), 9, 1), // designtemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(offsettemp), 10, 1), // offsettemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(minflowtemp), 11, 1), // minflowtemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(maxflowtemp), 12, 1), // maxflowtemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(roominfluence), 13, 1), // roominfluence
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(roominfl_factor), 14, 1), // roominflfactor
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(curroominfl), 15, 1), // curroominfl
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(nofrostmode), 16, 1), // nofrostmode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(nofrosttemp), 17, 1), // nofrosttemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(targetflowtemp), 18, 1), // targetflowtemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(heatingtype), 19, 1), // heatingtype
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(summersetmode), 20, 1), // summersetmode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(hpoperatingmode), 21, 1), // hpoperatingmode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(summermode), 22, 1), // summermode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(hpoperatingstate), 23, 1), // hpoperatingstate
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(controlmode), 24, 1), // controlmode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(program), 25, 1), // program
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(tempautotemp), 26, 1), // tempautotemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(remoteseltemp), 27, 1), // remoteseltemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(fastheatup), 28, 1), // fastheatup
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(switchonoptimization), 29, 1), // switchonoptimization
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(reducemode), 30, 1), // reducemode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(noreducetemp), 31, 1), // noreducetemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(reducetemp), 32, 1), // reducetemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(wwprio), 33, 1), // dhwprio
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(hpcooling), 34, 1), // cooling
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(coolingOn), 35, 1), // coolingon
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(hpmode), 36, 1), // hpmode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(dewoffset), 37, 1), // dewoffset
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(roomtempdiff), 38, 1), // roomtempdiff
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(hpminflowtemp), 39, 1), // hpminflowtemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(control), 40, 1), // control
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(remotetemp), 41, 1), // remotetemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(remotehum), 42, 1), // remotehum
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(heatondelay), 43, 1), // heatondelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(heatoffdelay), 44, 1), // heatoffdelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(instantstart), 45, 1), // instantstart
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(boost), 46, 1), // boost
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(boosttime), 47, 1), // boosttime
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(daytemp), 48, 1), // daytemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(nighttemp2), 49, 1), // nighttemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(holidaytemp), 50, 1), // holidaytemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(holidaymode), 51, 1), // holidaymode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(flowtempoffset), 52, 1), // flowtempoffset
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(holidays), 53, 13), // holidays
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations), 66, 13), // vacations
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(pause), 79, 1), // pause
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(party), 80, 1), // party
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacreducetemp), 81, 1), // vacreducetemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacreducemode), 82, 1), // vacreducemode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(switchtime1), 83, 8), // switchtime1
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(switchtime2), 91, 8), // switchtime2
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(offtemp), 99, 1), // offtemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(daylowtemp), 100, 1), // daytemp2
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(daymidtemp), 101, 1), // daytemp3
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(dayhightemp), 102, 1), // daytemp4
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(wwswitchtime), 103, 8), // switchtime
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations1), 111, 11), // vacations1
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations2), 122, 11), // vacations2
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations3), 133, 11), // vacations3
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations4), 144, 11), // vacations4
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations5), 155, 11), // vacations5
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations6), 166, 11), // vacations6
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(vacations7), 177, 11), // vacations7
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(reducehours), 188, 1), // reducehours
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(reduceminutes), 189, 1), // reduceminutes
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(heattemp), 190, 1), // heattemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(roomsensor), 191, 1), // roomsensor
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(heatup), 192, 1), // heatup
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(mode), 0, 1), // mode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwSetTemp), 1, 1), // settemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwSetTempLow), 2, 1), // settemplow
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwCircMode), 3, 1), // circmode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwChargeDuration), 4, 1), // chargeduration
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwCharge), 5, 1), // charge
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwExtra), 6, 1), // extra
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDisinfecting), 7, 1), // disinfecting
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDisinfectDay), 8, 1), // disinfectday
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDisinfectTime), 9, 1), // disinfecttime
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDailyHeating), 10, 1), // dailyheating
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDailyHeatTime), 11, 1), // dailyheattime
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwProgMode), 12, 1), // progmode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwCircProg), 13, 1), // circprog
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDisinfectHour), 14, 1), // disinfecthour
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwMaxTemp), 15, 1), // maxtemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwOneTimeKey), 16, 1), // onetimekey
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwswitchtime), 17, 8), // switchtime
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwcircswitchtime), 25, 8), // circswitchtime
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(holidays), 33, 13), // holidays
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(vacations), 46, 13), // vacations
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwWhenModeOff), 59, 1), // whenmodeoff
REGISTER_MAPPING(dt::MIXER, TAG_TYPE_HC, FL_(flowTempHc), 0, 1), // flowtemphc
REGISTER_MAPPING(dt::MIXER, TAG_TYPE_HC, FL_(valveStatus), 1, 1), // valvestatus
REGISTER_MAPPING(dt::MIXER, TAG_TYPE_HC, FL_(flowSetTemp), 2, 1), // flowsettemp
REGISTER_MAPPING(dt::MIXER, TAG_TYPE_HC, FL_(wwPumpStatus), 3, 1), // pumpstatus
REGISTER_MAPPING(dt::MIXER, TAG_TYPE_HC, FL_(activated), 4, 1), // activated
REGISTER_MAPPING(dt::MIXER, TAG_TYPE_HC, FL_(mixerSetTime), 5, 1), // valvesettime
REGISTER_MAPPING(dt::MIXER, TAG_TYPE_HC, FL_(flowTempVf), 6, 1), // flowtempvf
REGISTER_MAPPING(dt::MIXER, TAG_TYPE_HC, FL_(flowtempoffset), 7, 1), // flowtempoffset
REGISTER_MAPPING(dt::MIXER, TAG_TYPE_DHW, FL_(flowTempHc), 0, 1), // flowtemphc
REGISTER_MAPPING(dt::MIXER, TAG_TYPE_DHW, FL_(valveStatus), 1, 1), // valvestatus
REGISTER_MAPPING(dt::MIXER, TAG_TYPE_DHW, FL_(flowSetTemp), 2, 1), // flowsettemp
REGISTER_MAPPING(dt::MIXER, TAG_TYPE_DHW, FL_(wwPumpStatus), 3, 1), // pumpstatus
REGISTER_MAPPING(dt::MIXER, TAG_TYPE_DHW, FL_(activated), 4, 1), // activated
REGISTER_MAPPING(dt::MIXER, TAG_TYPE_DHW, FL_(mixerSetTime), 5, 1), // valvesettime
REGISTER_MAPPING(dt::MIXER, TAG_TYPE_DHW, FL_(flowtempoffset), 6, 1), // flowtempoffset
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(collectorTemp), 0, 1), // collectortemp
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(cylBottomTemp), 1, 1), // cylbottomtemp
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(solarPump), 2, 1), // solarpump
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(pumpWorkTime), 3, 2), // pumpworktime
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(cylMaxTemp), 5, 1), // cylmaxtemp
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(collectorShutdown), 6, 1), // collectorshutdown
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(cylHeated), 7, 1), // cylheated
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(solarPumpMod), 8, 1), // solarpumpmod
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(pumpMinMod), 9, 1), // pumpminmod
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(solarPumpTurnonDiff), 10, 1), // turnondiff
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(solarPumpTurnoffDiff), 11, 1), // turnoffdiff
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(solarPower), 12, 1), // solarpower
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(energyLastHour), 13, 2), // energylasthour
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(maxFlow), 15, 1), // maxflow
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(solarIsEnabled), 16, 1), // solarenabled
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(cylMiddleTemp), 17, 1), // cylmiddletemp
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(retHeatAssist), 18, 1), // retheatassist
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(m1Valve), 19, 1), // heatassistvalve
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(collector2Temp), 20, 1), // collector2temp
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(m1Power), 21, 1), // heatassistpower
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(solarPump2), 22, 1), // solarpump2
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(solarPump2Mod), 23, 1), // solarpump2mod
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(cyl2BottomTemp), 24, 1), // cyl2bottomtemp
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(heatExchangerTemp), 25, 1), // heatexchangertemp
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(cylPumpMod), 26, 1), // cylpumpmod
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(valveStatus), 27, 1), // valvestatus
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(vs1Status), 28, 1), // vs1status
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(collectorMaxTemp), 29, 1), // collectormaxtemp
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(collectorMinTemp), 30, 1), // collectormintemp
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(energyToday), 31, 2), // energytoday
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(energyTotal), 33, 2), // energytotal
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(pump2WorkTime), 35, 2), // pump2worktime
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(m1WorkTime), 37, 2), // m1worktime
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(heatTransferSystem), 39, 1), // heattransfersystem
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(externalCyl), 40, 1), // externalcyl
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(thermalDisinfect), 41, 1), // thermaldisinfect
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(heatMetering), 42, 1), // heatmetering
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(activated), 43, 1), // activated
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(solarPumpMode), 44, 1), // solarpumpmode
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(solarPumpKick), 45, 1), // pumpkick
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(plainWaterMode), 46, 1), // plainwatermode
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(doubleMatchFlow), 47, 1), // doublematchflow
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(pump2MinMod), 48, 1), // pump2minmod
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(solarPump2TurnonDiff), 49, 1), // turnondiff2
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(solarPump2TurnoffDiff), 50, 1), // turnoffdiff2
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(solarPump2Kick), 51, 1), // pump2kick
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(climateZone), 52, 1), // climatezone
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(collector1Area), 53, 1), // collector1area
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(collector1Type), 54, 1), // collector1type
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(collector2Area), 55, 1), // collector2area
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(collector2Type), 56, 1), // collector2type
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(cylPriority), 57, 1), // cylpriority
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(heatCntFlowTemp), 58, 1), // heatcntflowtemp
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(heatCntRetTemp), 59, 1), // heatcntrettemp
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(heatCnt), 60, 1), // heatcnt
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(swapFlowTemp), 61, 1), // swapflowtemp
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(swapRetTemp), 62, 1), // swaprettemp
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DHW, FL_(wwMinTemp), 0, 1), // mintemp
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(airHumidity), 0, 1), // airhumidity
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(dewTemperature), 1, 1), // dewtemperature
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(curFlowTemp), 2, 1), // curflowtemp
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(retTemp), 3, 1), // rettemp
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(sysRetTemp), 4, 1), // sysrettemp
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hpTa4), 5, 1), // hpta4
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hpTr1), 6, 1), // hptr1
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hpTr3), 7, 1), // hptr3
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hpTr4), 8, 1), // hptr4
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hpTr5), 9, 1), // hptr5
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hpTr6), 10, 1), // hptr6
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hpTl2), 11, 1), // hptl2
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hpPl1), 12, 1), // hppl1
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hpPh1), 13, 1), // hpph1
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(heatingPumpMod), 14, 1), // heatingpumpmod
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hpCompSpd), 15, 1), // hpcompspd
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hybridStrategy), 16, 1), // hybridstrategy
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(lowNoiseMode), 17, 1), // lownoisemode
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(lowNoiseStart), 18, 1), // lownoisestart
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(lowNoiseStop), 19, 1), // lownoisestop
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hybridDHW), 20, 1), // hybriddhw
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(energyPriceGas), 21, 1), // energypricegas
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(energyPriceEl), 22, 1), // energypriceel
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(energyPricePV), 23, 1), // energyfeedpv
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(switchOverTemp), 24, 1), // switchovertemp
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(airPurgeMode), 25, 1), // airpurgemode
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(heatPumpOutput), 26, 1), // heatpumpoutput
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(coolingCircuit), 27, 1), // coolingcircuit
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(compStartMod), 28, 1), // compstartmod
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(heatDrainPan), 29, 1), // heatdrainpan
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(heatCable), 30, 1), // heatcable
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(nrgTotal), 31, 2), // nrgtotal
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(nrgHeat), 33, 2), // nrgheat
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(meterTotal), 35, 2), // metertotal
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(meterComp), 37, 2), // metercomp
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(meterEHeat), 39, 2), // metereheat
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(meterHeat), 41, 2), // meterheat
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DHW, FL_(nrgWw), 0, 2), // nrg
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DHW, FL_(meterWw), 2, 2), // meter
REGISTER_MAPPING(dt::SWITCH, TAG_TYPE_DEVICE_DATA, FL_(activated), 0, 1), // activated
REGISTER_MAPPING(dt::SWITCH, TAG_TYPE_DEVICE_DATA, FL_(flowTempHc), 1, 1), // flowtemphc
REGISTER_MAPPING(dt::SWITCH, TAG_TYPE_DEVICE_DATA, FL_(status), 2, 1), // status
REGISTER_MAPPING(dt::CONTROLLER, TAG_TYPE_DEVICE_DATA, FL_(dateTime), 0, 13), // datetime
REGISTER_MAPPING(dt::ALERT, TAG_TYPE_DEVICE_DATA, FL_(setFlowTemp), 0, 1), // setflowtemp
REGISTER_MAPPING(dt::ALERT, TAG_TYPE_DEVICE_DATA, FL_(setBurnPow), 1, 1), // setburnpow
REGISTER_MAPPING(dt::EXTENSION, TAG_TYPE_DEVICE_DATA, FL_(flowTempVf), 0, 1), // flowtempvf
REGISTER_MAPPING(dt::EXTENSION, TAG_TYPE_DEVICE_DATA, FL_(input), 1, 1), // input
REGISTER_MAPPING(dt::EXTENSION, TAG_TYPE_DEVICE_DATA, FL_(outPower), 2, 1), // outpow
REGISTER_MAPPING(dt::EXTENSION, TAG_TYPE_DEVICE_DATA, FL_(setPower), 3, 1), // setpower
REGISTER_MAPPING(dt::EXTENSION, TAG_TYPE_DEVICE_DATA, FL_(setPoint), 4, 1), // setpoint
REGISTER_MAPPING(dt::EXTENSION, TAG_TYPE_DEVICE_DATA, FL_(minV), 5, 1), // minv
REGISTER_MAPPING(dt::EXTENSION, TAG_TYPE_DEVICE_DATA, FL_(maxV), 6, 1), // maxv
REGISTER_MAPPING(dt::EXTENSION, TAG_TYPE_DEVICE_DATA, FL_(minT), 7, 1), // mint
REGISTER_MAPPING(dt::EXTENSION, TAG_TYPE_DEVICE_DATA, FL_(maxT), 8, 1), // maxt
REGISTER_MAPPING(dt::EXTENSION, TAG_TYPE_DEVICE_DATA, FL_(mode), 9, 1), // mode
REGISTER_MAPPING(dt::VENTILATION, TAG_TYPE_DEVICE_DATA, FL_(outFresh), 0, 1), // outfresh
REGISTER_MAPPING(dt::VENTILATION, TAG_TYPE_DEVICE_DATA, FL_(inFresh), 1, 1), // infresh
REGISTER_MAPPING(dt::VENTILATION, TAG_TYPE_DEVICE_DATA, FL_(outEx), 2, 1), // outexhaust
REGISTER_MAPPING(dt::VENTILATION, TAG_TYPE_DEVICE_DATA, FL_(inEx), 3, 1), // inexhaust
REGISTER_MAPPING(dt::VENTILATION, TAG_TYPE_DEVICE_DATA, FL_(ventInSpeed), 4, 1), // ventinspeed
REGISTER_MAPPING(dt::VENTILATION, TAG_TYPE_DEVICE_DATA, FL_(ventOutSpeed), 5, 1), // ventoutspeed
REGISTER_MAPPING(dt::VENTILATION, TAG_TYPE_DEVICE_DATA, FL_(ventMode), 6, 1), // ventmode
REGISTER_MAPPING(dt::VENTILATION, TAG_TYPE_DEVICE_DATA, FL_(airquality), 7, 1), // airquality
REGISTER_MAPPING(dt::VENTILATION, TAG_TYPE_DEVICE_DATA, FL_(airHumidity), 8, 1), // airhumidity
};
} // namespace emsesp

106
src/modbus_test.h Normal file
View File

@@ -0,0 +1,106 @@
#if defined(EMSESP_STANDALONE) || defined(EMSESP_TEST)
#ifndef MODBUS_TEST_H
#define MODBUS_TEST_H
#include <cstdint>
#include <vector>
// Mocked ModbusMessage for tests
enum Error : uint8_t {
SUCCESS = 0x00,
ILLEGAL_FUNCTION = 0x01,
ILLEGAL_DATA_ADDRESS = 0x02,
ILLEGAL_DATA_VALUE = 0x03,
SERVER_DEVICE_FAILURE = 0x04,
ACKNOWLEDGE = 0x05,
SERVER_DEVICE_BUSY = 0x06,
NEGATIVE_ACKNOWLEDGE = 0x07,
MEMORY_PARITY_ERROR = 0x08,
GATEWAY_PATH_UNAVAIL = 0x0A,
GATEWAY_TARGET_NO_RESP = 0x0B,
TIMEOUT = 0xE0,
INVALID_SERVER = 0xE1,
CRC_ERROR = 0xE2, // only for Modbus-RTU
FC_MISMATCH = 0xE3,
SERVER_ID_MISMATCH = 0xE4,
PACKET_LENGTH_ERROR = 0xE5,
PARAMETER_COUNT_ERROR = 0xE6,
PARAMETER_LIMIT_ERROR = 0xE7,
REQUEST_QUEUE_FULL = 0xE8,
ILLEGAL_IP_OR_PORT = 0xE9,
IP_CONNECTION_FAILED = 0xEA,
TCP_HEAD_MISMATCH = 0xEB,
EMPTY_MESSAGE = 0xEC,
ASCII_FRAME_ERR = 0xED,
ASCII_CRC_ERR = 0xEE,
ASCII_INVALID_CHAR = 0xEF,
BROADCAST_ERROR = 0xF0,
UNDEFINED_ERROR = 0xFF // otherwise uncovered communication error
};
class ModbusMessage {
public:
ModbusMessage() {
}
ModbusMessage(std::vector<uint8_t> data) {
_data = data;
}
void get(uint16_t index, uint8_t & value) const {
value = _data[index];
}
void get(uint16_t index, uint16_t & value) const {
value = (_data[index] << 8) + _data[index + 1];
}
void get(uint16_t index, std::vector<uint8_t> & data, uint8_t byte_count) const {
for (auto i = 0; i < byte_count; i++)
data.push_back(_data[i + index]);
}
void add(uint8_t value) {
_data.push_back(value);
}
void add(uint16_t value) {
_data.push_back(value >> 8);
_data.push_back(value & 0xff);
}
uint8_t getServerID() const {
return _data[0];
}
uint8_t getFunctionCode() const {
return _data[1];
}
Error getError() const {
if (_data.size() > 2) {
if (_data[1] & 0x80) {
return static_cast<Error>(_data[2]);
}
}
return SUCCESS;
}
Error setError(uint8_t serverID, uint8_t functionCode, Error errorCode) {
_data.reserve(3);
_data.shrink_to_fit();
_data.clear();
add(serverID);
add(static_cast<uint8_t>((functionCode | 0x80) & 0xFF));
add(static_cast<uint8_t>(errorCode));
return SUCCESS;
}
std::vector<uint8_t> _data;
};
#endif
#endif

View File

@@ -396,6 +396,11 @@ void System::reload_settings() {
board_profile_ = settings.board_profile;
telnet_enabled_ = settings.telnet_enabled;
modbus_enabled_ = settings.modbus_enabled;
modbus_port_ = settings.modbus_port;
modbus_max_clients_ = settings.modbus_max_clients;
modbus_timeout_ = settings.modbus_timeout;
rx_gpio_ = settings.rx_gpio;
tx_gpio_ = settings.tx_gpio;
dallas_gpio_ = settings.dallas_gpio;

View File

@@ -120,6 +120,22 @@ class System {
return telnet_enabled_;
}
bool modbus_enabled() {
return modbus_enabled_;
}
uint16_t modbus_port() {
return modbus_port_;
}
uint8_t modbus_max_clients() {
return modbus_max_clients_;
}
uint32_t modbus_timeout() {
return modbus_timeout_;
}
bool analog_enabled() {
return analog_enabled_;
}
@@ -339,6 +355,10 @@ class System {
uint8_t enum_format_;
bool readonly_mode_;
String version_;
bool modbus_enabled_;
uint16_t modbus_port_;
uint8_t modbus_max_clients_;
uint32_t modbus_timeout_;
// ethernet
uint8_t phy_type_;

View File

@@ -1759,6 +1759,215 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
ok = true;
}
if (command == "modbus") {
shell.printfln("Testing Modbus...");
System::test_set_all_active(true);
add_device(0x08, 172); // boiler: Enviline/Compress 6000AW/Hybrid 3000-7000iAW/SupraEco/Geo 5xx/WLW196i
const auto & it = std::find_if(EMSESP::emsdevices.begin(), EMSESP::emsdevices.end(), [&](const std::unique_ptr<EMSdevice> & dev) {
return dev && dev->device_id() == 0x08;
});
if (it == EMSESP::emsdevices.end()) {
EMSESP::logger().err("ERROR - can not find mocked heatpump device");
return;
}
const auto & device = *it;
{
auto test_int8 = [&](const std::unique_ptr<EMSdevice> & device, uint8_t tag, const std::string & shortname) {
std::vector<uint16_t> modbus_regs(1);
if (auto result = device->get_modbus_value(tag, shortname, modbus_regs)) {
shell.printf("INT8 %s FAILED (ERROR %d)\n", shortname.c_str(), result);
} else {
shell.printf("INT8 %s: %d ", shortname.c_str(), (int8_t)modbus_regs[0]);
if ((int8_t)modbus_regs[0] == (int8_t)EMS_VALUE_DEFAULT_INT8_DUMMY)
shell.printfln("[OK]");
else
shell.printfln("[ERROR] - expected %d, got %d", (int8_t)EMS_VALUE_DEFAULT_INT8_DUMMY, (int8_t)modbus_regs[0]);
}
};
auto test_uint8 = [&](const std::unique_ptr<EMSdevice> & device, uint8_t tag, const std::string & shortname) {
std::vector<uint16_t> modbus_regs(1);
if (auto result = device->get_modbus_value(tag, shortname, modbus_regs)) {
shell.printf("UINT8 %s FAILED (ERROR %d)\n", shortname.c_str(), result);
} else {
shell.printf("UINT8 %s: %d ", shortname.c_str(), (uint8_t)modbus_regs[0]);
if ((uint8_t)modbus_regs[0] == (uint8_t)EMS_VALUE_DEFAULT_UINT8_DUMMY)
shell.printfln("[OK]");
else
shell.printfln("[ERROR] - expected %d, got %d", (uint8_t)EMS_VALUE_DEFAULT_UINT8_DUMMY, (uint8_t)modbus_regs[0]);
}
};
auto test_int16 = [&](const std::unique_ptr<EMSdevice> & device, uint8_t tag, const std::string & shortname) {
std::vector<uint16_t> modbus_regs(1);
if (auto result = device->get_modbus_value(tag, shortname, modbus_regs)) {
shell.printf("INT16 %s FAILED (ERROR %d)\n", shortname.c_str(), result);
} else {
shell.printf("INT16 %s: %d ", shortname.c_str(), (int16_t)modbus_regs[0]);
if ((int16_t)modbus_regs[0] == (int16_t)EMS_VALUE_DEFAULT_INT16_DUMMY)
shell.printfln("[OK]");
else
shell.printfln("[ERROR] - expected %d, got %d", (int16_t)EMS_VALUE_DEFAULT_INT16_DUMMY, (int16_t)modbus_regs[0]);
}
};
auto test_uint16 = [&](const std::unique_ptr<EMSdevice> & device, uint8_t tag, const std::string & shortname) {
std::vector<uint16_t> modbus_regs(1);
if (auto result = device->get_modbus_value(tag, shortname, modbus_regs)) {
shell.printf("UINT16 %s FAILED (ERROR %d)\n", shortname.c_str(), result);
} else {
shell.printf("UINT16 %s: %d ", shortname.c_str(), (uint16_t)modbus_regs[0]);
if ((uint16_t)modbus_regs[0] == (uint16_t)EMS_VALUE_DEFAULT_UINT16_DUMMY)
shell.printfln("[OK]");
else
shell.printfln("[ERROR] - expected %d, got %d", (uint16_t)EMS_VALUE_DEFAULT_UINT16_DUMMY, (uint16_t)modbus_regs[0]);
}
};
auto test_uint24 = [&](const std::unique_ptr<EMSdevice> & device, uint8_t tag, const std::string & shortname) {
std::vector<uint16_t> modbus_regs(2);
if (auto result = device->get_modbus_value(tag, shortname, modbus_regs)) {
shell.printf("UINT24 %s FAILED (ERROR %d)\n", shortname.c_str(), result);
} else {
uint32_t value = ((uint32_t)modbus_regs[0] << 16) | (uint32_t)modbus_regs[1];
shell.printf("UINT24 %s: %d ", shortname.c_str(), value);
if (value == (uint32_t)EMS_VALUE_DEFAULT_UINT24_DUMMY)
shell.printfln("[OK]");
else
shell.printfln("[ERROR] - expected %d, got %d", (uint32_t)EMS_VALUE_DEFAULT_UINT24_DUMMY, value);
}
};
/* there seem to be no uint32 entities to run this test on.
auto test_uint32 = [&](const std::unique_ptr<EMSdevice> & device, uint8_t tag, const std::string & shortname) {
std::vector<uint16_t> modbus_regs(2);
if (auto result = device->get_modbus_value(tag, shortname, modbus_regs)) {
shell.printf("UINT32 %s FAILED (ERROR %d)\n", shortname.c_str(), result);
} else {
uint32_t value = ((uint32_t)modbus_regs[0] << 16) | (uint32_t)modbus_regs[1];
shell.printf("UINT32 %s: %d ", shortname.c_str(), value);
if (value == (uint32_t)EMS_VALUE_DEFAULT_UINT32_DUMMY)
shell.printfln("[OK]");
else
shell.printfln("[ERROR] - expected %d, got %d", (uint32_t)EMS_VALUE_DEFAULT_UINT32_DUMMY, value);
}
};
*/
auto test_bool = [&](const std::unique_ptr<EMSdevice> & device, uint8_t tag, const std::string & shortname) {
std::vector<uint16_t> modbus_regs(1);
if (auto result = device->get_modbus_value(tag, shortname, modbus_regs)) {
shell.printf("BOOL %s FAILED (ERROR %d)\n", shortname.c_str(), result);
} else {
shell.printf("BOOL %s: %d ", shortname.c_str(), (uint8_t)modbus_regs[0]);
if ((uint8_t)modbus_regs[0] == (uint8_t)EMS_VALUE_DEFAULT_BOOL_DUMMY)
shell.printfln("[OK]");
else
shell.printfln("[ERROR] - expected %d, got %d", (uint8_t)EMS_VALUE_DEFAULT_BOOL_DUMMY, (uint8_t)modbus_regs[0]);
}
};
auto test_enum = [&](const std::unique_ptr<EMSdevice> & device, uint8_t tag, const std::string & shortname) {
std::vector<uint16_t> modbus_regs(1);
if (auto result = device->get_modbus_value(tag, shortname, modbus_regs)) {
shell.printf("ENUM %s FAILED (ERROR %d)\n", shortname.c_str(), result);
} else {
shell.printf("ENUM %s: %d ", shortname.c_str(), (uint8_t)modbus_regs[0]);
if ((uint8_t)modbus_regs[0] == (uint8_t)EMS_VALUE_DEFAULT_ENUM_DUMMY)
shell.printfln("[OK]");
else
shell.printfln("[ERROR] - expected %d, got %d", (uint8_t)EMS_VALUE_DEFAULT_ENUM_DUMMY, (uint8_t)modbus_regs[0]);
}
};
shell.println();
shell.printfln("Testing device->get_modbus_value():");
test_int8(device, DeviceValueTAG::TAG_DEVICE_DATA, "mintempsilent");
test_uint8(device, DeviceValueTAG::TAG_DEVICE_DATA, "selflowtemp");
test_int16(device, DeviceValueTAG::TAG_DEVICE_DATA, "outdoortemp");
test_uint16(device, DeviceValueTAG::TAG_DEVICE_DATA, "rettemp");
// test_uint32(device, DeviceValueTAG::TAG_DEVICE_DATA, "heatstarts"); // apparently there are no uint32 entities?
test_uint24(device, DeviceValueTAG::TAG_DEVICE_DATA, "heatstarts");
test_bool(device, DeviceValueTAG::TAG_DEVICE_DATA, "heatingactivated");
test_enum(device, DeviceValueTAG::TAG_DEVICE_DATA, "pumpmode");
}
// modbus_value_to_json
{
shell.println();
shell.printfln("Testing device->modbus_value_to_json():");
std::vector<uint8_t> modbus_bytes(2);
JsonDocument input;
JsonObject inputObject = input.to<JsonObject>();
modbus_bytes[0] = 0;
modbus_bytes[1] = EMS_VALUE_DEFAULT_UINT8_DUMMY;
device->modbus_value_to_json(DeviceValueTAG::TAG_DEVICE_DATA, "selflowtemp", modbus_bytes, inputObject);
std::string jsonString;
serializeJson(inputObject, jsonString);
shell.printf("UINT8 %s: %s (%d) ", "selflowtemp", jsonString.c_str(), inputObject["value"].as<int>());
if (inputObject["value"] == (uint8_t)EMS_VALUE_DEFAULT_UINT8_DUMMY)
shell.println("[OK]");
else
shell.println("[ERROR]");
}
// handleRead
{
shell.println();
shell.printfln("Testing modbus->handleRead():");
uint16_t reg = Modbus::REGISTER_BLOCK_SIZE * DeviceValueTAG::TAG_DEVICE_DATA + 209; // mintempsilent is tag 2 (TAG_DEVICE_DATA), offset 209
ModbusMessage request({device->device_type(), 0x03, static_cast<unsigned char>(reg >> 8), static_cast<unsigned char>(reg & 0xff), 0, 1});
auto response = EMSESP::modbus_->handleRead(request);
if (response.getError() == SUCCESS) {
shell.print("mintempsilent MODBUS response:");
for (const auto & d : response._data) {
shell.printf(" %d", d);
}
if (response._data.size() == 5 && response._data[3] == 0 && response._data[4] == (uint8_t)EMS_VALUE_DEFAULT_INT8_DUMMY) {
shell.printf(" [OK]");
} else {
shell.printf(" [ERROR - invalid response]");
}
shell.println();
} else {
shell.printf("mintempsilent [MODBUS ERROR %d]\n", response.getError());
}
}
// handleWrite
{
shell.println();
shell.printfln("Testing modbus->handleWrite():");
uint16_t reg = Modbus::REGISTER_BLOCK_SIZE * DeviceValueTAG::TAG_DEVICE_DATA + 4; // selflowtemp is tag 2 (TAG_DEVICE_DATA), offset 4
ModbusMessage request({device->device_type(), 0x06, static_cast<unsigned char>(reg >> 8), static_cast<unsigned char>(reg & 0xff), 0, 1, 2, 0, 45});
auto response = EMSESP::modbus_->handleWrite(request);
if (response.getError() == SUCCESS) {
shell.print("selflowtemp MODBUS response:");
for (const auto & d : response._data) {
shell.printf(" %d", d);
}
shell.println(" [OK]");
} else {
shell.printf("selflowtemp [MODBUS ERROR %d]\n", response.getError());
}
}
ok = true;
}
if (command == "poll2") {
shell.printfln("Testing Tx Sending last message on queue...");

View File

@@ -77,6 +77,10 @@ void WebSettings::read(WebSettings & settings, JsonObject root) {
root["eth_clock_mode"] = settings.eth_clock_mode;
String platform = EMSESP_PLATFORM;
root["platform"] = (platform == "ESP32" && EMSESP::system_.PSram()) ? "ESP32R" : platform;
root["modbus_enabled"] = settings.modbus_enabled;
root["modbus_port"] = settings.modbus_port;
root["modbus_max_clients"] = settings.modbus_max_clients;
root["modbus_timeout"] = settings.modbus_timeout;
}
// call on initialization and also when settings are updated via web or console
@@ -272,6 +276,22 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
settings.low_clock = root["low_clock"];
check_flag(prev, settings.low_clock, ChangeFlags::RESTART);
prev = settings.modbus_enabled;
settings.modbus_enabled = root["modbus_enabled"] | EMSESP_DEFAULT_MODBUS_ENABLED;
check_flag(prev, settings.modbus_enabled, ChangeFlags::RESTART);
prev = settings.modbus_port;
settings.modbus_port = root["modbus_port"] | EMSESP_DEFAULT_MODBUS_PORT;
check_flag(prev, settings.modbus_port, ChangeFlags::RESTART);
prev = settings.modbus_max_clients;
settings.modbus_max_clients = root["modbus_max_clients"] | EMSESP_DEFAULT_MODBUS_MAX_CLIENTS;
check_flag(prev, settings.modbus_max_clients, ChangeFlags::RESTART);
prev = settings.modbus_timeout;
settings.modbus_timeout = root["modbus_timeout"] | EMSESP_DEFAULT_MODBUS_TIMEOUT;
check_flag(prev, settings.modbus_timeout, ChangeFlags::RESTART);
//
// these may need mqtt restart to rebuild HA discovery topics
//

View File

@@ -68,6 +68,10 @@ class WebSettings {
uint8_t weblog_buffer;
bool weblog_compact;
bool fahrenheit;
bool modbus_enabled;
uint16_t modbus_port;
uint8_t modbus_max_clients;
uint32_t modbus_timeout;
uint8_t phy_type;
int8_t eth_power; // -1 means disabled