diff --git a/components/daikin_rotex_can/Accessor.h b/components/daikin_rotex_can/Accessor.h index ba75656..827076d 100644 --- a/components/daikin_rotex_can/Accessor.h +++ b/components/daikin_rotex_can/Accessor.h @@ -1,13 +1,11 @@ #pragma once -#include "esphome/components/select/select.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/binary_sensor/binary_sensor.h" -#include "esphome/components/number/number.h" #include "esphome/components/text/text.h" #include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/daikin_rotex_can/BidiMap.h" #include "esphome/components/daikin_rotex_can/utils.h" +#include namespace esphome { namespace daikin_rotex_can { @@ -15,127 +13,42 @@ namespace daikin_rotex_can { class DaikinRotexCanComponent; class Accessor { - struct TArguments { - sensor::Sensor* pSensor; + struct TEntityArguments { + EntityBase* pEntity; std::string id; std::string data; std::string expected_response; uint8_t data_offset; uint8_t data_size; float divider; - std::string update_entity; - std::string set_entity; - }; - using TSensorMap = std::map; - - struct TBinaryArguments { - binary_sensor::BinarySensor* pBinarySensor; - std::string id; - std::string data; - std::string expected_response; - uint8_t data_offset; - uint8_t data_size; - std::string update_entity; - std::string set_entity; - }; - using TBinarySensorMap = std::map; - - struct TTextArguments { - text_sensor::TextSensor* pTextSensor; - std::string id; - std::string data; - std::string expected_response; - uint8_t data_offset; - uint8_t data_size; BidiMap map; std::string update_entity; - std::string set_entity; - - TTextArguments( - text_sensor::TextSensor* _pTextSensor, - std::string _id, - std::string _data, - std::string _expected_response, + std::string setter; + TEntityArguments( + EntityBase* _pEntity, + std::string const& _id, + std::string const& _data, + std::string const& _expected_response, uint8_t _data_offset, uint8_t _data_size, + float _divider, std::string const& _map, std::string const& _update_entity, - std::string const& _set_entity + std::string const& _setter ) - : pTextSensor(_pTextSensor) + : pEntity(_pEntity) , id(_id) , data(_data) , expected_response(_expected_response) , data_offset(_data_offset) , data_size(_data_size) + , divider(_divider) , map(Utils::str_to_map(_map)) , update_entity(_update_entity) - , set_entity(_set_entity) - {} - }; - using TTextSensorMap = std::map; - - struct TSelectArguments { - select::Select* pSelect; - std::string id; - std::string data; - std::string expected_response; - uint8_t data_offset; - uint8_t data_size; - BidiMap map; - std::array set; - - TSelectArguments( - select::Select* _pSelect, - std::string _id, - std::string _data, - std::string _expected_response, - uint8_t _data_offset, - uint8_t _data_size, - std::string const& _map, - std::string _set - ) - : pSelect(_pSelect) - , id(_id) - , data(_data) - , expected_response(_expected_response) - , data_offset(_data_offset) - , data_size(_data_size) - , map(Utils::str_to_map(_map)) - , set(Utils::str_to_bytes_array16(_set)) + , setter(_setter) {} }; - using TSelectMap = std::map; - struct TNumberArguments { - number::Number* pNumber; - std::string id; - std::string data; - std::string expected_response; - uint8_t data_offset; - uint8_t data_size; - float divider; - std::array set; - TNumberArguments( - number::Number* _pNumber, - std::string _id, - std::string _data, - std::string _expected_response, - uint8_t _data_offset, - uint8_t _data_size, - float _divider, - std::string _set - ) - : pNumber(_pNumber) - , id(_id) - , data(_data) - , expected_response(_expected_response) - , data_offset(_data_offset) - , data_size(_data_size) - , divider(_divider) - , set(Utils::str_to_bytes_array16(_set)) - {} - }; - using TNumberMap = std::map; + using TEntityArgumentsList = std::list; public: Accessor(DaikinRotexCanComponent* pDaikinRotexCanComponent) : m_pDaikinRotexCanComponent(pDaikinRotexCanComponent) { @@ -145,6 +58,9 @@ class Accessor { return m_pDaikinRotexCanComponent; } + TEntityArgumentsList const& get_entities() const { return m_entities; } + void set_entity(std::string const& name, TEntityArguments const& arg) { m_entities.push_back(arg); } + // Texts text::Text* get_log_filter() const { return m_log_filter; } @@ -155,40 +71,14 @@ class Accessor { // Sensors - TSensorMap const& get_sensors() const { return m_sensors; } - void set_sensor(std::string const& name, TArguments const& arg) { m_sensors.insert({name, arg}); } - sensor::Sensor* get_thermal_power() const { return m_thermal_power; } void set_thermal_power(sensor::Sensor* pSensor) { m_thermal_power = pSensor; } - // Binary Sensors - - TBinarySensorMap const& get_binary_sensors() const { return m_binary_sensors; } - void set_binary_sensor(std::string const& name, TBinaryArguments const& arg) { m_binary_sensors.insert({name, arg}); } - - // Text Sensors - - TTextSensorMap const& get_text_sensors() const { return m_text_sensors; } - void set_text_sensor(std::string const& name, TTextArguments const& arg) { m_text_sensors.insert({name, arg}); } - - // Selects - - TSelectMap const& get_selects() const { return m_selects; } - void set_select(std::string const& name, TSelectArguments const& arg) { m_selects.insert({name, arg}); } - - // Numbers - TNumberMap const& get_numbers() const { return m_numbers; } - void set_number(std::string const& name, TNumberArguments const& arg) { m_numbers.insert({name, arg}); } - private: text::Text* m_log_filter; text::Text* m_custom_request_text; - TSensorMap m_sensors; - TBinarySensorMap m_binary_sensors; - TTextSensorMap m_text_sensors; - TSelectMap m_selects; - TNumberMap m_numbers; + TEntityArgumentsList m_entities; // Sensors sensor::Sensor* m_thermal_power; diff --git a/components/daikin_rotex_can/BidiMap.h b/components/daikin_rotex_can/BidiMap.h index ba9fe51..5b47651 100644 --- a/components/daikin_rotex_can/BidiMap.h +++ b/components/daikin_rotex_can/BidiMap.h @@ -1,5 +1,6 @@ #pragma once +#include #include template @@ -33,24 +34,16 @@ class BidiMap { return key_to_value.end(); } - KeyType getKey(const ValueType& value) const { - auto it = findByValue(value); - if (it != key_to_value.end()) { - return it->first; - } - throw std::runtime_error("Value not found"); + Iterator end() const { + return key_to_value.end(); } - ValueType getValue(const KeyType& key) const { - auto it = findByKey(key); - if (it != key_to_value.end()) { - return it->second; + std::string string() const { + std::stringstream ss; + for (const auto& pair : key_to_value) { + ss << "{" << std::to_string(pair.first) << ", " << pair.second << "} "; } - throw std::runtime_error("Key not found"); - } - - Iterator end() const { - return key_to_value.end(); + return ss.str(); } private: diff --git a/components/daikin_rotex_can/__init__.py b/components/daikin_rotex_can/__init__.py index 800c3e9..c2fe305 100644 --- a/components/daikin_rotex_can/__init__.py +++ b/components/daikin_rotex_can/__init__.py @@ -27,7 +27,6 @@ ########## Configuration of Sensors, TextSensors, BinarySensors, Selects and Numbers ########## sensor_configuration = [ - ####################### Sensors ####################### { "type": "sensor", "name": "t_hs", @@ -123,7 +122,7 @@ "data_offset": 6, "data_size": 1, "divider": 1, - "set": "30 00 FA 06 7F __ __" + "setter": "30 00 FA 06 7F __ __" }, { "type": "number", @@ -141,7 +140,7 @@ "data_offset": 6, "data_size": 1, "divider": 1, - "set": "30 00 FA 06 7E __ __" + "setter": "30 00 FA 06 7E __ __" }, { "type": "sensor", @@ -383,7 +382,7 @@ "data_offset": 3, "data_size": 2, "divider": 10.0, - "set": "30 00 05 __ __ 00 00" + "setter": "30 00 05 __ __ 00 00" }, { "type": "number", @@ -400,7 +399,7 @@ "data_offset": 5, "data_size": 2, "divider": 10.0, - "set": "30 00 FA 01 29 __ __" + "setter": "30 00 FA 01 29 __ __" }, { "type": "number", @@ -417,7 +416,7 @@ "data_offset": 5, "data_size": 2, "divider": 100.0, - "set": "30 00 FA 01 0E __ __" + "setter": "30 00 FA 01 0E __ __" }, { "type": "number", @@ -435,7 +434,7 @@ "data_offset": 5, "data_size": 2, "divider": 10.0, - "set": "30 00 FA 01 2B __ __" + "setter": "30 00 FA 01 2B __ __" }, { "type": "number", @@ -453,7 +452,7 @@ "data_offset": 3, "data_size": 2, "divider": 10.0, - "set": "30 00 28 __ __ 00 00" + "setter": "30 00 28 __ __ 00 00" }, { "type": "number", @@ -471,9 +470,8 @@ "data_offset": 3, "data_size": 2, "divider": 10.0, - "set": "30 00 13 __ __ 00 00" + "setter": "30 00 13 __ __ 00 00" }, - ####################### Text sensors ####################### { "type": "text_sensor", "name": "mode_of_operating" , @@ -574,7 +572,6 @@ 8007: "W8007 Wasserdruck in Anlage zu hoch" } }, - ####################### Binary sensors ####################### { "type": "binary_sensor", "name": "status_kompressor" , @@ -593,7 +590,6 @@ "data_offset": 6, "data_size": 1 }, - ####################### Selects ####################### { "type": "select", "name": "operating_mode" , @@ -611,7 +607,7 @@ 0x0B: "Automatik 1", 0x0C: "Automatik 2" }, - "set": "30 00 FA 01 12 __ 00" + "setter": "30 00 FA 01 12 __ 00" }, { "type": "select", @@ -625,7 +621,7 @@ 0x00: "Witterungsgeführt", 0x01: "Fest" }, - "set": "30 00 FA 01 41 00 __" + "setter": "30 00 FA 01 41 00 __" }, { "type": "select", @@ -640,7 +636,7 @@ 0x01: "SG Modus 1", 0x02: "SG Modus 2" }, - "set": "30 00 FA 06 94 00 __" + "setter": "30 00 FA 06 94 00 __" }, { "type": "select", @@ -654,7 +650,7 @@ 0x00: "Aus", 0x01: "An" }, - "set": "30 00 FA 06 93 00, __" + "setter": "30 00 FA 06 93 00, __" } ] @@ -785,96 +781,42 @@ def to_code(config): if entities := config.get(CONF_ENTITIES): for sens_conf in sensor_configuration: if yaml_sensor_conf := entities.get(sens_conf.get("name")): + entity = None match sens_conf.get("type"): case "sensor": - sens = yield sensor.new_sensor(yaml_sensor_conf) - - cg.add(var.getAccessor().set_sensor( - sens_conf.get("name"), - [ - sens, - sens_conf.get("name"), - sens_conf.get("data"), - sens_conf.get("expected_reponse"), - sens_conf.get("data_offset"), - sens_conf.get("data_size"), - sens_conf.get("divider"), - sens_conf.get("update_entity", ""), - sens_conf.get("set_entity", "") - ] - )) + entity = yield sensor.new_sensor(yaml_sensor_conf) case "text_sensor": - sens = yield text_sensor.new_text_sensor(yaml_sensor_conf) - - cg.add(var.getAccessor().set_text_sensor( - sens_conf.get("name"), - [ - sens, - sens_conf.get("name"), - sens_conf.get("data"), - sens_conf.get("expected_reponse"), - sens_conf.get("data_offset"), - sens_conf.get("data_size"), - "|".join([f"0x{key:02X}:{value}" for key, value in sens_conf.get("map").items()]), - sens_conf.get("update_entity", ""), - sens_conf.get("set_entity", "") - ] - )) + entity = yield text_sensor.new_text_sensor(yaml_sensor_conf) case "binary_sensor": - sens = yield binary_sensor.new_binary_sensor(yaml_sensor_conf) - - cg.add(var.getAccessor().set_binary_sensor( - sens_conf.get("name"), - [ - sens, - sens_conf.get("name"), - sens_conf.get("data"), - sens_conf.get("expected_reponse"), - sens_conf.get("data_offset"), - sens_conf.get("data_size"), - sens_conf.get("update_entity", ""), - sens_conf.get("set_entity", "") - ] - )) + entity = yield binary_sensor.new_binary_sensor(yaml_sensor_conf) case "select": - sel = yield select.new_select(yaml_sensor_conf, options = list(sens_conf.get("map").values())) - cg.add(sel.set_id(sens_conf.get("name"))) - yield cg.register_parented(sel, var) - - cg.add(var.getAccessor().set_select( - sens_conf.get("name"), - [ - sel, - sens_conf.get("name"), - sens_conf.get("data"), - sens_conf.get("expected_reponse"), - sens_conf.get("data_offset"), - sens_conf.get("data_size"), - "|".join([f"0x{key:02X}:{value}" for key, value in sens_conf.get("map").items()]), - sens_conf.get("set") - ] - )) + entity = yield select.new_select(yaml_sensor_conf, options = list(sens_conf.get("map").values())) + cg.add(entity.set_id(sens_conf.get("name"))) + yield cg.register_parented(entity, var) case "number": - num = yield number.new_number( + entity = yield number.new_number( yaml_sensor_conf, min_value=sens_conf.get("min_value"), max_value=sens_conf.get("max_value"), - step=sens_conf.get("step"), + step=sens_conf.get("step") ) - yield cg.register_parented(num, var) - cg.add(var.getAccessor().set_number( - sens_conf.get("name"), - [ - num, - sens_conf.get("name"), - sens_conf.get("data"), - sens_conf.get("expected_reponse"), - sens_conf.get("data_offset"), - sens_conf.get("data_size"), - sens_conf.get("divider"), - sens_conf.get("set") - ] - )) + cg.add(entity.set_id(sens_conf.get("name"))) + yield cg.register_parented(entity, var) + case _: + raise Exception("Unknown type: " + sens_conf.get("type")) + + cg.add(var.getAccessor().set_entity(sens_conf.get("name"), [ + entity, + sens_conf.get("name"), + sens_conf.get("data"), + sens_conf.get("expected_reponse"), + sens_conf.get("data_offset"), + sens_conf.get("data_size"), + sens_conf.get("divider", 1.0), + "|".join([f"0x{key:02X}:{value}" for key, value in sens_conf.get("map", {}).items()]), # map + sens_conf.get("update_entity", ""), + sens_conf.get("setter", "") + ])) ########## Sensors ########## @@ -887,5 +829,3 @@ def to_code(config): if button_conf := entities.get(CONF_DHW_RUN): but = yield button.new_button(button_conf) yield cg.register_parented(but, var) - - cg.add(var.validateConfig()) diff --git a/components/daikin_rotex_can/daikin_rotex_can.cpp b/components/daikin_rotex_can/daikin_rotex_can.cpp index 7699e97..7aafe29 100644 --- a/components/daikin_rotex_can/daikin_rotex_can.cpp +++ b/components/daikin_rotex_can/daikin_rotex_can.cpp @@ -16,285 +16,82 @@ DaikinRotexCanComponent::DaikinRotexCanComponent() { } -void DaikinRotexCanComponent::validateConfig() { - for (auto const& pair : m_accessor.get_sensors()) { - const std::array data = Utils::str_to_bytes_array8(pair.second.data); - const std::array expected_response = Utils::str_to_bytes_array16(pair.second.expected_response); +void DaikinRotexCanComponent::setup() { + ESP_LOGI(TAG, "setup"); + + for (auto const& entity_conf : m_accessor.get_entities()) { + const std::array data = Utils::str_to_bytes_array8(entity_conf.data); + const std::array expected_response = Utils::str_to_bytes_array16(entity_conf.expected_response); - /////////////////////// Sensors /////////////////////// m_data_requests.add({ - pair.second.id, + entity_conf.id, data, expected_response, - [pair]() -> EntityBase* { return pair.second.pSensor; }, - [pair, this](auto const& data) -> DataType { - float value = 0; - - if (pair.second.data_offset > 0 && (pair.second.data_offset + pair.second.data_size) <= 7) { - switch (pair.second.data_size) { - case 1: - value = data[pair.second.data_offset] / pair.second.divider; - pair.second.pSensor->publish_state(value); - break; - case 2: - value = ((data[pair.second.data_offset] << 8) + data[pair.second.data_offset + 1]) / pair.second.divider; - pair.second.pSensor->publish_state(value); - break; - default: - call_later([pair](){ - ESP_LOGE("validateConfig", "Invalid data size: %d", pair.second.data_size); + entity_conf.pEntity, + [entity_conf, this](auto const& data) -> DataType { + DataType variant; + + if (entity_conf.data_offset > 0 && (entity_conf.data_offset + entity_conf.data_size) <= 7) { + if (entity_conf.data_size >= 1 && entity_conf.data_size <= 2) { + const float value = entity_conf.data_size == 2 ? + (((data[entity_conf.data_offset] << 8) + data[entity_conf.data_offset + 1]) / entity_conf.divider) : + (data[entity_conf.data_offset] / entity_conf.divider); + + if (dynamic_cast(entity_conf.pEntity) != nullptr) { + Utils::toSensor(entity_conf.pEntity)->publish_state(value); + variant = value; + } else if (dynamic_cast(entity_conf.pEntity) != nullptr) { + Utils::toBinarySensor(entity_conf.pEntity)->publish_state(value); + variant = value; + } else if (dynamic_cast(entity_conf.pEntity) != nullptr) { + auto it = entity_conf.map.findByKey(value); + const std::string str = it != entity_conf.map.end() ? it->second : "INVALID"; + Utils::toTextSensor(entity_conf.pEntity)->publish_state(str); + variant = str; + } else if (dynamic_cast(entity_conf.pEntity) != nullptr) { + auto it = entity_conf.map.findByKey(value); + const std::string str = it != entity_conf.map.end() ? it->second : "INVALID"; + Utils::toSelect(entity_conf.pEntity)->publish_state(str); + variant = str; + } else if (dynamic_cast(entity_conf.pEntity) != nullptr) { + Utils::toNumber(entity_conf.pEntity)->publish_state(value); + variant = value; + } + } else { + call_later([entity_conf](){ + ESP_LOGE("validateConfig", "Invalid data size: %d", entity_conf.data_size); }); } } else { - call_later([pair](){ - ESP_LOGE("validateConfig", "Invalid data_offset: %d", pair.second.data_offset); + call_later([entity_conf](){ + ESP_LOGE("validateConfig", "Invalid data_offset: %d", entity_conf.data_offset); }); } - call_later([pair, this](){ - if (!pair.second.update_entity.empty()) { - updateState(pair.second.update_entity); - } - }); - - call_later([pair, value, this](){ - if (!pair.second.set_entity.empty()) { - TRequest const* pRequest = m_data_requests.get(pair.second.set_entity); - if (pRequest != nullptr) { - EntityBase* pEntity = pRequest->getEntity(); - if (number::Number* pNumber = dynamic_cast(pEntity)) { - pNumber->publish_state(value); - } else { - ESP_LOGE("handle ", "id<%s>.set_entity<%s> has unsupported type!", - pair.second.id.c_str(), pair.second.set_entity.c_str()); - } - } else { - ESP_LOGE("handle ", "id<%s>.set_entity<%s> is invalid!", - pair.second.id.c_str(), pair.second.set_entity.c_str()); - } + call_later([entity_conf, this](){ + if (!entity_conf.update_entity.empty()) { + updateState(entity_conf.update_entity); } }); - if (pair.second.id == "target_hot_water_temperature") { + if (entity_conf.id == "target_hot_water_temperature") { call_later([this](){ run_dhw_lambdas(); }); } - return value; - } - }); - } - - /////////////////////// Binary Sensors /////////////////////// - for (auto const& pair : m_accessor.get_binary_sensors()) { - const std::array data = Utils::str_to_bytes_array8(pair.second.data); - const std::array expected_response = Utils::str_to_bytes_array16(pair.second.expected_response); - - m_data_requests.add({ - pair.second.id, - data, - expected_response, - [pair]() -> EntityBase* { return pair.second.pBinarySensor; }, - [pair, this](auto const& data) -> DataType { - bool value; - - if (pair.second.data_offset > 0 && (pair.second.data_offset + pair.second.data_size) <= 7) { - switch (pair.second.data_size) { - case 1: { - value = data[pair.second.data_offset]; - pair.second.pBinarySensor->publish_state(value); - break; - } - default: - call_later([pair](){ - ESP_LOGE("validateConfig", "Invalid data size: %d", pair.second.data_size); - }); - } - } else { - call_later([pair](){ - ESP_LOGE("validateConfig", "Invalid data_offset: %d", pair.second.data_offset); - }); - } - - return value; - } - }); - } - - /////////////////////// Text Sensors /////////////////////// - for (auto const& pair : m_accessor.get_text_sensors()) { - const std::array data = Utils::str_to_bytes_array8(pair.second.data); - const std::array expected_response = Utils::str_to_bytes_array16(pair.second.expected_response); - - m_data_requests.add({ - pair.second.id, - data, - expected_response, - [pair]() -> EntityBase* { return pair.second.pTextSensor; }, - [pair, this](auto const& data) -> DataType { - std::string str; - - if (pair.second.data_offset > 0 && (pair.second.data_offset + pair.second.data_size) <= 7) { - switch (pair.second.data_size) { - case 1: { - const uint32_t value = data[pair.second.data_offset]; - str = pair.second.map.getValue(value); - - pair.second.pTextSensor->publish_state(str); - break; - } - case 2: { - const uint32_t value = (data[pair.second.data_offset] << 8) + data[pair.second.data_offset + 1]; - str = pair.second.map.getValue(value); - - pair.second.pTextSensor->publish_state(str); - break; - } - default: - call_later([pair](){ - ESP_LOGE("validateConfig", "Invalid data size: %d", pair.second.data_size); - }); - } - } else { - call_later([pair](){ - ESP_LOGE("validateConfig", "Invalid data_offset: %d", pair.second.data_offset); - }); - } - - call_later([pair, this](){ - if (!pair.second.update_entity.empty()) { - updateState(pair.second.update_entity); - } - }); - - call_later([pair, str, this](){ - if (!pair.second.set_entity.empty()) { - TRequest const* pRequest = m_data_requests.get(pair.second.set_entity); - if (pRequest != nullptr) { - EntityBase* pEntity = pRequest->getEntity(); - if (select::Select* pSelect = dynamic_cast(pEntity)) { - //ESP_LOGE("validateConfig", "set select: %s", pair.second.set_entity.c_str()); - pSelect->publish_state(str); - } else { - ESP_LOGE("handle ", "id<%s>.set_entity<%s> has unsupported type!", - pair.second.id.c_str(), pair.second.set_entity.c_str()); - } - } else { - ESP_LOGE("handle ", "id<%s>.set_entity<%s> is invalid!", - pair.second.id.c_str(), pair.second.set_entity.c_str()); - } - } - }); - - return str; - } - }); - } - - /////////////////////// Selects /////////////////////// - for (auto const& pair : m_accessor.get_selects()) { - const std::array data = Utils::str_to_bytes_array8(pair.second.data); - const std::array expected_response = Utils::str_to_bytes_array16(pair.second.expected_response); - - m_data_requests.add({ - pair.second.id, - data, - expected_response, - [pair]() -> EntityBase* { return pair.second.pSelect; }, - [pair, this](auto const& data) -> DataType { - std::string str; - - if (pair.second.data_offset > 0 && (pair.second.data_offset + pair.second.data_size) <= 7) { - switch (pair.second.data_size) { - case 1: { - const uint32_t value = data[pair.second.data_offset]; - str = pair.second.map.getValue(value); - - pair.second.pSelect->publish_state(str); - break; - } - case 2: { - const uint32_t value = (data[pair.second.data_offset] << 8) + data[pair.second.data_offset + 1]; - str = pair.second.map.getValue(value); - - pair.second.pSelect->publish_state(str); - break; - } - default: - call_later([pair](){ - ESP_LOGE("validateConfig", "Invalid data size: %d", pair.second.data_size); - }); - } - } else { - call_later([pair](){ - ESP_LOGE("validateConfig", "Invalid data_offset: %d", pair.second.data_offset); - }); - } - - return str; + return variant; }, - [pair](auto const& value) -> std::vector { - std::vector data; - uint8_t index = 0; - for (uint16_t word : pair.second.set) { - data.push_back(word == esphome::daikin_rotex_can::DC ? value : static_cast(word)); - } - return data; - } - }); - } - - /////////////////////// Numbers /////////////////////// - for (auto const& pair : m_accessor.get_numbers()) { - const std::array data = Utils::str_to_bytes_array8(pair.second.data); - const std::array expected_response = Utils::str_to_bytes_array16(pair.second.expected_response); - - m_data_requests.add({ - pair.second.id, - data, - expected_response, - [pair]() -> EntityBase* { return pair.second.pNumber; }, - [pair, this](auto const& data) -> DataType { - float value; - - if (pair.second.data_offset > 0 && (pair.second.data_offset + pair.second.data_size) <= 7) { - switch (pair.second.data_size) { - case 1: { - value = data[pair.second.data_offset] / pair.second.divider; - pair.second.pNumber->publish_state(value); - break; - case 2: - value = ((data[pair.second.data_offset] << 8) + data[pair.second.data_offset + 1]) / pair.second.divider; - pair.second.pNumber->publish_state(value); - break; - } - default: - call_later([pair](){ - ESP_LOGE("validateConfig", "Invalid data size: %d", pair.second.data_size); - }); - } - } else { - call_later([pair](){ - ESP_LOGE("validateConfig", "Invalid data_offset: %d", pair.second.data_offset); - }); - } - - return value; + [entity_conf](float const& value) -> std::vector { + return Utils::str_to_bytes(entity_conf.setter, value * entity_conf.divider); }, - [pair](auto const& value) -> std::vector { - return Utils::replace_placeholders(pair.second.set, esphome::daikin_rotex_can::DC, static_cast(value)); - } + !entity_conf.setter.empty() }); } m_data_requests.removeInvalidRequests(); } -void DaikinRotexCanComponent::setup() { - ESP_LOGI(TAG, "setup"); -} - void DaikinRotexCanComponent::updateState(std::string const& id) { if (id == "thermal_power") { update_thermal_power(); @@ -314,9 +111,7 @@ void DaikinRotexCanComponent::update_thermal_power() { float value = 0; if (mode_of_operating->state == "Warmwasserbereitung" && tv != nullptr && tr != nullptr && water_flow != nullptr) { value = (tv->state - tr->state) * (4.19 * water_flow->state) / 3600.0f; - } else if (mode_of_operating->state == "Heizen" && tvbh != nullptr && tr != nullptr && water_flow != nullptr) { - value = (tvbh->state - tr->state) * (4.19 * water_flow->state) / 3600.0f; - } else if (mode_of_operating->state == "Kühlen" && tvbh != nullptr && tr != nullptr && water_flow != nullptr) { + } else if ((mode_of_operating->state == "Heizen" || mode_of_operating->state == "Kühlen") && tvbh != nullptr && tr != nullptr && water_flow != nullptr) { value = (tvbh->state - tr->state) * (4.19 * water_flow->state) / 3600.0f; } thermal_power->publish_state(value); @@ -341,9 +136,14 @@ void DaikinRotexCanComponent::custom_request(std::string const& value) { ///////////////// Selects ///////////////// void DaikinRotexCanComponent::set_generic_select(std::string const& id, std::string const& state) { - for (auto& pair : m_accessor.get_selects()) { - if (pair.second.id == id) { - m_data_requests.sendSet(pair.second.pSelect->get_name(), pair.second.map.getKey(state)); + for (auto& entity_conf : m_accessor.get_entities()) { + if (entity_conf.id == id && dynamic_cast(entity_conf.pEntity) != nullptr) { + auto it = entity_conf.map.findByValue(state); + if (it != entity_conf.map.end()) { + m_data_requests.sendSet(entity_conf.pEntity->get_name(), it->first); + } else { + ESP_LOGE(TAG, "set_generic_select(%s, %s) => Invalid value!", id.c_str(), state.c_str()); + } break; } } @@ -351,9 +151,9 @@ void DaikinRotexCanComponent::set_generic_select(std::string const& id, std::str ///////////////// Numbers ///////////////// void DaikinRotexCanComponent::set_generic_number(std::string const& id, float value) { - for (auto& pair : m_accessor.get_selects()) { - if (pair.second.id == id) { - m_data_requests.sendSet(pair.second.pSelect->get_name(), value); + for (auto& entity_conf : m_accessor.get_entities()) { + if (entity_conf.id == id && dynamic_cast(entity_conf.pEntity) != nullptr) { + m_data_requests.sendSet(entity_conf.pEntity->get_name(), value); break; } } diff --git a/components/daikin_rotex_can/daikin_rotex_can.h b/components/daikin_rotex_can/daikin_rotex_can.h index 2cd709f..f2dc574 100644 --- a/components/daikin_rotex_can/daikin_rotex_can.h +++ b/components/daikin_rotex_can/daikin_rotex_can.h @@ -32,8 +32,6 @@ class DaikinRotexCanComponent: public Component { // Buttons void dhw_run(); - void validateConfig(); - Accessor& getAccessor() { return m_accessor; } void handle(uint32_t can_id, std::vector const& data); diff --git a/components/daikin_rotex_can/request.cpp b/components/daikin_rotex_can/request.cpp index 66c9d30..a635a05 100644 --- a/components/daikin_rotex_can/request.cpp +++ b/components/daikin_rotex_can/request.cpp @@ -1,4 +1,5 @@ #include "esphome/components/daikin_rotex_can/request.h" +#include "esphome/components/esp32_can/esp32_can.h" #include "esphome/core/hal.h" namespace esphome { @@ -47,27 +48,29 @@ bool TRequest::handle(uint32_t can_id, std::vector const& responseData, return false; } -void TRequest::sendGet(esphome::esp32_can::ESP32Can* pCanBus) { +bool TRequest::sendGet(esphome::esp32_can::ESP32Can* pCanBus) { if (pCanBus == nullptr) { ESP_LOGE("sendGet", "pCanbus is null!"); - return; + return false; } if (hasSendGet()) { const uint32_t can_id = 0x680; const bool use_extended_id = false; pCanBus->send_data(can_id, use_extended_id, { m_data.begin(), m_data.end() }); - Utils::log("sendGet", "%s can_id<%s> data<%s>", - getName().c_str(), Utils::to_hex(can_id).c_str(), Utils::to_hex(m_data).c_str()); + Utils::log("sendGet", "%s|%s can_id<%s> data<%s>", + getName().c_str(), m_id.c_str(), Utils::to_hex(can_id).c_str(), Utils::to_hex(m_data).c_str()); m_last_request = millis(); + return true; } + return false; } -void TRequest::sendSet(esphome::esp32_can::ESP32Can* pCanBus, float value) { +bool TRequest::sendSet(esphome::esp32_can::ESP32Can* pCanBus, float value) { if (pCanBus == nullptr) { ESP_LOGE("sendSet", "pCanbus is null!"); - return; + return false; } const uint32_t can_id = 0x680; @@ -76,10 +79,12 @@ void TRequest::sendSet(esphome::esp32_can::ESP32Can* pCanBus, float value) { auto data = m_set_lambda(value); pCanBus->send_data(can_id, use_extended_id, { data.begin(), data.end() }); - Utils::log("sendSet", "name<%s> value<%f> can_id<%s> data<%s>", - getName().c_str(), value, Utils::to_hex(can_id).c_str(), Utils::to_hex(data).c_str()); + Utils::log("sendSet", "name<%s|%s> value<%f> can_id<%s> data<%s>", + getName().c_str(), m_id.c_str(), value, Utils::to_hex(can_id).c_str(), Utils::to_hex(data).c_str()); sendGet(pCanBus); + + return true; } } diff --git a/components/daikin_rotex_can/request.h b/components/daikin_rotex_can/request.h index c300312..05a4f66 100644 --- a/components/daikin_rotex_can/request.h +++ b/components/daikin_rotex_can/request.h @@ -3,7 +3,6 @@ #include "esphome/components/daikin_rotex_can/types.h" #include "esphome/components/daikin_rotex_can/utils.h" #include "esphome/components/esp32_can/esp32_can.h" -#include "esphome/components/sensor/sensor.h" #include namespace esphome { @@ -14,7 +13,6 @@ const uint16_t DC = 0xFFFF; // Don't care class TRequest { public: - using TEntityProvider = std::function; using TGetLambda = std::function const&)>; using TSetLambda = std::function(float const&)>; public: @@ -22,14 +20,14 @@ class TRequest std::string const& id, std::array const& data, std::array const& expected_reponse, - TEntityProvider entity_provider, + EntityBase* entity, TGetLambda lambda, TSetLambda setLambda, bool setter) : m_id(id) , m_data(data) , m_expected_reponse(expected_reponse) - , m_entity_provider(entity_provider) + , m_entity(entity) , m_lambda(lambda) , m_set_lambda(setLambda) , m_last_update(0u) @@ -38,52 +36,22 @@ class TRequest { } - TRequest( - std::string const& id, - std::array const& data, - std::array const& expected_reponse, - TEntityProvider entity_provider, - TGetLambda lambda) - : TRequest(id, data, expected_reponse, entity_provider, lambda, [](float) -> std::vector { return {}; }, false) - { - } - - TRequest( - std::string const& id, - std::array const& data, - std::array const& expected_reponse, - TEntityProvider entity_provider, - TGetLambda lambda, - TSetLambda setLambda) - : TRequest(id, data, expected_reponse, entity_provider, lambda, setLambda, true) - { - } - - TRequest( - std::string const& id, - TEntityProvider entity_provider, - TSetLambda setLambda) - : TRequest(id, {}, {}, entity_provider, [](auto const&) -> DataType { return 0u; }, setLambda, true) - { - } - std::string const& get_id() const { return m_id; } std::string getName() const { - EntityBase* pEntity = m_entity_provider(); - return pEntity != nullptr ? pEntity->get_name().str() : ""; + return m_entity != nullptr ? m_entity->get_name().str() : ""; } EntityBase* getEntity() const { - return m_entity_provider(); + return m_entity; } sensor::Sensor* get_sensor() const { - return dynamic_cast(m_entity_provider()); + return dynamic_cast(m_entity); } bool isGetSupported() const { - return m_entity_provider() != nullptr; + return m_entity != nullptr; } uint32_t getLastUpdate() const { @@ -101,8 +69,8 @@ class TRequest bool isMatch(uint32_t can_id, std::vector const& responseData) const; bool handle(uint32_t can_id, std::vector const& responseData, uint32_t timestamp); - void sendGet(esphome::esp32_can::ESP32Can* pCanBus); - void sendSet(esphome::esp32_can::ESP32Can* pCanBus, float value); + bool sendGet(esphome::esp32_can::ESP32Can* pCanBus); + bool sendSet(esphome::esp32_can::ESP32Can* pCanBus, float value); bool inProgress() const; bool isSetter() const { return m_setter; } @@ -119,7 +87,7 @@ class TRequest std::string m_id; std::array m_data; std::array m_expected_reponse; - TEntityProvider m_entity_provider; + EntityBase* m_entity; TGetLambda m_lambda; std::function(float const&)> m_set_lambda; uint32_t m_last_update; diff --git a/components/daikin_rotex_can/requests.cpp b/components/daikin_rotex_can/requests.cpp index cae03bc..314e4e7 100644 --- a/components/daikin_rotex_can/requests.cpp +++ b/components/daikin_rotex_can/requests.cpp @@ -28,28 +28,11 @@ void TRequests::removeInvalidRequests() { bool TRequests::sendNextPendingGet() { TRequest* pRequest = getNextRequestToSend(); if (pRequest != nullptr) { - pRequest->sendGet(m_pCanbus); - return true; + return pRequest->sendGet(m_pCanbus); } return false; } -void TRequests::sendGet(std::string const& request_name) { - const auto it = std::find_if( - m_requests.begin(), - m_requests.end(), - [& request_name](auto& request) { - return request.getName() == request_name && request.isGetSupported(); - } - ); - - if (it != m_requests.end()) { - it->sendGet(m_pCanbus); - } else { - ESP_LOGE("sendGet", "Unknown request: %s", request_name.c_str()); - } -} - void TRequests::sendSet(std::string const& request_name, float value) { const auto it = std::find_if(m_requests.begin(), m_requests.end(), [&request_name](auto& request) { return request.isSetter() && request.getName() == request_name; } @@ -79,6 +62,12 @@ TRequest* TRequests::getNextRequestToSend() { const uint32_t timestamp = millis(); const uint32_t interval = static_cast(10/*id(update_interval).state*/) * 1000; + for (auto& request : m_requests) { + if (request.hasSendGet() && request.inProgress()) { + return nullptr; + } + } + for (auto& request : m_requests) { if (request.hasSendGet()) { if ((timestamp > (request.getLastUpdate() + interval)) && !request.inProgress()) { diff --git a/components/daikin_rotex_can/requests.h b/components/daikin_rotex_can/requests.h index db880ba..68861eb 100644 --- a/components/daikin_rotex_can/requests.h +++ b/components/daikin_rotex_can/requests.h @@ -26,7 +26,6 @@ class TRequests { text_sensor::TextSensor* get_text_sensor(std::string const& id); bool sendNextPendingGet(); - void sendGet(std::string const& request_name); void sendSet(std::string const& request_name, float value); void handle(uint32_t can_id, std::vector const& responseData); @@ -68,11 +67,11 @@ inline EntityBase* TRequests::get_entity(std::string const& id) { } inline sensor::Sensor* TRequests::get_sensor(std::string const& id) { - return dynamic_cast(get_entity(id)); + return Utils::toSensor(get_entity(id)); } inline text_sensor::TextSensor* TRequests::get_text_sensor(std::string const& id) { - return dynamic_cast(get_entity(id)); + return Utils::toTextSensor(get_entity(id)); } diff --git a/components/daikin_rotex_can/utils.cpp b/components/daikin_rotex_can/utils.cpp index 7b653bc..3b1757c 100644 --- a/components/daikin_rotex_can/utils.cpp +++ b/components/daikin_rotex_can/utils.cpp @@ -5,6 +5,7 @@ namespace esphome { namespace daikin_rotex_can { std::string Utils::g_log_filter = ""; +static const char* TAG = "Utils"; bool Utils::find(std::string const& haystack, std::string const& needle) { auto it = std::search( @@ -30,14 +31,6 @@ std::vector Utils::split(std::string const& str) { return result; } -bool Utils::is_number(const std::string& str) { - return !str.empty() && std::find_if( - str.begin(), - str.end(), - [](unsigned char chr) { return !std::isdigit(chr); } - ) == str.end(); -} - std::string Utils::to_hex(uint32_t value) { char hex_string[20]; sprintf(hex_string, "0x%02X", value); @@ -109,70 +102,83 @@ std::map Utils::str_to_map(const std::string& input) { return result; } -std::vector Utils::str_to_bytes(const std::string& str, uint16_t value) { +std::vector Utils::str_to_bytes(const std::string& input, uint16_t value) { std::vector result; - std::stringstream ss(str); + std::istringstream stream(input); std::string byteStr; - // High- und Low-Bytes des Werts berechnen - uint8_t high_byte = (value >> 8) & 0xFF; // High byte - uint8_t low_byte = value & 0xFF; // Low byte + uint8_t hiByte = (value >> 8) & 0xFF; + uint8_t loByte = value & 0xFF; - bool replaced_double_placeholder = false; + int placeholderCount = 0; - // String in Byte-Blöcke zerlegen - while (ss >> byteStr) { + while (stream >> byteStr) { if (byteStr == "__") { - if (!replaced_double_placeholder) { - // Prüfen, ob ein weiterer "__" folgt (doppelter Platzhalter) - std::string next_byte_str; - std::streampos pos = ss.tellg(); // Position speichern - if (ss >> next_byte_str && next_byte_str == "__") { - // Doppelte Platzhalter gefunden, durch High- und Low-Byte ersetzen - result.push_back(high_byte); - result.push_back(low_byte); - replaced_double_placeholder = true; - } else { - // Einzelner Platzhalter, rückgängig machen und nur ein Byte ersetzen - result.push_back(high_byte); // oder ein anderes byte - replaced_double_placeholder = true; - ss.seekg(pos); // Position zurücksetzen, um den nächsten Wert zu verarbeiten - } - } else { - // Einzelner Platzhalter, nur ein Byte ersetzen - result.push_back(low_byte); + ++placeholderCount; + if (placeholderCount == 2) { + result.push_back(hiByte); + result.push_back(loByte); + placeholderCount = 0; } } else { - // Konvertiere Hex-Strings in uint8_t und füge sie dem Ergebnis hinzu - uint8_t byte = static_cast(std::stoi(byteStr, nullptr, 16)); - result.push_back(byte); + if (placeholderCount == 1) { + result.push_back(loByte); + placeholderCount = 0; + } + result.push_back(static_cast(std::stoi(byteStr, nullptr, 16))); } } + if (placeholderCount == 1) { + result.push_back(loByte); + } + return result; } -std::vector Utils::replace_placeholders(const std::array& arr, uint16_t token, uint16_t value) { - std::vector result; +sensor::Sensor* Utils::toSensor(EntityBase* pEntity) { + if (sensor::Sensor* pSensor = dynamic_cast(pEntity)) { + return pSensor; + } else { + ESP_LOGE(TAG, "Entity is not a sensor: %s", pEntity->get_name().c_str()); + return nullptr; + } +} - uint8_t high_byte = (value >> 8) & 0xFF; - uint8_t low_byte = value & 0xFF; - - for (size_t i = 0; i < arr.size(); ++i) { - if (arr[i] == token) { - if (i + 1 < arr.size() && arr[i + 1] == token) { - result.push_back(high_byte); - result.push_back(low_byte); - ++i; - } else { - result.push_back(low_byte); - } - } else { - result.push_back(arr[i]); - } +text_sensor::TextSensor* Utils::toTextSensor(EntityBase* pEntity) { + if (text_sensor::TextSensor* pTextSensor = dynamic_cast(pEntity)) { + return pTextSensor; + } else { + ESP_LOGE(TAG, "Entity is not a text sensor: %s", pEntity->get_name().c_str()); + return nullptr; } +} - return result; +binary_sensor::BinarySensor* Utils::toBinarySensor(EntityBase* pEntity) { + if (binary_sensor::BinarySensor* pBinarySensor = dynamic_cast(pEntity)) { + return pBinarySensor; + } else { + ESP_LOGE(TAG, "Entity is not a binary sensor: %s", pEntity->get_name().c_str()); + return nullptr; + } +} + +select::Select* Utils::toSelect(EntityBase* pEntity) { + if (select::Select* pSelect = dynamic_cast(pEntity)) { + return pSelect; + } else { + ESP_LOGE(TAG, "Entity is not a select: %s", pEntity->get_name().c_str()); + return nullptr; + } +} + +number::Number* Utils::toNumber(EntityBase* pEntity) { + if (number::Number* pNumber = dynamic_cast(pEntity)) { + return pNumber; + } else { + ESP_LOGE(TAG, "Entity is not a number: %s", pEntity->get_name().c_str()); + return nullptr; + } } diff --git a/components/daikin_rotex_can/utils.h b/components/daikin_rotex_can/utils.h index 64b132d..d3b78dd 100644 --- a/components/daikin_rotex_can/utils.h +++ b/components/daikin_rotex_can/utils.h @@ -1,5 +1,10 @@ #pragma once +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/components/select/select.h" +#include "esphome/components/number/number.h" #include "esphome/core/log.h" #include #include @@ -46,14 +51,12 @@ class Utils { static bool find(std::string const& haystack, std::string const& needle); static std::vector split(std::string const& str); - static bool is_number(const std::string& str); static std::string to_hex(uint32_t value); static std::vector str_to_bytes(const std::string& str); static std::array str_to_bytes_array8(const std::string& str); static std::array str_to_bytes_array16(const std::string& str); static std::map str_to_map(const std::string& input); static std::vector str_to_bytes(const std::string& str, uint16_t value); - static std::vector replace_placeholders(const std::array& arr, uint16_t token, uint16_t value); template static void log(std::string const& tag, std::string const& str_format, Args... args) { @@ -73,6 +76,12 @@ class Utils { } } + static sensor::Sensor* toSensor(EntityBase*); + static text_sensor::TextSensor* toTextSensor(EntityBase*); + static binary_sensor::BinarySensor* toBinarySensor(EntityBase*); + static select::Select* toSelect(EntityBase*); + static number::Number* toNumber(EntityBase*); + static std::string g_log_filter; };