diff --git a/README.md b/README.md index 2accc22..e564eb7 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,14 @@ sensor: - platform: wmbus meter_id: 0x22113366 type: vario411 + +text_sensor: + - platform: wmbus + meter_id: 0xABCD1122 + type: izar + sensors: + - name: "Izar current_alarms" + field: "current_alarms" ``` @@ -250,7 +258,7 @@ In wmbus platform: > **_NOTE:_** MQTT can be defined in wmbus component or in [ESPHome level](https://esphome.io/components/mqtt.html). -Meter/sensor: +sensor: - **meter_id** (*Optional*, int): Meter ID. Can be specified as decimal or hex. - **type** (*Optional*, string): Meter type. When not defined, driver will be detected from telegram. @@ -260,7 +268,19 @@ Meter/sensor: - **name** (*Optional*, string): The name for the sensor. At least one of **id** and **name** must be specified. - **field** (*Optional*): Field from decoded telegram (without unit). If **field** is not present then **name** is used. - **unit_of_measurement** (**Required**): Unit for field defined above. - - All other options from [Sensor](https://esphome.io/components/sensor/index.html#config-sensor). + - All other options from [Sensor](https://esphome.io/components/sensor/). + + +text_sensor: + +- **meter_id** (*Optional*, int): Meter ID. Can be specified as decimal or hex. +- **type** (*Optional*, string): Meter type. When not defined, driver will be detected from telegram. +- **key** (*Optional*): Key for meter, used in payload decoding process. Defaults to ``""``. +- **sensors** (*Optional*): + - **id** (*Optional*, string): Manually specify the ID for code generation. At least one of **id** and **name** must be specified. + - **name** (*Optional*, string): The name for the sensor. At least one of **id** and **name** must be specified. + - **field** (*Optional*): Text field from decoded telegram. If **field** is not present then **name** is used. + - All other options from [Text Sensor](https://esphome.io/components/text_sensor/). ------------------------ diff --git a/components/wmbus/__init__.py b/components/wmbus/__init__.py index 0bccce0..226c708 100755 --- a/components/wmbus/__init__.py +++ b/components/wmbus/__init__.py @@ -47,7 +47,7 @@ CODEOWNERS = ["@SzczepanLeon"] DEPENDENCIES = ["time"] -AUTO_LOAD = ["sensor"] +AUTO_LOAD = ["sensor", "text_sensor"] wmbus_ns = cg.esphome_ns.namespace('wmbus') WMBusComponent = wmbus_ns.class_('WMBusComponent', cg.Component) diff --git a/components/wmbus/meters.cpp b/components/wmbus/meters.cpp index 238135c..0606b6a 100644 --- a/components/wmbus/meters.cpp +++ b/components/wmbus/meters.cpp @@ -1144,6 +1144,11 @@ bool MeterCommonImplementation::hasStringValue(FieldInfo* fi) return string_values_.count(fi->vname()) != 0; } +bool MeterCommonImplementation::hasStringValue(string name) +{ + return string_values_.count(name) != 0; +} + string MeterCommonImplementation::getMyStringValue(string name) { for (auto& p : string_values_) diff --git a/components/wmbus/meters.h b/components/wmbus/meters.h index e2a56fc..08950ac 100644 --- a/components/wmbus/meters.h +++ b/components/wmbus/meters.h @@ -343,6 +343,8 @@ struct Meter virtual std::string getStringValue(FieldInfo* fi) = 0; virtual std::string decodeTPLStatusByte(uchar sts) = 0; + virtual bool hasStringValue(string name) = 0; + virtual void onUpdate(std::function cb) = 0; virtual int numUpdates() = 0; diff --git a/components/wmbus/meters_common_implementation.h b/components/wmbus/meters_common_implementation.h index d465b51..c7736c0 100644 --- a/components/wmbus/meters_common_implementation.h +++ b/components/wmbus/meters_common_implementation.h @@ -206,6 +206,7 @@ struct MeterCommonImplementation : public virtual Meter bool hasValue(FieldInfo* fi); bool hasNumericValue(FieldInfo* fi); bool hasStringValue(FieldInfo* fi); + bool hasStringValue(string name); std::string decodeTPLStatusByte(uchar sts); diff --git a/components/wmbus/sensor/__init__.py b/components/wmbus/sensor/__init__.py index 572fce1..774c053 100755 --- a/components/wmbus/sensor/__init__.py +++ b/components/wmbus/sensor/__init__.py @@ -7,27 +7,6 @@ CONF_TYPE, CONF_KEY, CONF_NAME, - UNIT_DECIBEL_MILLIWATT, - DEVICE_CLASS_SIGNAL_STRENGTH, - STATE_CLASS_MEASUREMENT, - UNIT_CUBIC_METER, - DEVICE_CLASS_WATER, - STATE_CLASS_TOTAL_INCREASING, - UNIT_KILOWATT_HOURS, - DEVICE_CLASS_ENERGY, - DEVICE_CLASS_POWER, - STATE_CLASS_TOTAL, - UNIT_KILOWATT, - UNIT_EMPTY, - DEVICE_CLASS_EMPTY, - UNIT_CELSIUS, - DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_GAS, - UNIT_VOLT, - DEVICE_CLASS_VOLTAGE, - ENTITY_CATEGORY_DIAGNOSTIC, - UNIT_SECOND, - DEVICE_CLASS_TIMESTAMP, CONF_UNIT_OF_MEASUREMENT, ) @@ -87,11 +66,9 @@ def my_key(value): async def to_code(config): if config[CONF_TYPE]: cg.add_platformio_option("build_src_filter", [f"+<**/wmbus/driver_{config[CONF_TYPE].lower()}.cpp>"]) - var = cg.new_Pvariable(config[CONF_LISTENER_ID], - config[CONF_METER_ID], - config[CONF_TYPE].lower(), - config[CONF_KEY]) if config[CONF_METER_ID]: + wmbus = await cg.get_variable(config[CONF_WMBUS_ID]) + cg.add(wmbus.register_wmbus_listener(config[CONF_METER_ID], config[CONF_TYPE].lower(), config[CONF_KEY])) for s in config.get(CONF_SENSORS, []): if CONF_UNIT_OF_MEASUREMENT not in s: print(color(Fore.RED, f"unit_of_measurement not defined for sensor '{s[CONF_NAME]}'!")) @@ -102,6 +79,4 @@ async def to_code(config): field = s[CONF_NAME].lower() unit = s[CONF_UNIT_OF_MEASUREMENT] sens = await sensor.new_sensor(s) - cg.add(var.add_sensor(field, unit, sens)) - wmbus = await cg.get_variable(config[CONF_WMBUS_ID]) - cg.add(wmbus.register_wmbus_listener(var)) + cg.add(wmbus.add_sensor(config[CONF_METER_ID], field, unit, sens)) diff --git a/components/wmbus/text_sensor/__init__.py b/components/wmbus/text_sensor/__init__.py new file mode 100755 index 0000000..a1bb11e --- /dev/null +++ b/components/wmbus/text_sensor/__init__.py @@ -0,0 +1,77 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.log import Fore, color +from esphome.const import ( + CONF_ID, + CONF_TYPE, + CONF_KEY, + CONF_NAME, +) + +AUTO_LOAD = ["wmbus"] + +CONF_METER_ID = "meter_id" +CONF_LISTENER_ID = "listener_id" +CONF_WMBUS_ID = "wmbus_id" +CONF_FIELD = "field" +CONF_SENSORS = 'sensors' + +from .. import ( + WMBusComponent, + wmbus_ns +) + +CODEOWNERS = ["@SzczepanLeon"] + +WMBusListener = wmbus_ns.class_('WMBusListener') + +def my_key(value): + value = cv.string_strict(value) + parts = [value[i : i + 2] for i in range(0, len(value), 2)] + if (len(parts) != 16) and (len(parts) != 0): + raise cv.Invalid("Key must consist of 16 hexadecimal numbers") + parts_int = [] + if any(len(part) != 2 for part in parts): + raise cv.Invalid("Key must be format XX") + for part in parts: + try: + parts_int.append(int(part, 16)) + except ValueError: + # pylint: disable=raise-missing-from + raise cv.Invalid("Key must be hex values from 00 to FF") + return "".join(f"{part:02X}" for part in parts_int) + + +SENSOR_SCHEMA = text_sensor.text_sensor_schema( + # +).extend( + { + cv.Optional(CONF_FIELD, default=""): cv.string_strict, + } +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_LISTENER_ID): cv.declare_id(WMBusListener), + cv.GenerateID(CONF_WMBUS_ID): cv.use_id(WMBusComponent), + cv.Optional(CONF_METER_ID, default=0): cv.hex_int, + cv.Optional(CONF_TYPE, default=""): cv.string_strict, + cv.Optional(CONF_KEY, default=""): my_key, + cv.Optional(CONF_SENSORS): cv.ensure_list(SENSOR_SCHEMA), + } +).extend(cv.COMPONENT_SCHEMA) + +async def to_code(config): + if config[CONF_TYPE]: + cg.add_platformio_option("build_src_filter", [f"+<**/wmbus/driver_{config[CONF_TYPE].lower()}.cpp>"]) + if config[CONF_METER_ID]: + wmbus = await cg.get_variable(config[CONF_WMBUS_ID]) + cg.add(wmbus.register_wmbus_listener(config[CONF_METER_ID], config[CONF_TYPE].lower(), config[CONF_KEY])) + for s in config.get(CONF_SENSORS, []): + if (s[CONF_FIELD]): + field = s[CONF_FIELD].lower() + else: + field = s[CONF_NAME].lower() + sens = await text_sensor.new_text_sensor(s) + cg.add(wmbus.add_sensor(config[CONF_METER_ID], field, sens)) diff --git a/components/wmbus/version.h b/components/wmbus/version.h index 0418eda..2be3b86 100755 --- a/components/wmbus/version.h +++ b/components/wmbus/version.h @@ -1,4 +1,4 @@ #ifndef MY_VERSION -#define MY_VERSION "4.0.11" +#define MY_VERSION "4.1.0" #define WMBUSMETERS_VERSION "1.17.1-b8f4a945" #endif diff --git a/components/wmbus/wmbus.cpp b/components/wmbus/wmbus.cpp index 6af515d..559ed2f 100755 --- a/components/wmbus/wmbus.cpp +++ b/components/wmbus/wmbus.cpp @@ -141,7 +141,7 @@ namespace wmbus { field.second->publish_state(mbus_data.rssi); } else if (field.second->get_unit_of_measurement().empty()) { - ESP_LOGW(TAG, "Fields without unit not supported yet!"); + ESP_LOGW(TAG, "Fields without unit not supported as sensor, please switch to text_sensor."); } else { Unit field_unit = toUnit(field.second->get_unit_of_measurement()); @@ -159,6 +159,15 @@ namespace wmbus { } } } + for (auto const& field : sensor->text_fields) { + if (meter->hasStringValue(field.first)) { + std::string value = meter->getMyStringValue(field.first); + field.second->publish_state(value); + } + else { + ESP_LOGW(TAG, "Can't get requested field '%s'", field.first.c_str()); + } + } #ifdef USE_WMBUS_MQTT std::string json; meter->printJsonMeter(&t, &json, false); @@ -191,8 +200,11 @@ namespace wmbus { } } - void WMBusComponent::register_wmbus_listener(WMBusListener *listener) { - this->wmbus_listeners_[listener->id] = listener; + void WMBusComponent::register_wmbus_listener(const uint32_t meter_id, const std::string type, const std::string key) { + if (this->wmbus_listeners_.count(meter_id) == 0) { + WMBusListener *listener = new wmbus::WMBusListener(meter_id, type, key); + this->wmbus_listeners_.insert({meter_id, listener}); + } } void WMBusComponent::led_blink() { @@ -381,6 +393,10 @@ namespace wmbus { ESP_LOGCONFIG(TAG, " Field: '%s'", ele.first.first.c_str()); LOG_SENSOR(" ", "Name:", ele.second); } + for (const auto &ele : this->text_fields) { + ESP_LOGCONFIG(TAG, " Text field: '%s'", ele.first.c_str()); + LOG_TEXT_SENSOR(" ", "Name:", ele.second); + } } WMBusListener::WMBusListener(const uint32_t id, const std::string type, const std::string key) { diff --git a/components/wmbus/wmbus.h b/components/wmbus/wmbus.h index 8add3b9..346e055 100755 --- a/components/wmbus/wmbus.h +++ b/components/wmbus/wmbus.h @@ -6,6 +6,7 @@ #include "esphome/core/component.h" #include "esphome/components/network/ip_address.h" #include "esphome/components/sensor/sensor.h" +#include "esphome/components/text_sensor/text_sensor.h" #ifdef USE_WMBUS_MQTT #include #elif defined(USE_MQTT) @@ -72,6 +73,10 @@ namespace wmbus { void add_sensor(std::string field, std::string unit, sensor::Sensor *sensor) { this->fields[std::pair(field, unit)] = sensor; }; + std::map text_fields{}; + void add_sensor(std::string field, text_sensor::TextSensor *sensor) { + this->text_fields[field] = sensor; + }; void dump_config(); int char_to_int(char input); @@ -102,7 +107,7 @@ namespace wmbus { float get_setup_priority() const override { return setup_priority::LATE; } void set_led_pin(GPIOPin *led) { this->led_pin_ = led; } void set_led_blink_time(uint32_t led_blink_time) { this->led_blink_time_ = led_blink_time; } - void register_wmbus_listener(WMBusListener *listener); + void register_wmbus_listener(const uint32_t meter_id, const std::string type, const std::string key); void add_cc1101(InternalGPIOPin *mosi, InternalGPIOPin *miso, InternalGPIOPin *clk, InternalGPIOPin *cs, InternalGPIOPin *gdo0, InternalGPIOPin *gdo2, @@ -116,6 +121,16 @@ namespace wmbus { this->frequency_ = frequency; this->sync_mode_ = sync_mode; } + void add_sensor(uint32_t meter_id, std::string field, std::string unit, sensor::Sensor *sensor) { + if (this->wmbus_listeners_.count(meter_id) != 0) { + this->wmbus_listeners_[meter_id]->add_sensor(field, unit, sensor); + } + } + void add_sensor(uint32_t meter_id, std::string field, text_sensor::TextSensor *sensor) { + if (this->wmbus_listeners_.count(meter_id) != 0) { + this->wmbus_listeners_[meter_id]->add_sensor(field, sensor); + } + } #ifdef USE_ETHERNET void set_eth(ethernet::EthernetComponent *eth_component) { this->net_component_ = eth_component; } #elif defined(USE_WIFI)