From ffcd5b710094cfdebe4cf73c2f327d8e34c925b3 Mon Sep 17 00:00:00 2001 From: mheyse Date: Thu, 29 Aug 2024 13:53:51 +0200 Subject: [PATCH] Modbus: Handle writes to CMD entities, extend tests --- src/emsdevice.cpp | 34 +++++++++++++++++------- src/emsdevice.h | 2 +- src/modbus.cpp | 44 +++++++++++++++++++++++-------- src/test/test.cpp | 67 ++++++++++++++++++++++++++++++++++------------- 4 files changed, 107 insertions(+), 40 deletions(-) diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 630a109f7..a2f9c31ff 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -2027,13 +2027,13 @@ int EMSdevice::get_modbus_value(uint8_t tag, const std::string & shortname, std: return 0; } -bool EMSdevice::modbus_value_to_json(uint8_t tag, const std::string & shortname, const std::vector & modbus_data, JsonObject jsonValue) { - //Serial.printf("modbus_value_to_json(%d,%s,[%d bytes])\n", tag, shortname.c_str(), modbus_data.size()); +int EMSdevice::modbus_value_to_json(uint8_t tag, const std::string & shortname, const std::vector & modbus_data, JsonObject jsonValue) { + // LOG_DEBUG("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; + return -1; } auto & dv = *it; @@ -2042,7 +2042,7 @@ bool EMSdevice::modbus_value_to_json(uint8_t tag, const std::string & shortname, if (dv.type == DeviceValueType::BOOL) { // bools are 1 16 bit register if (modbus_data.size() != 2) { - return false; + return -2; } jsonValue["value"] = modbus_data[0] || modbus_data[1]; } @@ -2059,7 +2059,7 @@ bool EMSdevice::modbus_value_to_json(uint8_t tag, const std::string & shortname, else if (dv.type == DeviceValueType::ENUM) { // these data types are 1 16 bit register if (modbus_data.size() != 2) { - return false; + return -3; } jsonValue["value"] = (uint16_t)modbus_data[0] << 8 | (uint16_t)modbus_data[1]; @@ -2069,14 +2069,14 @@ bool EMSdevice::modbus_value_to_json(uint8_t tag, const std::string & shortname, 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; + return -4; } 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; + return -5; } jsonValue["value"] = @@ -2084,11 +2084,25 @@ bool EMSdevice::modbus_value_to_json(uint8_t tag, const std::string & shortname, * (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; + // handle CMD + else if (dv.type == DeviceValueType::CMD) { + if (modbus_data.size() > 4) { + return -7; + } + + uint32_t value = 0; + for(auto i = 0; i < modbus_data.size(); i++) { + value += (uint32_t)modbus_data[modbus_data.size() - i - 1] << (i * 8); + } + + jsonValue["value"] = Helpers::numericoperator2scalefactor(dv.numeric_operator) * (float)value; } - return true; + else { + return -6; + } + + return 0; } } // namespace emsesp diff --git a/src/emsdevice.h b/src/emsdevice.h index 9a9bbc550..97e2f99ef 100644 --- a/src/emsdevice.h +++ b/src/emsdevice.h @@ -203,7 +203,7 @@ class EMSdevice { } int get_modbus_value(uint8_t tag, const std::string & shortname, std::vector & result); - bool modbus_value_to_json(uint8_t tag, const std::string & shortname, const std::vector & modbus_data, JsonObject jsonValue); + int modbus_value_to_json(uint8_t tag, const std::string & shortname, const std::vector & modbus_data, JsonObject jsonValue); const char * brand_to_char(); const std::string to_string(); diff --git a/src/modbus.cpp b/src/modbus.cpp index aeaaf5da3..94c02d155 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -313,17 +313,18 @@ ModbusMessage Modbus::handleRead(const ModbusMessage & request) { } auto buf = std::vector(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); + auto error_code = dev->get_modbus_value(tag, modbusInfo->short_name, buf); + if (error_code) { + LOG_ERROR("Unable to read raw device value %s for tag=%d - error_code = %d", modbusInfo->short_name, (int)tag, error_code); response.setError(request.getServerID(), request.getFunctionCode(), SERVER_DEVICE_FAILURE); } + response.add(request.getServerID()); + response.add(request.getFunctionCode()); + response.add((uint8_t)(num_words * 2)); + for (auto & value : buf) + response.add(value); + return response; } @@ -368,6 +369,8 @@ ModbusMessage Modbus::handleWrite(const ModbusMessage & request) { auto register_offset = start_address - tag * REGISTER_BLOCK_SIZE; + LOG_DEBUG("Tag %d, offset %d", tag, register_offset); + const auto & dev_it = std::find_if(EMSESP::emsdevices.begin(), EMSESP::emsdevices.end(), [&](const std::unique_ptr & x) { return x->device_type() == device_type; }); @@ -380,6 +383,8 @@ ModbusMessage Modbus::handleWrite(const ModbusMessage & request) { const auto & dev = *dev_it; + LOG_DEBUG("found device '%s' of type %d", dev->name(), dev->device_type()); + // 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), @@ -397,6 +402,8 @@ ModbusMessage Modbus::handleWrite(const ModbusMessage & request) { return response; } + LOG_DEBUG("Writing to entity %s", modbusInfo->short_name); + // 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 @@ -408,9 +415,10 @@ ModbusMessage Modbus::handleWrite(const ModbusMessage & request) { JsonDocument input_doc; JsonObject input = input_doc.to(); - if (!dev->modbus_value_to_json(tag, modbusInfo->short_name, data, input)) { + auto error_code = dev->modbus_value_to_json(tag, modbusInfo->short_name, data, input); + if (error_code) { // error getting modbus value as json - LOG_ERROR("error getting modbus value as json"); + LOG_ERROR("error getting modbus value as json, error code = %d", error_code); response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_ADDRESS); return response; } @@ -488,7 +496,6 @@ int Modbus::getRegisterCount(const DeviceValue & dv) { case DeviceValue::INT16: case DeviceValue::UINT16: case DeviceValue::ENUM: // 8 bit - case DeviceValue::CMD: return 1; case DeviceValue::UINT24: @@ -496,6 +503,21 @@ int Modbus::getRegisterCount(const DeviceValue & dv) { case DeviceValue::TIME: // 32 bit return 2; + case DeviceValue::CMD: { + // calculate a sensible register size from min, max and numeric_operator + uint32_t num_values = std::max(dv.max, (uint32_t)abs(dv.min)); + int num_registers = 0; + + if (num_values <= (1L << 8)) num_registers = 1; + else if(num_values <= (1L << 16)) num_registers = 2; + else if(num_values <= (1L << 32)) num_registers = 4; + else LOG_ERROR("num_registers is too big to be encoded with modbus registers"); + + LOG_DEBUG("Value for CMD '%s' can take on %ld values and is encoded in %d registers", dv.short_name, num_values, num_registers); + + return num_registers; + } + case DeviceValue::STRING: break; // impossible to guess, needs to be hardcoded } diff --git a/src/test/test.cpp b/src/test/test.cpp index c13c8c349..3fe3e91cd 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -1844,17 +1844,28 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const System::test_set_all_active(true); add_device(0x08, 172); // boiler: Enviline/Compress 6000AW/Hybrid 3000-7000iAW/SupraEco/Geo 5xx/WLW196i + add_device(0x10, 158); // thermostat: RC310 - const auto & it = std::find_if(EMSESP::emsdevices.begin(), EMSESP::emsdevices.end(), [&](const std::unique_ptr & dev) { + const auto & boiler_it = std::find_if(EMSESP::emsdevices.begin(), EMSESP::emsdevices.end(), [&](const std::unique_ptr & dev) { return dev && dev->device_id() == 0x08; }); - if (it == EMSESP::emsdevices.end()) { - EMSESP::logger().err("ERROR - can not find mocked heatpump device"); + if (boiler_it == EMSESP::emsdevices.end()) { + EMSESP::logger().err("ERROR - can not find mocked boiler device"); return; } - const auto & device = *it; + const auto & thermostat_it = std::find_if(EMSESP::emsdevices.begin(), EMSESP::emsdevices.end(), [&](const std::unique_ptr & dev) { + return dev && dev->device_id() == 0x10; + }); + + if (thermostat_it == EMSESP::emsdevices.end()) { + EMSESP::logger().err("ERROR - can not find mocked thermostat device"); + return; + } + + const auto & boiler_dev = *boiler_it; + const auto & thermostat_dev = *thermostat_it; { auto test_int8 = [&](const std::unique_ptr & device, uint8_t tag, const std::string & shortname) { @@ -1967,14 +1978,14 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const 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_int8(boiler_dev, DeviceValueTAG::TAG_DEVICE_DATA, "mintempsilent"); + test_uint8(boiler_dev, DeviceValueTAG::TAG_DEVICE_DATA, "selflowtemp"); + test_int16(boiler_dev, DeviceValueTAG::TAG_DEVICE_DATA, "outdoortemp"); + test_uint16(boiler_dev, 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"); + test_uint24(boiler_dev, DeviceValueTAG::TAG_DEVICE_DATA, "heatstarts"); + test_bool(boiler_dev, DeviceValueTAG::TAG_DEVICE_DATA, "heatingactivated"); + test_enum(boiler_dev, DeviceValueTAG::TAG_DEVICE_DATA, "pumpmode"); } // modbus_value_to_json @@ -1987,7 +1998,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const JsonObject inputObject = input.to(); 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); + boiler_dev->modbus_value_to_json(DeviceValueTAG::TAG_DEVICE_DATA, "selflowtemp", modbus_bytes, inputObject); std::string jsonString; serializeJson(inputObject, jsonString); @@ -2004,9 +2015,9 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const 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 + uint16_t reg = Modbus::REGISTER_BLOCK_SIZE * DeviceValueTAG::TAG_DEVICE_DATA + 214; // mintempsilent is tag 2 (TAG_DEVICE_DATA), offset 214 - ModbusMessage request({device->device_type(), 0x03, static_cast(reg >> 8), static_cast(reg & 0xff), 0, 1}); + ModbusMessage request({boiler_dev->device_type(), 0x03, static_cast(reg >> 8), static_cast(reg & 0xff), 0, 1}); auto response = EMSESP::modbus_->handleRead(request); if (response.getError() == SUCCESS) { @@ -2025,13 +2036,13 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const } } - // handleWrite + // handleWrite boiler { shell.println(); - shell.printfln("Testing modbus->handleWrite():"); + shell.printfln("Testing modbus->handleWrite() for boiler:"); - 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(reg >> 8), static_cast(reg & 0xff), 0, 1, 2, 0, 45}); + uint16_t reg = Modbus::REGISTER_BLOCK_SIZE * DeviceValueTAG::TAG_DEVICE_DATA + 4; // selflowtemp + ModbusMessage request({boiler_dev->device_type(), 0x06, static_cast(reg >> 8), static_cast(reg & 0xff), 0, 1, 2, 0, 45}); auto response = EMSESP::modbus_->handleWrite(request); if (response.getError() == SUCCESS) { @@ -2045,6 +2056,26 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const } } + // handleWrite thermostat + { + shell.println(); + shell.printfln("Testing modbus->handleWrite() for thermostat:"); + + uint16_t reg = Modbus::REGISTER_BLOCK_SIZE * DeviceValueTAG::TAG_HC1 + 41; // remotetemp + ModbusMessage request({thermostat_dev->device_type(), 0x06, static_cast(reg >> 8), static_cast(reg & 0xff), 0, 1, 2, 0, 45}); + auto response = EMSESP::modbus_->handleWrite(request); + + if (response.getError() == SUCCESS) { + shell.print("remotetemp MODBUS response:"); + for (const auto & d : response._data) { + shell.printf(" %d", d); + } + shell.println(" [OK]"); + } else { + shell.printf("remotetemp [MODBUS ERROR %d]\n", response.getError()); + } + } + ok = true; }