From fdd55a145f248363eb6dbd6ad53b423821997c7a Mon Sep 17 00:00:00 2001 From: Hilbrand Bouwkamp Date: Sat, 30 Oct 2021 18:04:42 +0200 Subject: [PATCH] [dsmr] Add support for Austrian meters, Fix for channel id detection (#11458) * Fix fix for channel id detection, Added missing channels to emucs electra - M-bus channels are dynamic and present in the obis id. The binding had most channel types fixed because most of the time these channels are the same. However the device identifier is the same for multiple devices. But the binding only registered only one and while the channel id was derived from this obis data. For detected meters this resulted in the channel id to be the same if there are multiple devices. This change looks at the channel id to assign it to the found device. This is a bit tricky because the general device has no channel and has channels that have different id's. So the binding needs to cover that case. This change also adds some optional channels to the emucs electra meter. Signed-off-by: Hilbrand Bouwkamp * [dsmr] Add support for Austrian meters Improved the work done in pr #11193 Also-by: Thomas Signed-off-by: Hilbrand Bouwkamp * [dsmr] Added Null handling annotations. --- bundles/org.openhab.binding.dsmr/README.md | 283 +++++++++--------- .../connector/DSMRConnectorErrorEvent.java | 3 + .../internal/device/cosem/CosemObject.java | 18 +- .../device/cosem/CosemObjectFactory.java | 34 +-- .../device/cosem/CosemObjectType.java | 188 ++++++------ .../internal/device/cosem/CosemQuantity.java | 12 +- .../internal/device/cosem/OBISIdentifier.java | 103 +++---- .../device/p1telegram/P1TelegramParser.java | 63 ++-- .../internal/discovery/DSMRMeterDetector.java | 19 +- .../discovery/DSMRMeterDiscoveryService.java | 23 +- .../internal/handler/DSMRBridgeHandler.java | 8 +- .../internal/handler/DSMRMeterHandler.java | 11 +- .../dsmr/internal/meter/DSMRMeter.java | 13 +- .../internal/meter/DSMRMeterDescriptor.java | 1 - .../dsmr/internal/meter/DSMRMeterKind.java | 19 +- .../dsmr/internal/meter/DSMRMeterType.java | 80 +++-- .../main/resources/OH-INF/binding/binding.xml | 2 +- .../config/configuration_parameters.xml | 6 +- .../OH-INF/thing/channeltypes_electricity.xml | 32 +- .../thing/meter_electricity_dsmr_v4.xml | 2 +- .../thing/meter_electricity_dsmr_v4_0_4.xml | 2 +- .../thing/meter_electricity_emucs_v1_0.xml | 6 + .../thing/meter_electricity_smarty_v1.xml | 1 + .../meter_electricity_smarty_v1_austria.xml | 37 +++ .../dsmr/internal/TelegramReaderUtil.java | 12 +- .../p1telegram/P1TelegramParserTest.java | 6 +- .../discovery/DSMRMeterDetectorTest.java | 63 ++-- .../dsmr/internal/meter/DSMRMeterTest.java | 19 +- .../dsmr/internal/dsmr_50_austria.telegram | 21 ++ .../binding/dsmr/internal/flu5_extra.telegram | 33 ++ 30 files changed, 655 insertions(+), 465 deletions(-) create mode 100644 bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_smarty_v1_austria.xml create mode 100644 bundles/org.openhab.binding.dsmr/src/test/resources/org/openhab/binding/dsmr/internal/dsmr_50_austria.telegram create mode 100644 bundles/org.openhab.binding.dsmr/src/test/resources/org/openhab/binding/dsmr/internal/flu5_extra.telegram diff --git a/bundles/org.openhab.binding.dsmr/README.md b/bundles/org.openhab.binding.dsmr/README.md index a99d1416ba4e0..a97be10defc1a 100644 --- a/bundles/org.openhab.binding.dsmr/README.md +++ b/bundles/org.openhab.binding.dsmr/README.md @@ -1,7 +1,7 @@ # DSMR Binding -The DSMR-binding is targeted towards Dutch, Belgium and Luxembourger users having a smart meter (Dutch: 'Slimme meter'). -Data of Dutch/Belgium/Luxembourg smart meters can be obtained via the P1-port. +The DSMR-binding is targeted towards Dutch, Belgium, Luxembourger and Austrian users having a smart meter (Dutch: 'Slimme meter'). +Data of Dutch/Belgium/Luxembourg/Austrian smart meters can be obtained via the P1-port. When connecting this port from a serial port the data can be read out. This binding reads the P1-port of: @@ -9,6 +9,7 @@ This binding reads the P1-port of: * Dutch Smart Meters that comply to NTA8130, DSMR v2.1, DSMR v2.2, DSMR v3.0, DSMR v4.0, DSMR v4.04, and DSMR 5.0. * Belgium Smart Meters that comply to e-MUCS v1.0. * Luxembourg’s electricity meter "Smarty" that comply to V1.0. +* Austrian electricity meters. Although DSMR v4.2 is not an official specification, the binding has support for this version. @@ -24,8 +25,10 @@ The P1-port is a serial port. To configure the serial port within openHAB see th ### dsmrBridge (The Netherlands/Belgium) `dsmrBridge`: This is the device that communicated between the binding (serial) and its internal meters. -You always have to have a 'Dutch/Belgium Smart Meter'-bridge. The bridge contains the serial port configuration. -Specific meters are bound via the bridge to the smart meter. A smart meter consists typically out of minimal 2 meters. +You always have to have a 'Dutch/Belgium Smart Meter'-bridge. +The bridge contains the serial port configuration. +Specific meters are bound via the bridge to the smart meter. +A smart meter consists typically out of minimal 2 meters. A generic meter and the electricity meter. Each meter is bound to the DSMR protocol the physical meter supports. For each meter it is possible to set a refresh rate at which the status is updated. The physical meter might update with a high frequency per second, while it is desired to have only values per minute. @@ -34,10 +37,11 @@ The Belgium e-MUCS protocol is an extension to the DSMR standard. Belgium meters have `emucs` in the thing name. Due to it similarities the bridge for Belgium meters is also a dsmrBridge. -### smartyBridge (Luxembourg) +### smartyBridge (Luxembourg, Austria) `smartyBridge`: This is the device that communicated between the binding (serial) and its internal meters. -You always have to have a 'Smarty Smart Meter'-bridge. The bridge contains the serial port configuration. +You always have to have a 'Smarty Smart Meter'-bridge. +The bridge contains the serial port configuration. ## Discovery @@ -162,134 +166,139 @@ The following channels are supported: - O channel is supported only if the device has this functionality -| Channel Type ID | Item Type | Description | Ace4000 | DSMR V2.1 | DSMR V2.2 | DSMR V3.0 | DSMR V4.0 | DSMR V4.0.4 | DSMR V4.2 | DSMR V5 |SMARTY V1.0 | e-MUCS V1.0 | -|--------------------------------------------------|--------------------------|------------------------------------------------------------------------|---------|-----------|-----------|-----------|-----------|-------------|-----------|---------|-------------|-------------| -| | | **Channels for the generic device** | | | | | | | | | | | -| `p1_text_code` | String | Text code from the device | - | Y | Y | Y | Y | Y | Y | - | - | - | -| `p1_text_string` | String | Text string from the device | - | Y | Y | Y | Y | Y | Y | Y | Y | Y | -| `p1_version_output` | String | Version information (most times this refers to the DSMR specification) | - | - | - | - | Y | Y | Y | Y | Y | - | -| `p1_emucs_version_output` | String | e-MUCS version information | - | - | - | - | - | - | - | - | Y | y | -| `p1_timestamp` | DateTime | Timestamp of the last device reading | - | - | - | - | Y | Y | Y | Y | Y | Y | -| | | **Channels for the cooling meter** | | | | | | | | | | | -| `cmeter_value_v2` | Number:Energy | The total amount of cooling used in the past period (GJ) | Y | - | Y | - | - | - | - | - | - | - | -| `cmeter_value_v2_timestamp` | DateTime | Timestamp of the last meter reading | Y | - | Y | - | - | - | - | - | - | - | -| `cmeter_equipment_identifier_v2_2` | String | Equipment identifier | - | - | Y | - | - | - | - | - | - | - | -| | | **Channels for the main electricity meter** | | | | | | | | | | | -| `emeter_equipment_identifier_v2_x` | String | Electricity Equipment identifier | - | Y | Y | - | - | - | - | - | Y | - | -| `emeter_equipment_identifier` | String | Electricity Equipment identifier | - | - | - | Y | Y | Y | Y | Y | - | Y | -| `emeter_delivery_tariff0` | Number:Energy | Total amount of electricity used for tariff 0 (kWh) | Y | - | - | - | - | - | - | - | Y | - | -| `emeter_delivery_tariff1` | Number:Energy | Total amount of electricity used for tariff 1 (kWh) | Y | Y | Y | Y | Y | Y | Y | Y | - | Y | -| `emeter_delivery_tariff2` | Number:Energy | Total amount of electricity used for tariff 2 (kWh) | Y | Y | Y | Y | Y | Y | Y | Y | - | Y | -| `emeter_production_tariff0` | Number:Energy | Total amount of electricity produced for tariff 0 (kWh) | Y | - | - | - | - | - | - | - | Y | - | -| `emeter_production_tariff1` | Number:Energy | Total amount of electricity produced for tariff 1 (kWh) | Y | Y | Y | Y | Y | Y | Y | Y | - | Y | -| `emeter_production_tariff2` | Number:Energy | Total amount of electricity produced for tariff 2 (kWh) | Y | Y | Y | Y | Y | Y | Y | Y | - | Y | -| `emeter_delivery_tariff0_antifraud` | Number:Energy | Total amount of electricity used for tariff 2 [antifraud] (kWh) | Y | - | - | - | - | - | - | - | - | - | -| `emeter_delivery_tariff1_antifraud` | Number:Energy | Total amount of electricity used for tariff 1 [antifraud] (kWh) | Y | - | - | - | - | - | - | - | - | - | -| `emeter_delivery_tariff2_antifraud` | Number:Energy | Total amount of electricity used for tariff 2 [antifraud] (kWh) | Y | - | - | - | - | - | - | - | - | - | -| `emeter_total_imported_energy_register_q` | Number:Energy | Total Imported Energy (Q+) (kvarh) | - | - | - | - | - | - | - | - | Y | - | -| `emeter_total_exported_energy_register_q` | Number:Energy | Total Exported Energy (Q-) (kvarh) | - | - | - | - | - | - | - | - | Y | - | -| `emeter_tariff_indicator` | String | Current tariff indicator | Y | Y | Y | Y | Y | Y | Y | Y | - | Y | -| `emeter_treshold_a_v2_1` | Number:ElectricCurrent | Actual threshold (A) | - | Y | - | - | - | - | - | - | - | - | -| `emeter_treshold_a` | Number:ElectricCurrent | Actual threshold (A) | Y | - | Y | Y | - | - | - | - | - | - | -| `emeter_fuse_threshold_a` | Number:ElectricCurrent | Active fuse threshold (A) | - | - | - | - | - | - | - | - | - | Y | -| `emeter_treshold_kwh` | Number:Power | Actual threshold (kW) | - | - | - | - | Y | Y | - | - | - | Y | -| `emeter_switch_position_v2_1` | Number | Switch position | - | Y | - | - | - | - | - | - | - | - | -| `emeter_switch_position` | Number | Switch position | Y | - | Y | Y | Y | Y | - | - | Y | Y | -| `emeter_active_import_power` | Number:Power | Aggregate active import power (W) | Y | - | - | - | - | - | - | - | - | - | -| `emeter_actual_delivery` | Number:Power | Current power delivery (kW) | - | Y | Y | Y | Y | Y | Y | Y | Y | Y | -| `emeter_actual_production` | Number:Power | Current power production (kW) | - | - | - | Y | Y | Y | Y | Y | Y | Y | -| `emeter_actual_reactive_delivery` | Number | Actual Reactive Power Delivery (kvar) | - | - | - | - | - | - | - | - | Y | - | -| `emeter_actual_reactive_production` | Number | Actual Reactive Power Production (kvar) | - | - | - | - | - | - | - | - | Y | - | -| `emeter_active_threshold_smax` | Number | Active threshold (SMAX) (kVA) | - | - | - | - | - | - | - | - | Y | - | -| `emeter_power_failures` | Number | Number of power failures | - | - | - | - | Y | Y | Y | Y | Y | - | -| `emeter_long_power_failures` | Number | Number of long power failures | - | - | - | - | Y | Y | Y | Y | - | - | -| `emeter_power_failure_log_entries` | Number | Number of entries in the power failure log | - | - | - | - | Y | Y | Y | Y | Y | - | -| `emeter_power_failure_log_timestamp[x]` *note 2* | DateTime | Timestamp for entry [x] in the power failure log | - | - | - | - | Y | Y | Y | Y | Y | - | -| `emeter_power_failure_log_duration[x]` *note 2* | Number:Time | Duration for entry [x] the power failure log | - | - | - | - | Y | Y | Y | Y | Y | - | -| `emeter_voltage_sags_l1` | Number | Number of voltage sags L1 | - | - | - | - | Y | Y | Y | Y | Y | - | -| `emeter_voltage_sags_l2` | Number | Number of voltage sags L2 | - | - | - | - | O | O | O | O | O | - | -| `emeter_voltage_sags_l3` | Number | Number of voltage sags L3 | - | - | - | - | O | O | O | O | O | - | -| `emeter_voltage_swells_l1` | Number | Number of voltage swells L1 | - | - | - | - | Y | Y | Y | Y | - | - | -| `emeter_voltage_swells_l2` | Number | Number of voltage swells L2 | - | - | - | - | O | O | O | O | - | - | -| `emeter_voltage_swells_l3` | Number | Number of voltage swells L3 | - | - | - | - | O | O | O | O | - | - | -| `emeter_instant_current_l1` | Number:ElectricCurrent | Instant Current L1 (A) | - | - | - | - | Y | Y | Y | Y | Y | Y | -| `emeter_instant_current_l2` | Number:ElectricCurrent | Instant Current L2 (A) | - | - | - | - | O | O | O | O | O | Y | -| `emeter_instant_current_l3` | Number:ElectricCurrent | Instant Current L3 (A) | - | - | - | - | O | O | O | O | O | Y | -| `emeter_instant_power_delivery_l1` | Number:Power | Instant Power Delivery L1 (kW) | - | - | - | - | Y | Y | Y | Y | Y | - | -| `emeter_instant_power_delivery_l2` | Number:Power | Instant Power Delivery L2 (kW) | - | - | - | - | O | O | O | O | O | - | -| `emeter_instant_power_delivery_l3` | Number:Power | Instant Power Delivery L3 (kW) | - | - | - | - | O | O | O | O | O | - | -| `emeter_instant_power_production_l1` | Number:Power | Instant Power Production L1 (kW) | - | - | - | - | Y | Y | Y | Y | Y | - | -| `emeter_instant_power_production_l2` | Number:Power | Instant Power Production L2 (kW) | - | - | - | - | O | O | O | O | O | - | -| `emeter_instant_power_production_l3` | Number:Power | Instant Power Production L3 (kW) | - | - | - | - | O | O | O | O | O | - | -| `emeter_instant_reactive_power_delivery_l1` | Number:Power | Instant Reactive Power Delivery L1 (kvar) | - | - | - | - | - | - | - | - | Y | - | -| `emeter_instant_reactive_power_delivery_l2` | Number:Power | Instant Reactive Power Delivery L2 (kvar) | - | - | - | - | - | - | - | - | Y | - | -| `emeter_instant_reactive_power_delivery_l3` | Number:Power | Instant Reactive Power Delivery L3 (kvar) | - | - | - | - | - | - | - | - | Y | - | -| `emeter_instant_reactive_power_production_l1` | Number:Power | Instant Reactive Power Prodcution L1 (kvar) | - | - | - | - | - | - | - | - | Y | - | -| `emeter_instant_reactive_power_production_l2` | Number:Power | Instant Reactive Power Prodcution L2 (kvar) | - | - | - | - | - | - | - | - | Y | - | -| `emeter_instant_reactive_power_production_l3` | Number:Power | Instant Reactive Power Prodcution L3 (kvar) | - | - | - | - | - | - | - | - | Y | - | -| `emeter_instant_voltage_l1` | Number:ElectricPotential | Instant Voltage L1 (V) | - | - | - | - | - | - | - | Y | - | Y | -| `emeter_instant_voltage_l2` | Number:ElectricPotential | Instant Voltage L2 (V) | - | - | - | - | - | - | - | O | - | Y | -| `emeter_instant_voltage_l3` | Number:ElectricPotential | Instant Voltage L3 (V) | - | - | - | - | - | - | - | O | - | Y | -| | | **Channels for the slave electricity meter** | | | | | | | | | | | -| `meter_device_type` | String | Slave Electricity Meter Device Type | - | - | - | - | Y | Y | Y | Y | - | - | -| `meter_equipment_identifier` | String | Slave Electricity Meter ID | - | - | - | - | Y | Y | Y | Y | - | - | -| `emeter_delivery_tariff0` | Number:Energy | Total amount of slave electricity used for tariff 0 (kWh) | Y | - | - | - | - | - | - | - | - | - | -| `emeter_delivery_tariff1` | Number:Energy | Total amount of slave electricity used for tariff 1 (kWh) | Y | - | - | - | - | - | - | - | - | - | -| `emeter_delivery_tariff2` | Number:Energy | Total amount of slave electricity used for tariff 2 (kWh) | Y | - | - | - | - | - | - | - | - | - | -| `emeter_production_tariff0` | Number:Energy | Total amount of slave electricity produced for tariff 0 (kWh) | Y | - | - | - | - | - | - | - | - | - | -| `emeter_production_tariff1` | Number:Energy | Total amount of slave electricity produced for tariff 1 (kWh) | Y | - | - | - | - | - | - | - | - | - | -| `emeter_production_tariff2` | Number:Energy | Total amount of slave electricity produced for tariff 2 (kWh) | Y | - | - | - | - | - | - | - | - | - | -| `emeter_tariff_indicator` | String | Current slave tariff indicator | Y | - | - | - | - | - | - | - | - | - | -| `emeter_treshold_a` | Number:ElectricCurrent | Actual slave threshold (A) | Y | - | - | - | - | - | - | - | - | - | -| `meter_switch_position` | Number | Slave electricity switch position | Y | - | - | Y | Y | Y | - | - | - | - | -| `emeter_active_import_power` | Number:Power | Slave aggregate active import power (W) | Y | - | - | - | - | - | - | - | - | - | -| `emeter_value` | Number:Energy | Slave electricity usage (kWh) in the past period | - | - | - | - | Y | Y | Y | Y | - | - | -| `emeter_value_timestamp` | DateTime | Timestamp of the last reading | - | - | - | - | Y | Y | Y | Y | - | - | -| | | **Channels for the gas meter** | | | | | | | | | | | -| `meter_device_type` | String | Gas Meter Device Type | - | - | - | Y | - | - | - | - | - | Y | -| `meter_equipment_identifier` | String | Gas Meter ID | Y | - | - | Y | - | - | - | - | - | Y | -| `gmeter_equipment_identifier_v2` | String | Gas Meter ID | - | Y | Y | - | - | - | - | - | - | - | -| `gmeter_24h_delivery_v2` | Number:Volume | Gas Delivery past 24 hours | Y | Y | Y | - | - | - | - | - | - | - | -| `gmeter_24h_delivery_v2_timestamp` | DateTime | Timestamp of the last reading | Y | Y | Y | - | - | - | - | - | - | - | -| `gmeter_24h_delivery_compensated_v2` | Number:Volume | Gas Delivery past 24 hours (compensated) | - | Y | Y | - | - | - | - | - | - | - | -| `gmeter_24h_delivery_compensated_v2_timestamp` | DateTime | Timestamp of the last reading | - | Y | Y | - | - | - | - | - | - | - | -| `gmeter_value_v3` | Number:Volume | Gas Delivery past period | - | - | - | Y | - | - | - | - | - | - | -| `gmeter_value_v3_timestamp` | DateTime | Timestamp of the last reading | - | - | - | Y | - | - | - | - | - | - | -| `gmeter_last_value` | Number:Volume | Gas Delivery last reading value | - | - | - | - | - | - | - | - | - | Y | -| `gmeter_last_value_timestamp` | DateTime | Timesamp of last Gas Delivery reading | - | - | - | - | - | - | - | - | - | Y | -| `gmeter_valve_position_v2_1` | Number | Gas Valve position | - | Y | - | - | - | - | - | - | - | - | -| `gmeter_valve_position_v2_2` | Number | Gas Valve position | Y | - | Y | - | - | - | - | - | - | - | -| `meter_valve_switch_position` | Number | Gas Valve position | - | - | - | Y | - | - | - | - | - | Y | -| | | **Channels for the generic meter** | | | | | | | | | | | -| `meter_device_type` | String | Generic Meter Device Type | - | - | - | Y | - | - | - | - | - | - | -| `gmeter_equipment_identifier` | String | Generic Meter ID | - | - | - | Y | - | - | - | - | - | - | -| `genmeter_value_v3` | Number | Delivery past period | - | - | - | Y | - | - | - | - | - | - | -| `meter_valve_switch_position` | Number | Generic Meter Valve/Switch position | - | - | - | Y | - | - | - | - | - | - | -| | | **Channels for the GJ meter (Heating or Cooling)** | | | | | | | | | | - | -| `meter_device_type` | String | GJ Meter Device Type | - | - | - | Y | Y | Y | Y | Y | - | | -| `meter_equipment_identifier` | Number | GJ Meter ID | - | - | - | Y | Y | Y | Y | Y | - | - | -| `gjmeter_value_v3` | Number:Energy | GJ Delivery past period | - | - | - | Y | - | - | - | - | - | - | -| `gjmeter_value_v3_timestamp` | DateTime | Timestamp of the last reading | - | - | - | Y | - | - | - | - | - | - | -| `gjmeter_value_v4` | Number:Energy | GJ Delivery past period | - | - | - | - | Y | Y | Y | Y | - | - | -| `gjmeter_value_v4_timestamp` | DateTime | Timestamp of the last reading | - | - | - | - | Y | Y | Y | Y | - | - | -| | | **Channels for the heating meter** | | | | | | | | | | | -| `meter_valve_switch_position` | Number | GJ Meter Valve position | - | - | - | Y | Y | Y | Y | - | - | - | -| `meter_equipment_identifier` | String | Heating Meter ID | Y | - | - | - | - | - | - | - | - | - | -| `hmeter_equipment_identifier_v2_2` | String | Heating Meter ID | - | - | Y | - | - | - | - | - | - | - | -| `hmeter_value_v2` | Number:Energy | Heating Delivery past period | Y | - | Y | - | - | - | - | - | - | - | -| `hmeter_value_v2_timestamp` | DateTime | Timestamp of the last reading | Y | - | Y | - | - | - | - | - | - | - | -| | | m3 Meter Device Type | - | - | - | - | Y | Y | Y | Y | - | - | -| `meter_equipment_identifier` | String | m3 Meter ID | - | - | - | - | Y | Y | Y | Y | - | - | -| `m3meter_value` | Number:Volume | m3 Delivery past period | - | - | - | - | Y | Y | Y | Y | - | - | -| `meter_valve_switch_position` | Number | m3 Meter Valve position | - | - | - | - | Y | Y | Y | - | - | - | -| | | **Channels for the water meter** | | | | | | | | | | | -| `meter_device_type` | String | Water Meter Device Type | - | - | - | Y | - | - | - | - | - | - | -| `meter_equipment_identifier` | String | Water Meter ID | Y | - | - | Y | - | - | - | - | - | - | -| `wmeter_equipment_identifier_v2_2` | String | Water Meter ID | - | - | Y | - | - | - | - | - | - | - | -| `wmeter_value_v2` | Number:Volume | Water Delivery past period | Y | - | Y | - | - | - | - | - | - | - | -| `wmeter_value_v2_timestamp` | DateTime | Timestamp of the last reading | Y | - | Y | - | - | - | - | - | - | - | -| `wmeter_value_v3` | Number:Volume | Water Delivery past period | - | - | - | Y | - | - | - | - | - | - | -| `meter_valve_switch_position` | Number | Water Meter Valve position | - | - | - | Y | - | - | - | - | - | - | +| Channel Type ID | Item Type | Description | Ace4000 | DSMR V2.1 | DSMR V2.2 | DSMR V3.0 | DSMR V4.0 | DSMR V4.0.4 | DSMR V4.2 | DSMR V5 | SMARTY V1.0 | e-MUCS V1.0 | Austian | +|--------------------------------------------------|--------------------------|------------------------------------------------------------------------|---------|-----------|-----------|-----------|-----------|-------------|-----------|---------|-------------|-------------|---------| +| | | **Channels for the generic device** | | | | | | | | | | | | +| `p1_text_code` | String | Text code from the device | - | Y | Y | Y | Y | Y | Y | - | - | - | - | +| `p1_text_string` | String | Text string from the device | - | Y | Y | Y | Y | Y | Y | Y | Y | Y | - | +| `p1_version_output` | String | Version information (most times this refers to the DSMR specification) | - | - | - | - | Y | Y | Y | Y | Y | - | Y | +| `p1_emucs_version_output` | String | e-MUCS version information | - | - | - | - | - | - | - | - | Y | y | - | +| `p1_timestamp` | DateTime | Timestamp of the last device reading | - | - | - | - | Y | Y | Y | Y | Y | Y | Y | +| | | **Channels for the cooling meter** | | | | | | | | | | | | +| `cmeter_value_v2` | Number:Energy | The total amount of cooling used in the past period (GJ) | Y | - | Y | - | - | - | - | - | - | - | - | +| `cmeter_value_v2_timestamp` | DateTime | Timestamp of the last meter reading | Y | - | Y | - | - | - | - | - | - | - | - | +| `cmeter_equipment_identifier_v2_2` | String | Equipment identifier | - | - | Y | - | - | - | - | - | - | - | - | +| | | **Channels for the main electricity meter** | | | | | | | | | | | | +| `emeter_equipment_identifier_v2_x` | String | Electricity Equipment identifier | - | Y | Y | - | - | - | - | - | Y | - | - | +| `emeter_equipment_identifier` | String | Electricity Equipment identifier | - | - | - | Y | Y | Y | Y | Y | - | Y | - | +| `emeter_delivery_tariff0` | Number:Energy | Total amount of electricity used for tariff 0 (kWh) | Y | - | - | - | - | - | - | - | Y | - | Y | +| `emeter_delivery_tariff1` | Number:Energy | Total amount of electricity used for tariff 1 (kWh) | Y | Y | Y | Y | Y | Y | Y | Y | - | Y | Y | +| `emeter_delivery_tariff2` | Number:Energy | Total amount of electricity used for tariff 2 (kWh) | Y | Y | Y | Y | Y | Y | Y | Y | - | Y | Y | +| `emeter_production_tariff0` | Number:Energy | Total amount of electricity produced for tariff 0 (kWh) | Y | - | - | - | - | - | - | - | Y | - | Y | +| `emeter_production_tariff1` | Number:Energy | Total amount of electricity produced for tariff 1 (kWh) | Y | Y | Y | Y | Y | Y | Y | Y | - | Y | Y | +| `emeter_production_tariff2` | Number:Energy | Total amount of electricity produced for tariff 2 (kWh) | Y | Y | Y | Y | Y | Y | Y | Y | - | Y | Y | +| `emeter_delivery_tariff0_antifraud` | Number:Energy | Total amount of electricity used for tariff 2 [antifraud] (kWh) | Y | - | - | - | - | - | - | - | - | - | - | +| `emeter_delivery_tariff1_antifraud` | Number:Energy | Total amount of electricity used for tariff 1 [antifraud] (kWh) | Y | - | - | - | - | - | - | - | - | - | - | +| `emeter_delivery_tariff2_antifraud` | Number:Energy | Total amount of electricity used for tariff 2 [antifraud] (kWh) | Y | - | - | - | - | - | - | - | - | - | - | +| `emeter_total_imported_energy_register_q` | Number:Energy | Total Imported Energy (Q+) (kvarh) | - | - | - | - | - | - | - | - | Y | - | Y | +| `emeter_total_imported_energy_register_r_rate1` | Number:Energy | Total Imported Energy Rate 1 (kvarh) | - | - | - | - | - | - | - | - | - | - | Y | +| `emeter_total_imported_energy_register_r_rate2` | Number:Energy | Total Imported Energy Rate 2 (kvarh) | - | - | - | - | - | - | - | - | - | - | Y | +| `emeter_total_exported_energy_register_q` | Number:Energy | Total Exported Energy (Q-) (kvarh) | - | - | - | - | - | - | - | - | Y | - | Y | +| `emeter_total_exported_energy_register_r_rate1` | Number:Energy | Total Exported Energy Rate 1 (kvarh) | - | - | - | - | - | - | - | - | - | - | Y | +| `emeter_total_exported_energy_register_r_rate2` | Number:Energy | Total Exported Energy Rate 2 (kvarh) | - | - | - | - | - | - | - | - | - | - | Y | +| `emeter_tariff_indicator` | String | Current tariff indicator | Y | Y | Y | Y | Y | Y | Y | Y | - | Y | - | +| `emeter_treshold_a_v2_1` | Number:ElectricCurrent | Actual threshold (A) | - | Y | - | - | - | - | - | - | - | - | - | +| `emeter_treshold_a` | Number:ElectricCurrent | Actual threshold (A) | Y | - | Y | Y | - | - | - | - | - | - | - | +| `emeter_fuse_threshold_a` | Number:ElectricCurrent | Active fuse threshold (A) | - | - | - | - | - | - | - | - | - | Y | - | +| `emeter_treshold_kwh` | Number:Power | Actual threshold (kW) | - | - | - | - | Y | Y | - | - | - | Y | - | +| `emeter_switch_position_v2_1` | Number | Switch position | - | Y | - | - | - | - | - | - | - | - | - | +| `emeter_switch_position` | Number | Switch position | Y | - | Y | Y | Y | Y | - | - | Y | Y | - | +| `emeter_active_import_power` | Number:Power | Aggregate active import power (W) | Y | - | - | - | - | - | - | - | - | - | - | +| `emeter_actual_delivery` | Number:Power | Current power delivery (kW) | - | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | +| `emeter_actual_production` | Number:Power | Current power production (kW) | - | - | - | Y | Y | Y | Y | Y | Y | Y | Y | +| `emeter_actual_reactive_delivery` | Number | Actual Reactive Power Delivery (kvar) | - | - | - | - | - | - | - | - | Y | - | Y | +| `emeter_actual_reactive_production` | Number | Actual Reactive Power Production (kvar) | - | - | - | - | - | - | - | - | Y | - | Y | +| `emeter_active_threshold_smax` | Number | Active threshold (SMAX) (kVA) | - | - | - | - | - | - | - | - | Y | - | - | +| `emeter_threshold_kw` | Number:Power | Active threshold (SMAX) (kVA) | - | - | - | Y | Y | Y | - | - | Y | Y | - | +| `emeter_power_failures` | Number | Number of power failures | - | - | - | - | Y | Y | Y | Y | Y | - | - | +| `emeter_long_power_failures` | Number | Number of long power failures | - | - | - | - | Y | Y | Y | Y | - | - | - | +| `emeter_power_failure_log_entries` | Number | Number of entries in the power failure log | - | - | - | - | Y | Y | Y | Y | Y | - | - | +| `emeter_power_failure_log_timestamp[x]` *note 2* | DateTime | Timestamp for entry [x] in the power failure log | - | - | - | - | Y | Y | Y | Y | Y | - | - | +| `emeter_power_failure_log_duration[x]` *note 2* | Number:Time | Duration for entry [x] the power failure log | - | - | - | - | Y | Y | Y | Y | Y | - | - | +| `emeter_voltage_sags_l1` | Number | Number of voltage sags L1 | - | - | - | - | Y | Y | Y | Y | Y | - | - | +| `emeter_voltage_sags_l2` | Number | Number of voltage sags L2 | - | - | - | - | O | O | O | O | O | - | - | +| `emeter_voltage_sags_l3` | Number | Number of voltage sags L3 | - | - | - | - | O | O | O | O | O | - | - | +| `emeter_voltage_swells_l1` | Number | Number of voltage swells L1 | - | - | - | - | Y | Y | Y | Y | - | - | - | +| `emeter_voltage_swells_l2` | Number | Number of voltage swells L2 | - | - | - | - | O | O | O | O | - | - | - | +| `emeter_voltage_swells_l3` | Number | Number of voltage swells L3 | - | - | - | - | O | O | O | O | - | - | - | +| `emeter_instant_current_l1` | Number:ElectricCurrent | Instant Current L1 (A) | - | - | - | - | Y | Y | Y | Y | Y | Y | - | +| `emeter_instant_current_l2` | Number:ElectricCurrent | Instant Current L2 (A) | - | - | - | - | O | O | O | O | O | Y | - | +| `emeter_instant_current_l3` | Number:ElectricCurrent | Instant Current L3 (A) | - | - | - | - | O | O | O | O | O | Y | - | +| `emeter_instant_power_delivery_l1` | Number:Power | Instant Power Delivery L1 (kW) | - | - | - | - | Y | Y | Y | Y | Y | - | - | +| `emeter_instant_power_delivery_l2` | Number:Power | Instant Power Delivery L2 (kW) | - | - | - | - | O | O | O | O | O | - | - | +| `emeter_instant_power_delivery_l3` | Number:Power | Instant Power Delivery L3 (kW) | - | - | - | - | O | O | O | O | O | - | - | +| `emeter_instant_power_production_l1` | Number:Power | Instant Power Production L1 (kW) | - | - | - | - | Y | Y | Y | Y | Y | - | - | +| `emeter_instant_power_production_l2` | Number:Power | Instant Power Production L2 (kW) | - | - | - | - | O | O | O | O | O | - | - | +| `emeter_instant_power_production_l3` | Number:Power | Instant Power Production L3 (kW) | - | - | - | - | O | O | O | O | O | - | - | +| `emeter_instant_reactive_power_delivery_l1` | Number:Power | Instant Reactive Power Delivery L1 (kvar) | - | - | - | - | - | - | - | - | Y | - | - | +| `emeter_instant_reactive_power_delivery_l2` | Number:Power | Instant Reactive Power Delivery L2 (kvar) | - | - | - | - | - | - | - | - | Y | - | - | +| `emeter_instant_reactive_power_delivery_l3` | Number:Power | Instant Reactive Power Delivery L3 (kvar) | - | - | - | - | - | - | - | - | Y | - | - | +| `emeter_instant_reactive_power_production_l1` | Number:Power | Instant Reactive Power Prodcution L1 (kvar) | - | - | - | - | - | - | - | - | Y | - | - | +| `emeter_instant_reactive_power_production_l2` | Number:Power | Instant Reactive Power Prodcution L2 (kvar) | - | - | - | - | - | - | - | - | Y | - | - | +| `emeter_instant_reactive_power_production_l3` | Number:Power | Instant Reactive Power Prodcution L3 (kvar) | - | - | - | - | - | - | - | - | Y | - | - | +| `emeter_instant_voltage_l1` | Number:ElectricPotential | Instant Voltage L1 (V) | - | - | - | - | - | - | - | Y | - | Y | - | +| `emeter_instant_voltage_l2` | Number:ElectricPotential | Instant Voltage L2 (V) | - | - | - | - | - | - | - | O | - | Y | - | +| `emeter_instant_voltage_l3` | Number:ElectricPotential | Instant Voltage L3 (V) | - | - | - | - | - | - | - | O | - | Y | - | +| | | **Channels for the slave electricity meter** | | | | | | | | | | | | +| `meter_device_type` | String | Slave Electricity Meter Device Type | - | - | - | - | Y | Y | Y | Y | - | - | - | +| `meter_equipment_identifier` | String | Slave Electricity Meter ID | - | - | - | - | Y | Y | Y | Y | - | - | - | +| `emeter_delivery_tariff0` | Number:Energy | Total amount of slave electricity used for tariff 0 (kWh) | Y | - | - | - | - | - | - | - | - | - | - | +| `emeter_delivery_tariff1` | Number:Energy | Total amount of slave electricity used for tariff 1 (kWh) | Y | - | - | - | - | - | - | - | - | - | - | +| `emeter_delivery_tariff2` | Number:Energy | Total amount of slave electricity used for tariff 2 (kWh) | Y | - | - | - | - | - | - | - | - | - | - | +| `emeter_production_tariff0` | Number:Energy | Total amount of slave electricity produced for tariff 0 (kWh) | Y | - | - | - | - | - | - | - | - | - | - | +| `emeter_production_tariff1` | Number:Energy | Total amount of slave electricity produced for tariff 1 (kWh) | Y | - | - | - | - | - | - | - | - | - | - | +| `emeter_production_tariff2` | Number:Energy | Total amount of slave electricity produced for tariff 2 (kWh) | Y | - | - | - | - | - | - | - | - | - | - | +| `emeter_tariff_indicator` | String | Current slave tariff indicator | Y | - | - | - | - | - | - | - | - | - | - | +| `emeter_treshold_a` | Number:ElectricCurrent | Actual slave threshold (A) | Y | - | - | - | - | - | - | - | - | - | - | +| `meter_switch_position` | Number | Slave electricity switch position | Y | - | - | Y | Y | Y | - | - | - | - | - | +| `emeter_active_import_power` | Number:Power | Slave aggregate active import power (W) | Y | - | - | - | - | - | - | - | - | - | - | +| `emeter_value` | Number:Energy | Slave electricity usage (kWh) in the past period | - | - | - | - | Y | Y | Y | Y | - | - | - | +| `emeter_value_timestamp` | DateTime | Timestamp of the last reading | - | - | - | - | Y | Y | Y | Y | - | - | - | +| | | **Channels for the gas meter** | | | | | | | | | | | | +| `meter_device_type` | String | Gas Meter Device Type | - | - | - | Y | - | - | - | - | - | Y | - | +| `meter_equipment_identifier` | String | Gas Meter ID | Y | - | - | Y | - | - | - | - | - | Y | - | +| `gmeter_equipment_identifier_v2` | String | Gas Meter ID | - | Y | Y | - | - | - | - | - | - | - | - | +| `gmeter_24h_delivery_v2` | Number:Volume | Gas Delivery past 24 hours | Y | Y | Y | - | - | - | - | - | - | - | - | +| `gmeter_24h_delivery_v2_timestamp` | DateTime | Timestamp of the last reading | Y | Y | Y | - | - | - | - | - | - | - | - | +| `gmeter_24h_delivery_compensated_v2` | Number:Volume | Gas Delivery past 24 hours (compensated) | - | Y | Y | - | - | - | - | - | - | - | - | +| `gmeter_24h_delivery_compensated_v2_timestamp` | DateTime | Timestamp of the last reading | - | Y | Y | - | - | - | - | - | - | - | - | +| `gmeter_value_v3` | Number:Volume | Gas Delivery past period | - | - | - | Y | - | - | - | - | - | - | - | +| `gmeter_value_v3_timestamp` | DateTime | Timestamp of the last reading | - | - | - | Y | - | - | - | - | - | - | - | +| `gmeter_last_value` | Number:Volume | Gas Delivery last reading value | - | - | - | - | - | - | - | - | - | Y | - | +| `gmeter_last_value_timestamp` | DateTime | Timesamp of last Gas Delivery reading | - | - | - | - | - | - | - | - | - | Y | - | +| `gmeter_valve_position_v2_1` | Number | Gas Valve position | - | Y | - | - | - | - | - | - | - | - | - | +| `gmeter_valve_position_v2_2` | Number | Gas Valve position | Y | - | Y | - | - | - | - | - | - | - | - | +| `meter_valve_switch_position` | Number | Gas Valve position | - | - | - | Y | - | - | - | - | - | Y | - | +| | | **Channels for the generic meter** | | | | | | | | | | | | +| `meter_device_type` | String | Generic Meter Device Type | - | - | - | Y | - | - | - | - | - | - | - | +| `gmeter_equipment_identifier` | String | Generic Meter ID | - | - | - | Y | - | - | - | - | - | - | - | +| `genmeter_value_v3` | Number | Delivery past period | - | - | - | Y | - | - | - | - | - | - | - | +| `meter_valve_switch_position` | Number | Generic Meter Valve/Switch position | - | - | - | Y | - | - | - | - | - | - | - | +| | | **Channels for the GJ meter (Heating or Cooling)** | | | | | | | | | | - | - | +| `meter_device_type` | String | GJ Meter Device Type | - | - | - | Y | Y | Y | Y | Y | - | | | +| `meter_equipment_identifier` | Number | GJ Meter ID | - | - | - | Y | Y | Y | Y | Y | - | - | - | +| `gjmeter_value_v3` | Number:Energy | GJ Delivery past period | - | - | - | Y | - | - | - | - | - | - | - | +| `gjmeter_value_v3_timestamp` | DateTime | Timestamp of the last reading | - | - | - | Y | - | - | - | - | - | - | - | +| `gjmeter_value_v4` | Number:Energy | GJ Delivery past period | - | - | - | - | Y | Y | Y | Y | - | - | - | +| `gjmeter_value_v4_timestamp` | DateTime | Timestamp of the last reading | - | - | - | - | Y | Y | Y | Y | - | - | - | +| | | **Channels for the heating meter** | | | | | | | | | | | | +| `meter_valve_switch_position` | Number | GJ Meter Valve position | - | - | - | Y | Y | Y | Y | - | - | - | - | +| `meter_equipment_identifier` | String | Heating Meter ID | Y | - | - | - | - | - | - | - | - | - | - | +| `hmeter_equipment_identifier_v2_2` | String | Heating Meter ID | - | - | Y | - | - | - | - | - | - | - | - | +| `hmeter_value_v2` | Number:Energy | Heating Delivery past period | Y | - | Y | - | - | - | - | - | - | - | - | +| `hmeter_value_v2_timestamp` | DateTime | Timestamp of the last reading | Y | - | Y | - | - | - | - | - | - | - | - | +| | | m3 Meter Device Type | - | - | - | - | Y | Y | Y | Y | - | - | - | +| `meter_equipment_identifier` | String | m3 Meter ID | - | - | - | - | Y | Y | Y | Y | - | - | - | +| `m3meter_value` | Number:Volume | m3 Delivery past period | - | - | - | - | Y | Y | Y | Y | - | - | - | +| `meter_valve_switch_position` | Number | m3 Meter Valve position | - | - | - | - | Y | Y | Y | - | - | - | - | +| | | **Channels for the water meter** | | | | | | | | | | | | +| `meter_device_type` | String | Water Meter Device Type | - | - | - | Y | - | - | - | - | - | - | - | +| `meter_equipment_identifier` | String | Water Meter ID | Y | - | - | Y | - | - | - | - | - | - | - | +| `wmeter_equipment_identifier_v2_2` | String | Water Meter ID | - | - | Y | - | - | - | - | - | - | - | - | +| `wmeter_value_v2` | Number:Volume | Water Delivery past period | Y | - | Y | - | - | - | - | - | - | - | - | +| `wmeter_value_v2_timestamp` | DateTime | Timestamp of the last reading | Y | - | Y | - | - | - | - | - | - | - | - | +| `wmeter_value_v3` | Number:Volume | Water Delivery past period | - | - | - | Y | - | - | - | - | - | - | - | +| `meter_valve_switch_position` | Number | Water Meter Valve position | - | - | - | Y | - | - | - | - | - | - | - | *note 2*. The power failure log has a dynamic number of entries starting at `0`. So `emeter_power_failure_log_timestamp0`, `emeter_power_failure_log_duration0` refers to the first entry, @@ -311,7 +320,7 @@ ItemType "" () {channel=""} **Examples** ``` -Number:Energy MeterDeliveryTariff0 "Total electricity delivered to the resident during low tariff period [%.3f kWh]" {channel="dsmr:electricity_v5_0:mysmartmeter:electricityV5:emeter_delivery_tariff1} +Number:Energy MeterDeliveryTariff0 "Delivered Low Tariff [%.3f kWh]" {channel="dsmr:electricity_v5_0:mysmartmeter:electricityV5:emeter_delivery_tariff1} ``` ## Full configuration example @@ -330,8 +339,8 @@ Bridge dsmr:dsmrBridge:mysmartmeter [serialPort="/dev/ttyUSB0"] { ``` String P1Version "P1 Version output" {channel="dsmr:device_v5:mysmartmeter:dsmrV5Device:p1_version_output"} -Number:Energy MeterDeliveryTariff0 "Total electricity delivered to the resident during low tariff period [%.3f kWh]" {channel="dsmr:electricity_v5_0:mysmartmeter:electricityV5:emeter_delivery_tariff1"} -Number:Energy MeterDeliveryTariff1 "Total electricity delivered to the resident during high tariff period [%.3f kWh]" {channel="dsmr:electricity_v5_0:mysmartmeter:electricityV5:emeter_delivery_tariff2"} +Number:Energy MeterDeliveryTariff0 "Delivered Low Tariff [%.3f kWh]" {channel="dsmr:electricity_v5_0:mysmartmeter:electricityV5:emeter_delivery_tariff1"} +Number:Energy MeterDeliveryTariff1 "Delivered High Tariff [%.3f kWh]" {channel="dsmr:electricity_v5_0:mysmartmeter:electricityV5:emeter_delivery_tariff2"} ``` ## Determine M-Bus channel diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRConnectorErrorEvent.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRConnectorErrorEvent.java index 04676842aaf48..429df2c64fec1 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRConnectorErrorEvent.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRConnectorErrorEvent.java @@ -12,12 +12,15 @@ */ package org.openhab.binding.dsmr.internal.device.connector; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Error events from a connector. * * @author M. Volaart - Initial contribution * @author Hilbrand Bouwkamp - Reduced number of event to only errors */ +@NonNullByDefault public enum DSMRConnectorErrorEvent { DONT_EXISTS, IN_USE, diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemObject.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemObject.java index 574e9ed2458fd..c39e1ac616b2f 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemObject.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemObject.java @@ -123,16 +123,14 @@ public void parseCosemValues(String cosemValueString) throws ParseException { int cosemValueItr = 0; while (cosemValueMatcher.find()) { - Entry> valueDescriptorEntry = type.getDescriptor(cosemValueItr); - State cosemValue = valueDescriptorEntry.getValue().getStateValue(cosemValueMatcher.group(2)); - - if (cosemValue != null) { - if (!cosemValues.containsKey(valueDescriptorEntry.getKey())) { - cosemValues.put(valueDescriptorEntry.getKey(), cosemValue); - } else { - logger.warn("Value for descriptor {} already exists, dropping value {}", valueDescriptorEntry, - cosemValue); - } + final Entry> valueDescriptorEntry = type.getDescriptor(cosemValueItr); + final State cosemValue = valueDescriptorEntry.getValue().getStateValue(cosemValueMatcher.group(2)); + + if (!cosemValues.containsKey(valueDescriptorEntry.getKey())) { + cosemValues.put(valueDescriptorEntry.getKey(), cosemValue); + } else { + logger.warn("Value for descriptor {} already exists, dropping value {}", valueDescriptorEntry, + cosemValue); } cosemValueItr++; } diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemObjectFactory.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemObjectFactory.java index 2f91faa17c641..82505ef6bafe5 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemObjectFactory.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemObjectFactory.java @@ -42,16 +42,6 @@ public class CosemObjectFactory { */ private final Map> obisLookupTableMultipleFixed = new HashMap<>(); - /** - * Lookup cache for dynamic OBIS Identifiers - */ - private final Map obisLookupTableDynamic = new HashMap<>(); - - /** - * Lookup cache for wild card Cosem Object types - */ - private final List obisWildcardCosemTypeList = new ArrayList<>(); - /** * Creates a new CosemObjectFactory */ @@ -64,7 +54,7 @@ public CosemObjectFactory() { * (i.e. groupA == null || groupB == null || groupC == null). This lookuptable will be filled * dynamically with unique wildcard OBISIdentifiers when values are received and matches a particular real * device (if the device is changed, this lookupTable must be cleared by removing the corresponding DSMRDevice - * Thing from the configuration. + * Thing from the configuration.) * - obisWildCardCosemTypeList. This is the list of all wild card Cosem Object types. Multiple Cosem Object * Types can have the same wild card OBISIdentifer. * @@ -72,9 +62,7 @@ public CosemObjectFactory() { * correct OBISIdentifier is discovered for a certain OBISMsgType this is added to the obisLookupTableDynamic. */ for (CosemObjectType msgType : CosemObjectType.values()) { - if (msgType.obisId.reducedOBISIdentifierIsWildCard()) { - obisWildcardCosemTypeList.add(msgType); - } else if (msgType.obisId.isConflict()) { + if (msgType.obisId.isConflict()) { obisLookupTableMultipleFixed.computeIfAbsent(msgType.obisId, r -> new ArrayList<>()).add(msgType); } else { obisLookupTableFixed.put(msgType.obisId, msgType); @@ -123,29 +111,11 @@ public CosemObjectFactory() { } } - objectType = obisLookupTableDynamic.get(reducedObisId); - if (objectType != null) { - logger.trace("Found obisId {} in the dynamic lookup table", reducedObisId); - return getCosemObjectInternal(objectType, obisId, cosemStringValues); - } - objectType = obisLookupTableFixed.get(reducedObisIdGroupE); if (objectType != null) { return getCosemObjectInternal(objectType, obisId, cosemStringValues); } - for (CosemObjectType obisMsgType : obisWildcardCosemTypeList) { - if (obisMsgType.obisId.equalsWildCard(reducedObisId)) { - CosemObject cosemObject = getCosemObjectInternal(obisMsgType, obisId, cosemStringValues); - if (cosemObject != null) { - logger.trace("Searched reducedObisId {} in the wild card type list, result: {}", reducedObisId, - cosemObject); - obisLookupTableDynamic.put(reducedObisId, obisMsgType); - return cosemObject; - } - } - } - logger.debug("Received unknown Cosem Object(OBIS id: {})", obisId); return null; diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemObjectType.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemObjectType.java index 0b921807adf42..92ace681a49c1 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemObjectType.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemObjectType.java @@ -14,11 +14,11 @@ import java.util.AbstractMap.SimpleEntry; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Map.Entry; -import org.openhab.binding.dsmr.internal.meter.DSMRMeterConstants; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.library.unit.Units; /** @@ -36,122 +36,126 @@ * @author M. Volaart - Initial contribution * @author Hilbrand Bouwkamp - Cosem subclasses made into factory classes and introduced quantity type */ +@NonNullByDefault public enum CosemObjectType { - UNKNOWN(new OBISIdentifier(-1, DSMRMeterConstants.UNKNOWN_CHANNEL, -1, -1, -1, null), CosemString.INSTANCE), + UNKNOWN(new OBISIdentifier(-1, -1, -1, -1), CosemString.INSTANCE), /* General messages */ - P1_VERSION_OUTPUT(new OBISIdentifier(1, 3, 0, 2, 8, null), CosemString.INSTANCE), - P1_EMUCS_VERSION_OUTPUT(new OBISIdentifier(0, 0, 96, 1, 4, null), CosemString.INSTANCE), - P1_TIMESTAMP(new OBISIdentifier(0, 0, 1, 0, 0, null), new CosemDate("")), - P1_TEXT_CODE(new OBISIdentifier(0, 0, 96, 13, 1, null), CosemHexString.INSTANCE), - P1_TEXT_STRING(new OBISIdentifier(0, 0, 96, 13, 0, null), CosemHexString.INSTANCE), - P1_TEXT_STRING_LONG(new OBISIdentifier(0, 0, 96, 13, null, null), CosemHexString.INSTANCE), + P1_VERSION_OUTPUT(new OBISIdentifier(1, 0, 2, 8), CosemString.INSTANCE), + P1_EMUCS_VERSION_OUTPUT(new OBISIdentifier(0, 96, 1, 4), CosemString.INSTANCE), + P1_TIMESTAMP(new OBISIdentifier(0, 1, 0, 0), new CosemDate("")), + P1_TEXT_CODE(new OBISIdentifier(0, 96, 13, 1), CosemHexString.INSTANCE), + P1_TEXT_STRING(new OBISIdentifier(0, 96, 13, 0), CosemHexString.INSTANCE), + P1_TEXT_STRING_LONG(new OBISIdentifier(0, 96, 13, null), CosemHexString.INSTANCE), /* Generic Meter Cosem Object types */ - METER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, null, 96, 1, 0, null), CosemHexString.INSTANCE), - METER_DEVICE_TYPE(new OBISIdentifier(0, null, 24, 1, 0, null), CosemString.INSTANCE), - METER_VALVE_SWITCH_POSITION(new OBISIdentifier(0, null, 24, 4, 0, null), CosemDecimal.INSTANCE), + METER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, 96, 1, 0), CosemHexString.INSTANCE), + METER_DEVICE_TYPE(new OBISIdentifier(0, 24, 1, 0), CosemString.INSTANCE), + METER_VALVE_SWITCH_POSITION(new OBISIdentifier(0, 24, 4, 0), CosemDecimal.INSTANCE), /* Electricity Meter */ - EMETER_EQUIPMENT_IDENTIFIER_V2_X(new OBISIdentifier(0, 0, 42, 0, 0, null), CosemString.INSTANCE), - EMETER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, null, 96, 1, 1, null), CosemHexString.INSTANCE), - EMETER_VALUE(new OBISIdentifier(0, null, 24, 2, 1, null), CosemDate.INSTANCE, CosemQuantity.KILO_WATT_HOUR), - EMETER_DELIVERY_TARIFF0(new OBISIdentifier(1, null, 1, 8, 0, null), CosemQuantity.KILO_WATT_HOUR), - EMETER_DELIVERY_TARIFF1(new OBISIdentifier(1, null, 1, 8, 1, null), CosemQuantity.KILO_WATT_HOUR), - EMETER_DELIVERY_TARIFF2(new OBISIdentifier(1, null, 1, 8, 2, null), CosemQuantity.KILO_WATT_HOUR), - EMETER_DELIVERY_TARIFF0_ANTIFRAUD(new OBISIdentifier(1, null, 15, 8, 0, null), CosemQuantity.KILO_WATT_HOUR), - EMETER_DELIVERY_TARIFF1_ANTIFRAUD(new OBISIdentifier(1, null, 15, 8, 1, null), CosemQuantity.KILO_WATT_HOUR), - EMETER_DELIVERY_TARIFF2_ANTIFRAUD(new OBISIdentifier(1, null, 15, 8, 2, null), CosemQuantity.KILO_WATT_HOUR), - EMETER_PRODUCTION_TARIFF0(new OBISIdentifier(1, null, 2, 8, 0, null), CosemQuantity.KILO_WATT_HOUR), - EMETER_PRODUCTION_TARIFF1(new OBISIdentifier(1, null, 2, 8, 1, null), CosemQuantity.KILO_WATT_HOUR), - EMETER_PRODUCTION_TARIFF2(new OBISIdentifier(1, null, 2, 8, 2, null), CosemQuantity.KILO_WATT_HOUR), - EMETER_TARIFF_INDICATOR(new OBISIdentifier(0, null, 96, 14, 0, null), CosemString.INSTANCE), - EMETER_ACTIVE_IMPORT_POWER(new OBISIdentifier(1, null, 15, 7, 0, null), CosemQuantity.WATT), - EMETER_ACTUAL_DELIVERY(new OBISIdentifier(1, 0, 1, 7, 0, null), CosemQuantity.KILO_WATT), - EMETER_ACTUAL_PRODUCTION(new OBISIdentifier(1, 0, 2, 7, 0, null), CosemQuantity.KILO_WATT), - EMETER_TRESHOLD_A_V2_1(new OBISIdentifier(1, 0, 17, 0, 0, null), CosemQuantity.AMPERE), - EMETER_TRESHOLD_A(new OBISIdentifier(0, 0, 17, 0, 0, null, true), CosemQuantity.AMPERE), - EMETER_FUSE_THRESHOLD_A(new OBISIdentifier(1, 0, 31, 4, 0, null), CosemQuantity.AMPERE), - EMETER_TRESHOLD_KWH(new OBISIdentifier(0, 0, 17, 0, 0, null, true), CosemQuantity.KILO_WATT), - EMETER_SWITCH_POSITION_V2_1(new OBISIdentifier(1, 0, 96, 3, 10, null), CosemDecimal.INSTANCE), - EMETER_SWITCH_POSITION(new OBISIdentifier(0, 0, 96, 3, 10, null), CosemDecimal.INSTANCE), - EMETER_POWER_FAILURES(new OBISIdentifier(0, 0, 96, 7, 21, null), CosemDecimal.INSTANCE), - EMETER_LONG_POWER_FAILURES(new OBISIdentifier(0, 0, 96, 7, 9, null), CosemDecimal.INSTANCE), - EMETER_POWER_FAILURE_LOG(new OBISIdentifier(1, 0, 99, 97, 0, null), 2, new CosemDecimal("entries"), + EMETER_EQUIPMENT_IDENTIFIER_V2_X(new OBISIdentifier(0, 42, 0, 0), CosemString.INSTANCE), + EMETER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, 96, 1, 1), CosemHexString.INSTANCE), + EMETER_VALUE(new OBISIdentifier(0, 24, 2, 1, true), CosemDate.INSTANCE, CosemQuantity.KILO_WATT_HOUR), + EMETER_DELIVERY_TARIFF0(new OBISIdentifier(1, 1, 8, 0), CosemQuantity.KILO_WATT_HOUR), + EMETER_DELIVERY_TARIFF1(new OBISIdentifier(1, 1, 8, 1), CosemQuantity.KILO_WATT_HOUR), + EMETER_DELIVERY_TARIFF2(new OBISIdentifier(1, 1, 8, 2), CosemQuantity.KILO_WATT_HOUR), + EMETER_DELIVERY_TARIFF0_ANTIFRAUD(new OBISIdentifier(1, 15, 8, 0), CosemQuantity.KILO_WATT_HOUR), + EMETER_DELIVERY_TARIFF1_ANTIFRAUD(new OBISIdentifier(1, 15, 8, 1), CosemQuantity.KILO_WATT_HOUR), + EMETER_DELIVERY_TARIFF2_ANTIFRAUD(new OBISIdentifier(1, 15, 8, 2), CosemQuantity.KILO_WATT_HOUR), + EMETER_PRODUCTION_TARIFF0(new OBISIdentifier(1, 2, 8, 0), CosemQuantity.KILO_WATT_HOUR), + EMETER_PRODUCTION_TARIFF1(new OBISIdentifier(1, 2, 8, 1), CosemQuantity.KILO_WATT_HOUR), + EMETER_PRODUCTION_TARIFF2(new OBISIdentifier(1, 2, 8, 2), CosemQuantity.KILO_WATT_HOUR), + EMETER_TARIFF_INDICATOR(new OBISIdentifier(0, 96, 14, 0), CosemString.INSTANCE), + EMETER_ACTIVE_IMPORT_POWER(new OBISIdentifier(1, 15, 7, 0), CosemQuantity.WATT), + EMETER_ACTUAL_DELIVERY(new OBISIdentifier(1, 1, 7, 0), CosemQuantity.KILO_WATT), + EMETER_ACTUAL_PRODUCTION(new OBISIdentifier(1, 2, 7, 0), CosemQuantity.KILO_WATT), + EMETER_TRESHOLD_A_V2_1(new OBISIdentifier(1, 17, 0, 0), CosemQuantity.AMPERE), + EMETER_TRESHOLD_A(new OBISIdentifier(0, 17, 0, 0, true), CosemQuantity.AMPERE), + EMETER_FUSE_THRESHOLD_A(new OBISIdentifier(1, 31, 4, 0), CosemQuantity.AMPERE), + EMETER_TRESHOLD_KW(new OBISIdentifier(0, 17, 0, 0, true), CosemQuantity.KILO_WATT), + EMETER_SWITCH_POSITION_V2_1(new OBISIdentifier(1, 96, 3, 10), CosemDecimal.INSTANCE), + EMETER_SWITCH_POSITION(new OBISIdentifier(0, 96, 3, 10), CosemDecimal.INSTANCE), + EMETER_POWER_FAILURES(new OBISIdentifier(0, 96, 7, 21), CosemDecimal.INSTANCE), + EMETER_LONG_POWER_FAILURES(new OBISIdentifier(0, 96, 7, 9), CosemDecimal.INSTANCE), + EMETER_POWER_FAILURE_LOG(new OBISIdentifier(1, 99, 97, 0), 2, new CosemDecimal("entries"), new CosemString("obisId"), /* Next 2 descriptors are repeating */ CosemDate.INSTANCE, new CosemQuantity<>(Units.SECOND, "duration")), - EMETER_VOLTAGE_SAGS_L1(new OBISIdentifier(1, 0, 32, 32, 0, null), CosemDecimal.INSTANCE), - EMETER_VOLTAGE_SAGS_L2(new OBISIdentifier(1, 0, 52, 32, 0, null), CosemDecimal.INSTANCE), - EMETER_VOLTAGE_SAGS_L3(new OBISIdentifier(1, 0, 72, 32, 0, null), CosemDecimal.INSTANCE), - EMETER_VOLTAGE_SWELLS_L1(new OBISIdentifier(1, 0, 32, 36, 0, null), CosemDecimal.INSTANCE), - EMETER_VOLTAGE_SWELLS_L2(new OBISIdentifier(1, 0, 52, 36, 0, null), CosemDecimal.INSTANCE), - EMETER_VOLTAGE_SWELLS_L3(new OBISIdentifier(1, 0, 72, 36, 0, null), CosemDecimal.INSTANCE), - EMETER_INSTANT_CURRENT_L1(new OBISIdentifier(1, 0, 31, 7, 0, null), CosemQuantity.AMPERE), - EMETER_INSTANT_CURRENT_L2(new OBISIdentifier(1, 0, 51, 7, 0, null), CosemQuantity.AMPERE), - EMETER_INSTANT_CURRENT_L3(new OBISIdentifier(1, 0, 71, 7, 0, null), CosemQuantity.AMPERE), - EMETER_INSTANT_POWER_DELIVERY_L1(new OBISIdentifier(1, 0, 21, 7, 0, null), CosemQuantity.KILO_WATT), - EMETER_INSTANT_POWER_DELIVERY_L2(new OBISIdentifier(1, 0, 41, 7, 0, null), CosemQuantity.KILO_WATT), - EMETER_INSTANT_POWER_DELIVERY_L3(new OBISIdentifier(1, 0, 61, 7, 0, null), CosemQuantity.KILO_WATT), - EMETER_INSTANT_POWER_PRODUCTION_L1(new OBISIdentifier(1, 0, 22, 7, 0, null), CosemQuantity.KILO_WATT), - EMETER_INSTANT_POWER_PRODUCTION_L2(new OBISIdentifier(1, 0, 42, 7, 0, null), CosemQuantity.KILO_WATT), - EMETER_INSTANT_POWER_PRODUCTION_L3(new OBISIdentifier(1, 0, 62, 7, 0, null), CosemQuantity.KILO_WATT), - EMETER_INSTANT_VOLTAGE_L1(new OBISIdentifier(1, 0, 32, 7, 0, null), CosemQuantity.VOLT), - EMETER_INSTANT_VOLTAGE_L2(new OBISIdentifier(1, 0, 52, 7, 0, null), CosemQuantity.VOLT), - EMETER_INSTANT_VOLTAGE_L3(new OBISIdentifier(1, 0, 72, 7, 0, null), CosemQuantity.VOLT), + EMETER_VOLTAGE_SAGS_L1(new OBISIdentifier(1, 32, 32, 0), CosemDecimal.INSTANCE), + EMETER_VOLTAGE_SAGS_L2(new OBISIdentifier(1, 52, 32, 0), CosemDecimal.INSTANCE), + EMETER_VOLTAGE_SAGS_L3(new OBISIdentifier(1, 72, 32, 0), CosemDecimal.INSTANCE), + EMETER_VOLTAGE_SWELLS_L1(new OBISIdentifier(1, 32, 36, 0), CosemDecimal.INSTANCE), + EMETER_VOLTAGE_SWELLS_L2(new OBISIdentifier(1, 52, 36, 0), CosemDecimal.INSTANCE), + EMETER_VOLTAGE_SWELLS_L3(new OBISIdentifier(1, 72, 36, 0), CosemDecimal.INSTANCE), + EMETER_INSTANT_CURRENT_L1(new OBISIdentifier(1, 31, 7, 0), CosemQuantity.AMPERE), + EMETER_INSTANT_CURRENT_L2(new OBISIdentifier(1, 51, 7, 0), CosemQuantity.AMPERE), + EMETER_INSTANT_CURRENT_L3(new OBISIdentifier(1, 71, 7, 0), CosemQuantity.AMPERE), + EMETER_INSTANT_POWER_DELIVERY_L1(new OBISIdentifier(1, 21, 7, 0), CosemQuantity.KILO_WATT), + EMETER_INSTANT_POWER_DELIVERY_L2(new OBISIdentifier(1, 41, 7, 0), CosemQuantity.KILO_WATT), + EMETER_INSTANT_POWER_DELIVERY_L3(new OBISIdentifier(1, 61, 7, 0), CosemQuantity.KILO_WATT), + EMETER_INSTANT_POWER_PRODUCTION_L1(new OBISIdentifier(1, 22, 7, 0), CosemQuantity.KILO_WATT), + EMETER_INSTANT_POWER_PRODUCTION_L2(new OBISIdentifier(1, 42, 7, 0), CosemQuantity.KILO_WATT), + EMETER_INSTANT_POWER_PRODUCTION_L3(new OBISIdentifier(1, 62, 7, 0), CosemQuantity.KILO_WATT), + EMETER_INSTANT_VOLTAGE_L1(new OBISIdentifier(1, 32, 7, 0), CosemQuantity.VOLT), + EMETER_INSTANT_VOLTAGE_L2(new OBISIdentifier(1, 52, 7, 0), CosemQuantity.VOLT), + EMETER_INSTANT_VOLTAGE_L3(new OBISIdentifier(1, 72, 7, 0), CosemQuantity.VOLT), /* Gas Meter */ - GMETER_EQUIPMENT_IDENTIFIER_V2(new OBISIdentifier(7, 0, 0, 0, 0, null), CosemString.INSTANCE), - GMETER_24H_DELIVERY_V2(new OBISIdentifier(7, 0, 23, 1, 0, null), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE), - GMETER_24H_DELIVERY_COMPENSATED_V2(new OBISIdentifier(7, 0, 23, 2, 0, null), CosemQuantity.CUBIC_METRE, - CosemDate.INSTANCE), - GMETER_LAST_VALUE(new OBISIdentifier(0, null, 24, 2, 3, null), CosemDate.INSTANCE, CosemQuantity.CUBIC_METRE), - GMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null), CosemDate.INSTANCE, // Time stamp off the reading + GMETER_EQUIPMENT_IDENTIFIER_V2(new OBISIdentifier(7, 0, 0, 0), CosemString.INSTANCE), + GMETER_24H_DELIVERY_V2(new OBISIdentifier(7, 23, 1, 0), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE), + GMETER_24H_DELIVERY_COMPENSATED_V2(new OBISIdentifier(7, 23, 2, 0), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE), + GMETER_LAST_VALUE(new OBISIdentifier(0, 24, 2, 3), CosemDate.INSTANCE, CosemQuantity.CUBIC_METRE), + GMETER_VALUE_V3(new OBISIdentifier(0, 24, 3, 0, true), CosemDate.INSTANCE, // Time stamp off the reading new CosemString("val1"), // Specification is not clear what this value is new CosemDecimal("val2"), // Specification is not clear what this value is new CosemDecimal("val3"), // Specification is not clear what this value is new CosemString("obisId"), // String containing a OBIS Identifier new CosemString("unit"), // String containing the type (m3) CosemDecimal.INSTANCE), - GMETER_VALVE_POSITION_V2_1(new OBISIdentifier(7, 0, 96, 3, 10, null), CosemDecimal.INSTANCE), - GMETER_VALVE_POSITION_V2_2(new OBISIdentifier(7, 0, 24, 4, 0, null), CosemDecimal.INSTANCE), + GMETER_VALVE_POSITION_V2_1(new OBISIdentifier(7, 96, 3, 10), CosemDecimal.INSTANCE), + GMETER_VALVE_POSITION_V2_2(new OBISIdentifier(7, 24, 4, 0), CosemDecimal.INSTANCE), /* Heating Meter */ - HMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(5, 0, 0, 0, 0, null), CosemString.INSTANCE), - HMETER_VALUE_V2(new OBISIdentifier(5, 0, 1, 0, 0, null), CosemQuantity.GIGA_JOULE, CosemDate.INSTANCE), + HMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(5, 0, 0, 0), CosemString.INSTANCE), + HMETER_VALUE_V2(new OBISIdentifier(5, 1, 0, 0), CosemQuantity.GIGA_JOULE, CosemDate.INSTANCE), /* Cooling Meter */ - CMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(6, 0, 0, 0, 0, null), CosemString.INSTANCE), - CMETER_VALUE_V2(new OBISIdentifier(6, 0, 1, 0, 0, null), CosemQuantity.GIGA_JOULE, CosemDate.INSTANCE), + CMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(6, 0, 0, 0), CosemString.INSTANCE), + CMETER_VALUE_V2(new OBISIdentifier(6, 1, 0, 0), CosemQuantity.GIGA_JOULE, CosemDate.INSTANCE), /* Water Meter */ - WMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(8, 0, 0, 0, 0, null), CosemString.INSTANCE), - WMETER_VALUE_V2(new OBISIdentifier(8, 0, 1, 0, 0, null), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE), - WMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null), CosemQuantity.CUBIC_METRE), + WMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(8, 0, 0, 0), CosemString.INSTANCE), + WMETER_VALUE_V2(new OBISIdentifier(8, 1, 0, 0), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE), + WMETER_VALUE_V3(new OBISIdentifier(0, 24, 3, 0, true), CosemQuantity.CUBIC_METRE), /* M3 Meter (Gas, Water) */ - M3METER_VALUE(new OBISIdentifier(0, null, 24, 2, 1, null), CosemDate.INSTANCE, CosemQuantity.CUBIC_METRE), + M3METER_VALUE(new OBISIdentifier(0, 24, 2, 1, true), CosemDate.INSTANCE, CosemQuantity.CUBIC_METRE), /* GJ Meter (Heating, Cooling) */ - GJMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null), CosemQuantity.GIGA_JOULE), - GJMETER_VALUE_V4(new OBISIdentifier(0, null, 24, 2, 1, null), CosemDate.INSTANCE, CosemQuantity.GIGA_JOULE), + GJMETER_VALUE_V3(new OBISIdentifier(0, 24, 3, 0, true), CosemQuantity.GIGA_JOULE), + GJMETER_VALUE_V4(new OBISIdentifier(0, 24, 2, 1, true), CosemDate.INSTANCE, CosemQuantity.GIGA_JOULE), /* Generic Meter (DSMR v3 only) */ - GENMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null), CosemDecimal.INSTANCE), + GENMETER_VALUE_V3(new OBISIdentifier(0, 24, 3, 0, true), CosemDecimal.INSTANCE), /* Additional Luxembourgish Smarty Electricity */ - EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_Q(new OBISIdentifier(1, null, 3, 8, 0, null), CosemQuantity.KILO_VAR_HOUR), - EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_Q(new OBISIdentifier(1, null, 4, 8, 0, null), CosemQuantity.KILO_VAR_HOUR), + EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_Q(new OBISIdentifier(1, 3, 8, 0), CosemQuantity.KILO_VAR_HOUR), + EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_R_RATE1(new OBISIdentifier(1, 3, 8, 1), CosemQuantity.KILO_VAR_HOUR), + EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_R_RATE2(new OBISIdentifier(1, 3, 8, 2), CosemQuantity.KILO_VAR_HOUR), + EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_Q(new OBISIdentifier(1, 4, 8, 0), CosemQuantity.KILO_VAR_HOUR), + EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_R_RATE1(new OBISIdentifier(1, 4, 8, 1), CosemQuantity.KILO_VAR_HOUR), + EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_R_RATE2(new OBISIdentifier(1, 4, 8, 2), CosemQuantity.KILO_VAR_HOUR), // The actual reactive's and threshold have no unit in the data and therefore are not quantity types. - EMETER_ACTUAL_REACTIVE_DELIVERY(new OBISIdentifier(1, 0, 3, 7, 0, null), CosemDecimal.INSTANCE_WITH_UNITS), - EMETER_ACTUAL_REACTIVE_PRODUCTION(new OBISIdentifier(1, 0, 4, 7, 0, null), CosemDecimal.INSTANCE_WITH_UNITS), - EMETER_ACTIVE_THRESHOLD_SMAX(new OBISIdentifier(0, 0, 17, 0, 0, null, true), CosemDecimal.INSTANCE_WITH_UNITS), - EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L1(new OBISIdentifier(1, 0, 23, 7, 0, null), CosemQuantity.KILO_VAR), - EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L2(new OBISIdentifier(1, 0, 43, 7, 0, null), CosemQuantity.KILO_VAR), - EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L3(new OBISIdentifier(1, 0, 63, 7, 0, null), CosemQuantity.KILO_VAR), - EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L1(new OBISIdentifier(1, 0, 24, 7, 0, null), CosemQuantity.KILO_VAR), - EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L2(new OBISIdentifier(1, 0, 44, 7, 0, null), CosemQuantity.KILO_VAR), - EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L3(new OBISIdentifier(1, 0, 64, 7, 0, null), CosemQuantity.KILO_VAR); + EMETER_ACTUAL_REACTIVE_DELIVERY(new OBISIdentifier(1, 3, 7, 0), CosemDecimal.INSTANCE_WITH_UNITS), + EMETER_ACTUAL_REACTIVE_PRODUCTION(new OBISIdentifier(1, 4, 7, 0), CosemDecimal.INSTANCE_WITH_UNITS), + EMETER_ACTIVE_THRESHOLD_SMAX(new OBISIdentifier(0, 17, 0, 0, true), CosemDecimal.INSTANCE_WITH_UNITS), + EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L1(new OBISIdentifier(1, 23, 7, 0), CosemQuantity.KILO_VAR), + EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L2(new OBISIdentifier(1, 43, 7, 0), CosemQuantity.KILO_VAR), + EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L3(new OBISIdentifier(1, 63, 7, 0), CosemQuantity.KILO_VAR), + EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L1(new OBISIdentifier(1, 24, 7, 0), CosemQuantity.KILO_VAR), + EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L2(new OBISIdentifier(1, 44, 7, 0), CosemQuantity.KILO_VAR), + EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L3(new OBISIdentifier(1, 64, 7, 0), CosemQuantity.KILO_VAR); /** OBIS reduced identifier */ public final OBISIdentifier obisId; @@ -182,9 +186,9 @@ public enum CosemObjectType { this.obisId = obisId; if (nrOfRepeatingDescriptors == 0) { this.descriptors = Arrays.asList(descriptors); - this.repeatingDescriptors = Collections.emptyList(); + this.repeatingDescriptors = List.of(); } else { - List> allDescriptors = Arrays.asList(descriptors); + final List> allDescriptors = List.of(descriptors); /* * The last nrOfRepeatingDescriptors CosemValueDescriptor will go into the repeatingDescriptor list. @@ -207,19 +211,19 @@ public enum CosemObjectType { * @param idx the CosemValueDescriptor to return * @return the CosemValueDescriptor or null if not found. */ - public Entry> getDescriptor(int idx) { + public @Nullable Entry> getDescriptor(int idx) { if (idx >= descriptors.size() && !repeatingDescriptors.isEmpty()) { /* We have a repeating list, find the correct repeating descriptor */ - int repeatingIdx = (idx - descriptors.size()) % repeatingDescriptors.size(); + final int repeatingIdx = (idx - descriptors.size()) % repeatingDescriptors.size(); - CosemValueDescriptor descriptor = repeatingDescriptors.get(repeatingIdx); + final CosemValueDescriptor descriptor = repeatingDescriptors.get(repeatingIdx); /* The repeating descriptor must have a specific channel */ - int repeatCount = (idx - descriptors.size()) / repeatingDescriptors.size(); + final int repeatCount = (idx - descriptors.size()) / repeatingDescriptors.size(); return new SimpleEntry<>(descriptor.getChannelId() + repeatCount, descriptor); } else if (idx < descriptors.size()) { - CosemValueDescriptor descriptor = descriptors.get(idx); + final CosemValueDescriptor descriptor = descriptors.get(idx); return new SimpleEntry<>(descriptor.getChannelId(), descriptor); } else { diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemQuantity.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemQuantity.java index 89723a6beaa93..f4af8bc59f2b0 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemQuantity.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemQuantity.java @@ -50,6 +50,7 @@ class CosemQuantity> extends CosemValueDescripto public static final CosemQuantity WATT = new CosemQuantity<>(Units.WATT); public static final CosemQuantity KILO_VAR = new CosemQuantity<>(Units.KILOVAR); public static final CosemQuantity KILO_VAR_HOUR = new CosemQuantity<>(Units.KILOVAR_HOUR); + public static final CosemQuantity KILO_VA = new CosemQuantity<>(MetricPrefix.KILO(Units.VOLT_AMPERE)); /** * Pattern to convert a cosem value to a value that can be parsed by {@link QuantityType}. @@ -100,13 +101,14 @@ public CosemQuantity(Unit unit, String channelId) { @Override protected QuantityType getStateValue(String cosemValue) throws ParseException { try { - QuantityType qt = new QuantityType<>(prepare(cosemValue)); + final QuantityType it = new QuantityType<>(prepare(cosemValue)); + final @Nullable QuantityType qt = it.toUnit(unit); - if (!unit.equals(qt.getUnit())) { + if (qt == null) { throw new ParseException("Failed to parse value '" + cosemValue + "' as unit " + unit, 0); } return qt; - } catch (IllegalArgumentException nfe) { + } catch (final IllegalArgumentException nfe) { throw new ParseException("Failed to parse value '" + cosemValue + "' as unit " + unit, 0); } } @@ -123,7 +125,7 @@ protected QuantityType getStateValue(String cosemValue) throws ParseException * We also support unit that do not follow the exact case. */ private String prepare(String cosemValue) { - Matcher matcher = COSEM_VALUE_WITH_UNIT_PATTERN.matcher(cosemValue.replace("m3", "m³")); + final Matcher matcher = COSEM_VALUE_WITH_UNIT_PATTERN.matcher(cosemValue.replace("m3", "m³")); if (!matcher.find()) { return cosemValue; } @@ -131,7 +133,7 @@ private String prepare(String cosemValue) { try { Integer.parseInt(matcher.group(2)); return cosemValue; - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { return matcher.group(1) + ' ' + matcher.group(2); } } diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/OBISIdentifier.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/OBISIdentifier.java index bff9c770366f9..ff745d2c299db 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/OBISIdentifier.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/OBISIdentifier.java @@ -24,12 +24,12 @@ * Class representing an OBISIdentifier * * @author M. Volaart - Initial contribution - * @author Hilbrand Bouwkamp - Fix bug in regex pattern. + * @author Hilbrand Bouwkamp - Simplified, groupF not relevant, and groupB renamed to channel. */ @NonNullByDefault public class OBISIdentifier { /** - * String representing a-b:c.d.e.f OBIS ID + * String representing a-channel:c.d.e.f OBIS ID */ private static final String OBISID_REGEX = "((\\d+)\\-)?((\\d+):)?((\\d+)\\.)(\\d+)(\\.(\\d+))?(.(\\d+))?"; @@ -39,49 +39,44 @@ public class OBISIdentifier { private static final Pattern OBIS_ID_PATTERN = Pattern.compile(OBISID_REGEX); /* the six individual group values of the OBIS ID */ - private int groupA; - private @Nullable Integer groupB; - private int groupC; - private int groupD; - private @Nullable Integer groupE; - private @Nullable Integer groupF; + private final int groupA; + private final @Nullable Integer channel; + private final int groupC; + private final int groupD; + private final @Nullable Integer groupE; + private final @Nullable Integer groupF; private boolean conflict; /** - * Constructs a new OBIS Identifier (A-B:C.D.E.F) + * Constructs a new OBIS Identifier (A-x:C.D.E.x) * * @param groupA A value - * @param groupB B value * @param groupC C value * @param groupD D value * @param groupE E value - * @param groupF F value */ - public OBISIdentifier(int groupA, @Nullable Integer groupB, int groupC, int groupD, @Nullable Integer groupE, - @Nullable Integer groupF) { - this(groupA, groupB, groupC, groupD, groupE, groupF, false); + public OBISIdentifier(final int groupA, final int groupC, final int groupD, @Nullable final Integer groupE) { + this(groupA, groupC, groupD, groupE, false); } /** - * Constructs a new OBIS Identifier (A-B:C.D.E.F) + * Constructs a new OBIS Identifier (A-x:C.D.E.x) * * @param groupA A value - * @param groupB B value * @param groupC C value * @param groupD D value * @param groupE E value - * @param groupF F value * @param conflict if true indicates this OBIS Identifier is used for different types of data. */ - public OBISIdentifier(int groupA, @Nullable Integer groupB, int groupC, int groupD, @Nullable Integer groupE, - @Nullable Integer groupF, boolean conflict) { + public OBISIdentifier(final int groupA, final int groupC, final int groupD, @Nullable final Integer groupE, + final boolean conflict) { this.groupA = groupA; - this.groupB = groupB; + this.channel = null; this.groupC = groupC; this.groupD = groupD; this.groupE = groupE; - this.groupF = groupF; + this.groupF = null; this.conflict = conflict; } @@ -91,33 +86,25 @@ public OBISIdentifier(int groupA, @Nullable Integer groupB, int groupC, int grou * @param obisIDString the OBIS String ID * @throws ParseException if obisIDString is not a valid OBIS Identifier */ - public OBISIdentifier(String obisIDString) throws ParseException { - Matcher m = OBIS_ID_PATTERN.matcher(obisIDString); + public OBISIdentifier(final String obisIDString) throws ParseException { + final Matcher m = OBIS_ID_PATTERN.matcher(obisIDString); if (m.matches()) { // Optional value A - if (m.group(2) != null) { - this.groupA = Integer.parseInt(m.group(2)); - } + this.groupA = m.group(2) == null ? null : Integer.parseInt(m.group(2)); // Optional value B - if (m.group(4) != null) { - this.groupB = Integer.valueOf(m.group(4)); - } + this.channel = m.group(4) == null ? null : Integer.valueOf(m.group(4)); // Required value C & D this.groupC = Integer.parseInt(m.group(6)); this.groupD = Integer.parseInt(m.group(7)); // Optional value E - if (m.group(9) != null) { - this.groupE = Integer.valueOf(m.group(9)); - } + this.groupE = m.group(9) == null ? null : Integer.valueOf(m.group(9)); // Optional value F - if (m.group(11) != null) { - this.groupF = Integer.valueOf(m.group(11)); - } + this.groupF = m.group(11) == null ? null : Integer.valueOf(m.group(11)); } else { throw new ParseException("Invalid OBIS identifier:" + obisIDString, 0); } @@ -135,10 +122,10 @@ public int getGroupA() { } /** - * @return the groupB + * @return the M-bus channel */ - public @Nullable Integer getGroupB() { - return groupB; + public @Nullable Integer getChannel() { + return channel; } /** @@ -171,7 +158,7 @@ public int getGroupD() { @Override public String toString() { - return groupA + "-" + (groupB == null ? "" : (groupB + ":")) + groupC + "." + groupD + return groupA + "-" + (channel == null ? "" : (channel + ":")) + groupC + "." + groupD + (groupE == null ? "" : ("." + groupE)) + (groupF == null ? "" : ("*" + groupF)); } @@ -184,7 +171,7 @@ public String toString() { * @return true if both OBISIdentifiers match, false otherwise */ @Override - public boolean equals(@Nullable Object other) { + public boolean equals(@Nullable final Object other) { OBISIdentifier o; if (other != null && other instanceof OBISIdentifier) { o = (OBISIdentifier) other; @@ -194,9 +181,9 @@ public boolean equals(@Nullable Object other) { boolean result = true; result &= groupA == o.groupA; - if (groupB != null && o.groupB != null) { - result &= (groupB.equals(o.groupB)); - } else if (!(groupB == null && o.groupB == null)) { + if (channel != null && o.channel != null) { + result &= (channel.equals(o.channel)); + } else if (!(channel == null && o.channel == null)) { result = false; } result &= groupC == o.groupC; @@ -222,12 +209,12 @@ public boolean equals(@Nullable Object other) { * * @return true if identifiers match fully or against a wildcard, false otherwise */ - public boolean equalsWildCard(OBISIdentifier o) { + public boolean equalsWildCard(final OBISIdentifier o) { boolean result = true; result &= groupA == o.groupA; - if (groupB != null && o.groupB != null) { - result &= (groupB.equals(o.groupB)); + if (channel != null && o.channel != null) { + result &= (channel.equals(o.channel)); } result &= groupC == o.groupC; result &= groupD == o.groupD; @@ -243,39 +230,25 @@ public boolean equalsWildCard(OBISIdentifier o) { @Override public int hashCode() { - return Objects.hash(groupA, (groupB != null ? groupB : 0), groupC, groupD, (groupE != null ? groupE : 0), + return Objects.hash(groupA, (channel != null ? channel : 0), groupC, groupD, (groupE != null ? groupE : 0), (groupF != null ? groupF : 0)); } /** - * Returns an reduced OBIS Identifier. This means group F is set to null - * (.i.e. not applicable) + * Returns an reduced OBIS Identifier. * * @return reduced OBIS Identifier */ public OBISIdentifier getReducedOBISIdentifier() { - return new OBISIdentifier(groupA, groupB, groupC, groupD, groupE, null); + return new OBISIdentifier(groupA, groupC, groupD, groupE); } /** - * Returns an reduced OBIS Identifier with both group E and F is set to null - * (.i.e. not applicable) + * Returns an reduced OBIS Identifier with group E set to null (.i.e. not applicable) * * @return reduced OBIS Identifier */ public OBISIdentifier getReducedOBISIdentifierGroupE() { - return new OBISIdentifier(groupA, groupB, groupC, groupD, null, null); - } - - /** - * Returns whether or not the reduced OBIS Identifier is a wildcard identifier (meaning groupA groupB or groupC is - * null) - * Note that the DSMR specification does not use groupF so this is implemented always as a wildcard. - * To distinguish wildcard from non wildcard OBISIdentifiers, groupF is ignored. - * - * @return true if the reducedOBISIdentifier is a wildcard identifier, false otherwise. - */ - public boolean reducedOBISIdentifierIsWildCard() { - return groupB == null; + return new OBISIdentifier(groupA, groupC, groupD, null); } } diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParser.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParser.java index 5dd129f768ea7..af8de9445b30e 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParser.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParser.java @@ -128,13 +128,23 @@ private enum State { */ private final P1TelegramListener telegramListener; + /** + * Enable in tests. Will throw an exception on CRC error. + */ + private final boolean test; + /** * Creates a new P1TelegramParser * * @param telegramListener */ public P1TelegramParser(P1TelegramListener telegramListener) { + this(telegramListener, false); + } + + public P1TelegramParser(P1TelegramListener telegramListener, boolean test) { this.telegramListener = telegramListener; + this.test = test; factory = new CosemObjectFactory(); state = State.WAIT_FOR_START; @@ -151,7 +161,7 @@ public P1TelegramParser(P1TelegramListener telegramListener) { @Override public void parse(byte[] data, int length) { if (lenientMode || logger.isTraceEnabled()) { - String rawBlock = new String(data, 0, length, StandardCharsets.UTF_8); + final String rawBlock = new String(data, 0, length, StandardCharsets.UTF_8); if (lenientMode) { rawData.append(rawBlock); @@ -161,7 +171,7 @@ public void parse(byte[] data, int length) { } } for (int i = 0; i < length; i++) { - char c = (char) data[i]; + final char c = (char) data[i]; switch (state) { case WAIT_FOR_START: @@ -245,22 +255,7 @@ public void parse(byte[] data, int length) { logger.trace("telegramState {}, crcValue to check 0x{}", telegramState, crcValue); // Only perform CRC check if telegram is still ok if (telegramState == TelegramState.OK && crcValue.length() > 0) { - if (Pattern.matches(CRC_PATTERN, crcValue)) { - int crcP1Telegram = Integer.parseInt(crcValue.toString(), 16); - int calculatedCRC = crc.getCurrentCRCCode(); - - if (logger.isDebugEnabled()) { - logger.trace("received CRC value: {}, calculated CRC value: 0x{}", crcValue, - String.format("%04X", calculatedCRC)); - } - if (crcP1Telegram != calculatedCRC) { - logger.trace("CRC value does not match, p1 Telegram failed"); - - telegramState = TelegramState.CRC_ERROR; - } - } else { - telegramState = TelegramState.CRC_ERROR; - } + telegramState = checkCRC(telegramState); } telegramListener.telegramReceived(constructTelegram()); reset(); @@ -280,6 +275,34 @@ public void parse(byte[] data, int length) { logger.trace("State after parsing: {}", state); } + private TelegramState checkCRC(TelegramState currentState) { + final TelegramState telegramState; + + if (Pattern.matches(CRC_PATTERN, crcValue)) { + final int crcP1Telegram = Integer.parseInt(crcValue.toString(), 16); + final int calculatedCRC = crc.getCurrentCRCCode(); + + if (logger.isDebugEnabled()) { + logger.trace("received CRC value: {}, calculated CRC value: 0x{}", crcValue, + String.format("%04X", calculatedCRC)); + } + if (crcP1Telegram != calculatedCRC) { + if (test) { + throw new IllegalArgumentException( + String.format("Invalid CRC. Read: %s, expected: %04X", crcValue, calculatedCRC)); + } + logger.trace("CRC value does not match, p1 Telegram failed"); + + telegramState = TelegramState.CRC_ERROR; + } else { + telegramState = currentState; + } + } else { + telegramState = TelegramState.CRC_ERROR; + } + return telegramState; + } + private P1Telegram constructTelegram() { final List cosemObjectsCopy = new ArrayList<>(cosemObjects); @@ -375,11 +398,11 @@ private void clearObisData() { * Store the current CosemObject in the list of received cosem Objects */ private void storeCurrentCosemObject() { - String obisIdString = obisId.toString(); + final String obisIdString = obisId.toString(); if (!obisIdString.isEmpty()) { final String obisValueString = obisValue.toString(); - CosemObject cosemObject = factory.getCosemObject(obisIdString, obisValueString); + final CosemObject cosemObject = factory.getCosemObject(obisIdString, obisValueString); if (cosemObject == null) { if (lenientMode) { diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDetector.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDetector.java index e5a446677de6e..2739cd6f8fb3e 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDetector.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDetector.java @@ -13,10 +13,13 @@ package org.openhab.binding.dsmr.internal.discovery; import java.util.AbstractMap.SimpleEntry; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.dsmr.internal.device.cosem.CosemObject; @@ -46,19 +49,15 @@ class DSMRMeterDetector { * @param telegram The received telegram * @return collection of detected {@link DSMRMeterDescriptor} */ - public Entry, Map> detectMeters(P1Telegram telegram) { + public Entry, List> detectMeters(P1Telegram telegram) { final Map detectedMeters = new HashMap<>(); - final Map availableCosemObjects = new HashMap<>(); - final Map undetectedCosemObjects = new HashMap<>(); - - // Fill hashmap for fast comparing the set of received Cosem objects to the required set of Cosem Objects - telegram.getCosemObjects().forEach(msg -> availableCosemObjects.put(msg.getType(), msg)); - undetectedCosemObjects.putAll(availableCosemObjects); + final List availableCosemObjects = List.copyOf(telegram.getCosemObjects()); + final List undetectedCosemObjects = new ArrayList<>(telegram.getCosemObjects()); // Find compatible meters for (DSMRMeterType meterType : DSMRMeterType.values()) { logger.trace("Trying if meter type {} is compatible", meterType); - final DSMRMeterDescriptor meterDescriptor = meterType.isCompatible(availableCosemObjects); + final DSMRMeterDescriptor meterDescriptor = meterType.findCompatible(availableCosemObjects); if (meterDescriptor == null) { logger.trace("Meter type {} is not compatible", meterType); @@ -74,7 +73,9 @@ public Entry, Map> logger.debug("New compatible meter: {}", meterDescriptor); detectedMeters.put(meterType.meterKind, meterDescriptor); for (CosemObjectType cot : meterDescriptor.getMeterType().supportedCosemObjects) { - undetectedCosemObjects.remove(cot); + List collect = undetectedCosemObjects.stream().filter(u -> cot == u.getType()) + .collect(Collectors.toList()); + collect.forEach(undetectedCosemObjects::remove); } } } diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDiscoveryService.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDiscoveryService.java index a742eb1208fc8..be9f7e5273143 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDiscoveryService.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDiscoveryService.java @@ -14,7 +14,6 @@ import java.util.Collection; import java.util.List; -import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; @@ -101,7 +100,7 @@ public void telegramReceived(P1Telegram telegram) { if (logger.isDebugEnabled()) { logger.debug("Detect meters from #{} objects", telegram.getCosemObjects().size()); } - final Entry, Map> detectedMeters = meterDetector + final Entry, List> detectedMeters = meterDetector .detectMeters(telegram); verifyUnregisteredCosemObjects(telegram, detectedMeters.getValue()); validateConfiguredMeters(dsmrBridgeHandler.getThing().getThings(), @@ -109,17 +108,16 @@ public void telegramReceived(P1Telegram telegram) { detectedMeters.getKey().forEach(m -> meterDiscovered(m, dsmrBridgeHandler.getThing().getUID())); } - protected void verifyUnregisteredCosemObjects(P1Telegram telegram, - Map undetectedCosemObjects) { - if (!undetectedCosemObjects.isEmpty()) { - if (undetectedCosemObjects.entrySet().stream() - .anyMatch(e -> e.getKey() == CosemObjectType.METER_EQUIPMENT_IDENTIFIER - && e.getValue().getCosemValues().entrySet().stream().anyMatch( + protected void verifyUnregisteredCosemObjects(P1Telegram telegram, List list) { + if (!list.isEmpty()) { + if (list.stream() + .anyMatch(e -> e.getType() == CosemObjectType.METER_EQUIPMENT_IDENTIFIER + && e.getCosemValues().entrySet().stream().anyMatch( cv -> cv.getValue() instanceof StringType && cv.getValue().toString().isEmpty()))) { // Unregistered meter detected. log to the user. reportUnregisteredMeters(); } else { - reportUnrecognizedCosemObjects(undetectedCosemObjects); + reportUnrecognizedCosemObjects(list); logger.info("There are unrecognized cosem values in the data received from the meter," + " which means some meters might not be detected. Please report your raw data as reference: {}", telegram.getRawTelegram()); @@ -138,11 +136,10 @@ protected void verifyUnregisteredCosemObjects(P1Telegram telegram, /** * Called when Unrecognized cosem objects where found. This can be a bug or a new meter not yet supported. * - * @param unidentifiedCosemObjects Map with the unrecognized. + * @param list Map with the unrecognized. */ - protected void reportUnrecognizedCosemObjects(Map unidentifiedCosemObjects) { - unidentifiedCosemObjects - .forEach((k, v) -> logger.info("Unrecognized cosem object '{}' found in the data: {}", k, v)); + protected void reportUnrecognizedCosemObjects(List list) { + list.forEach(c -> logger.info("Unrecognized cosem object '{}' found in the data: {}", c.getType(), c)); } /** diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRBridgeHandler.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRBridgeHandler.java index fd1e757e15783..12c74c572dd7c 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRBridgeHandler.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRBridgeHandler.java @@ -220,7 +220,7 @@ public boolean unregisterDSMRMeterListener(P1TelegramListener meterListener) { */ private void alive() { logger.trace("Bridge alive check with #{} children.", getThing().getThings().size()); - long deltaLastReceived = System.nanoTime() - telegramReceivedTimeNanos; + final long deltaLastReceived = System.nanoTime() - telegramReceivedTimeNanos; if (deltaLastReceived > receivedTimeoutNanos) { logger.debug("No data received for {} seconds, restarting port if possible.", @@ -271,13 +271,15 @@ public void handleErrorEvent(DSMRConnectorErrorEvent portEvent) { * @param telegram received meter values. */ private void meterValueReceived(P1Telegram telegram) { - updateStatus(ThingStatus.ONLINE); + if (isInitialized() && getThing().getStatus() != ThingStatus.ONLINE) { + updateStatus(ThingStatus.ONLINE); + } getThing().getThings().forEach(child -> { if (logger.isTraceEnabled()) { logger.trace("Update child:{} with {} objects", child.getThingTypeUID().getId(), telegram.getCosemObjects().size()); } - DSMRMeterHandler dsmrMeterHandler = (DSMRMeterHandler) child.getHandler(); + final DSMRMeterHandler dsmrMeterHandler = (DSMRMeterHandler) child.getHandler(); if (dsmrMeterHandler instanceof DSMRMeterHandler) { dsmrMeterHandler.telegramReceived(telegram); diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRMeterHandler.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRMeterHandler.java index fafd4966d7721..ec0fb1fe26f3e 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRMeterHandler.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRMeterHandler.java @@ -25,6 +25,7 @@ import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener; import org.openhab.binding.dsmr.internal.meter.DSMRMeter; import org.openhab.binding.dsmr.internal.meter.DSMRMeterConfiguration; +import org.openhab.binding.dsmr.internal.meter.DSMRMeterConstants; import org.openhab.binding.dsmr.internal.meter.DSMRMeterDescriptor; import org.openhab.binding.dsmr.internal.meter.DSMRMeterType; import org.openhab.core.thing.ChannelUID; @@ -66,6 +67,11 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList */ private @NonNullByDefault({}) ScheduledFuture meterWatchdog; + /** + * The M-bus channel this meter is on, or if the channel is irrelevant is set to unknown channel + */ + private int channel = DSMRMeterConstants.UNKNOWN_CHANNEL; + /** * Creates a new MeterHandler for the given Thing. * @@ -106,7 +112,8 @@ public void initialize() { return; } DSMRMeterConfiguration meterConfig = getConfigAs(DSMRMeterConfiguration.class); - DSMRMeterDescriptor meterDescriptor = new DSMRMeterDescriptor(meterType, meterConfig.channel); + channel = meterType.meterKind.isChannelRelevant() ? meterConfig.channel : DSMRMeterConstants.UNKNOWN_CHANNEL; + DSMRMeterDescriptor meterDescriptor = new DSMRMeterDescriptor(meterType, channel); meter = new DSMRMeter(meterDescriptor); meterWatchdog = scheduler.scheduleWithFixedDelay(this::updateState, meterConfig.refresh, meterConfig.refresh, TimeUnit.SECONDS); @@ -163,7 +170,7 @@ public void telegramReceived(P1Telegram telegram) { if (localMeter == null) { return; } - List filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects()); + List filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects(), channel); if (filteredValues.isEmpty()) { if (getThing().getStatus() == ThingStatus.ONLINE) { diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeter.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeter.java index 13c7294ab137a..ccafec3985958 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeter.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeter.java @@ -56,9 +56,9 @@ public DSMRMeter(DSMRMeterDescriptor meterDescriptor) { for (CosemObjectType msgType : meterDescriptor.getMeterType().supportedCosemObjects) { OBISIdentifier obisId = msgType.obisId; - if (msgType.obisId.getGroupB() == null) { - supportedIdentifiers.add(new OBISIdentifier(obisId.getGroupA(), meterDescriptor.getChannel(), - obisId.getGroupC(), obisId.getGroupD(), obisId.getGroupE(), msgType.obisId.getGroupF())); + if (msgType.obisId.getChannel() == null) { + supportedIdentifiers.add(new OBISIdentifier(obisId.getGroupA(), obisId.getGroupC(), obisId.getGroupD(), + obisId.getGroupE())); } else { supportedIdentifiers.add(msgType.obisId); } @@ -71,11 +71,12 @@ public DSMRMeter(DSMRMeterDescriptor meterDescriptor) { * @param cosemObjects list of CosemObject that must be processed and where the objects of this meter are removed * @return List of CosemObject that this meter can process */ - public List filterMeterValues(List cosemObjects) { + public List filterMeterValues(List cosemObjects, int channel) { logger.trace("supported identifiers: {}, searching for objects {}", supportedIdentifiers, cosemObjects); List filteredValues = cosemObjects.stream() - .filter(cosemObject -> supportedIdentifiers - .contains(cosemObject.getObisIdentifier().getReducedOBISIdentifier())) + .filter(cosemObject -> (DSMRMeterConstants.UNKNOWN_CHANNEL == channel + || cosemObject.getObisIdentifier().getChannel() == channel) + && supportedIdentifiers.contains(cosemObject.getObisIdentifier().getReducedOBISIdentifier())) .collect(Collectors.toList()); return filteredValues; } diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterDescriptor.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterDescriptor.java index 8e29833c869fe..5609d86f2cdac 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterDescriptor.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterDescriptor.java @@ -44,7 +44,6 @@ public class DSMRMeterDescriptor { * * @param meterType The meter type * @param channel The M-Bus channel this meter is connected to - * @throws IllegalArgumentException if one of the parameters is null */ public DSMRMeterDescriptor(DSMRMeterType meterType, int channel) { this.meterType = meterType; diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterKind.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterKind.java index 4904659ff867b..3b99946ffecc3 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterKind.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterKind.java @@ -12,14 +12,17 @@ */ package org.openhab.binding.dsmr.internal.meter; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * This class describes the kind of meters the binding supports * * @author M. Volaart - Initial contribution */ +@NonNullByDefault public enum DSMRMeterKind { INVALID, - DEVICE, + DEVICE(false), MAIN_ELECTRICITY, GAS, HEATING, @@ -31,6 +34,20 @@ public enum DSMRMeterKind { SLAVE_ELECTRICITY1, SLAVE_ELECTRICITY2; + private final boolean channelRelevant; + + private DSMRMeterKind() { + this(true); + } + + private DSMRMeterKind(final boolean channelRelevant) { + this.channelRelevant = channelRelevant; + } + + public boolean isChannelRelevant() { + return channelRelevant; + } + /** * @return Returns the i18n label key for this meter. */ diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterType.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterType.java index 53bd1f2f5427b..02d3cb62de3b6 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterType.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterType.java @@ -13,10 +13,18 @@ package org.openhab.binding.dsmr.internal.meter; import java.util.Arrays; +import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.dsmr.internal.DSMRBindingConstants; import org.openhab.binding.dsmr.internal.device.cosem.CosemObject; import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectType; @@ -29,6 +37,7 @@ * * @author M. Volaart - Initial contribution */ +@NonNullByDefault public enum DSMRMeterType { // Don't auto format the enum list. For readability the format for the enum is: // First line parameters; DSMRMeterKind and CosemObjectType (identification object type) @@ -143,7 +152,7 @@ public enum DSMRMeterType { CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR, CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION}, new CosemObjectType[] { - CosemObjectType.EMETER_TRESHOLD_A, CosemObjectType.EMETER_TRESHOLD_KWH, + CosemObjectType.EMETER_TRESHOLD_A, CosemObjectType.EMETER_TRESHOLD_KW, CosemObjectType.EMETER_SWITCH_POSITION}), /** DSMR V3.0 Gas meter */ @@ -175,7 +184,7 @@ public enum DSMRMeterType { CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER, CosemObjectType.EMETER_DELIVERY_TARIFF1, CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1, CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR, - CosemObjectType.EMETER_TRESHOLD_KWH, CosemObjectType.EMETER_SWITCH_POSITION, + CosemObjectType.EMETER_TRESHOLD_KW, CosemObjectType.EMETER_SWITCH_POSITION, CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION, CosemObjectType.EMETER_POWER_FAILURES, CosemObjectType.EMETER_LONG_POWER_FAILURES, CosemObjectType.EMETER_VOLTAGE_SAGS_L1, CosemObjectType.EMETER_VOLTAGE_SWELLS_L1 }, @@ -205,7 +214,7 @@ public enum DSMRMeterType { CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER, CosemObjectType.EMETER_DELIVERY_TARIFF1, CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1, CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR, - CosemObjectType.EMETER_TRESHOLD_KWH, CosemObjectType.EMETER_SWITCH_POSITION, + CosemObjectType.EMETER_TRESHOLD_KW, CosemObjectType.EMETER_SWITCH_POSITION, CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION, CosemObjectType.EMETER_POWER_FAILURES, CosemObjectType.EMETER_LONG_POWER_FAILURES, CosemObjectType.EMETER_VOLTAGE_SAGS_L1, CosemObjectType.EMETER_VOLTAGE_SWELLS_L1, @@ -280,9 +289,10 @@ public enum DSMRMeterType { CosemObjectType.EMETER_PRODUCTION_TARIFF0, CosemObjectType.EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_Q, CosemObjectType.EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_Q, CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION, CosemObjectType.EMETER_ACTUAL_REACTIVE_DELIVERY, - CosemObjectType.EMETER_ACTUAL_REACTIVE_PRODUCTION, CosemObjectType.EMETER_ACTIVE_THRESHOLD_SMAX, + CosemObjectType.EMETER_ACTUAL_REACTIVE_PRODUCTION, CosemObjectType.EMETER_SWITCH_POSITION }, new CosemObjectType[] { + CosemObjectType.EMETER_TRESHOLD_KW, CosemObjectType.EMETER_ACTIVE_THRESHOLD_SMAX, CosemObjectType.EMETER_POWER_FAILURES, CosemObjectType.EMETER_VOLTAGE_SAGS_L1, CosemObjectType.EMETER_VOLTAGE_SAGS_L2, CosemObjectType.EMETER_VOLTAGE_SAGS_L3, CosemObjectType.EMETER_VOLTAGE_SWELLS_L1, CosemObjectType.EMETER_VOLTAGE_SWELLS_L2, @@ -294,7 +304,23 @@ public enum DSMRMeterType { CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L1, CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L2, CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L3, CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L1, CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L2, CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L3, - }), + }), + /** Austrian "Smarty" meter */ + ELECTRICITY_SMARTY_V1_0_AUSTRIA(DSMRMeterKind.MAIN_ELECTRICITY, CosemObjectType.UNKNOWN, + new CosemObjectType[] { + CosemObjectType.EMETER_DELIVERY_TARIFF0, CosemObjectType.EMETER_DELIVERY_TARIFF1, + CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF0, + CosemObjectType.EMETER_PRODUCTION_TARIFF1, CosemObjectType.EMETER_PRODUCTION_TARIFF2, + CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION, + CosemObjectType.EMETER_ACTUAL_REACTIVE_DELIVERY, CosemObjectType.EMETER_ACTUAL_REACTIVE_PRODUCTION, + CosemObjectType.EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_Q, CosemObjectType.EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_Q, + CosemObjectType.EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_R_RATE1, CosemObjectType.EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_R_RATE2, + CosemObjectType.EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_R_RATE1, CosemObjectType.EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_R_RATE2, + }, + new CosemObjectType[] { + CosemObjectType.P1_VERSION_OUTPUT, CosemObjectType.P1_TIMESTAMP, + }), + /** Belgium Smart Meter for the e-MUCS specification */ DEVICE_EMUCS_V1_0(DSMRMeterKind.DEVICE, CosemObjectType.UNKNOWN, CosemObjectType.P1_TEXT_STRING, CosemObjectType.P1_TEXT_STRING, CosemObjectType.P1_EMUCS_VERSION_OUTPUT, @@ -307,13 +333,16 @@ public enum DSMRMeterType { CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1, CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR, CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION, - CosemObjectType.EMETER_TRESHOLD_KWH, CosemObjectType.EMETER_FUSE_THRESHOLD_A, + CosemObjectType.EMETER_TRESHOLD_KW, CosemObjectType.EMETER_FUSE_THRESHOLD_A, CosemObjectType.EMETER_SWITCH_POSITION}, new CosemObjectType[] { + CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L1, CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L2, + CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L3, CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L1, + CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L2, CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L3, CosemObjectType.EMETER_INSTANT_CURRENT_L1, CosemObjectType.EMETER_INSTANT_CURRENT_L2, CosemObjectType.EMETER_INSTANT_CURRENT_L3, CosemObjectType.EMETER_INSTANT_VOLTAGE_L1, CosemObjectType.EMETER_INSTANT_VOLTAGE_L2, CosemObjectType.EMETER_INSTANT_VOLTAGE_L3 - }), + }), /** Belgium Smart Gas Meter for the e-MUCS specification */ GAS_EMUCS_V1_0(DSMRMeterKind.GAS, CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER, @@ -398,23 +427,36 @@ public enum DSMRMeterType { * @param availableCosemObjects the Cosem Objects to detect if the current meter compatible * @return {@link DSMRMeterDescriptor} containing the identification of the compatible meter */ - public DSMRMeterDescriptor isCompatible(Map availableCosemObjects) { - DSMRMeterDescriptor meterDescriptor = null; - - for (CosemObjectType objectType : requiredCosemObjects) { - if (!availableCosemObjects.containsKey(objectType)) { - logger.trace("Required objectType {} not found", objectType); + public @Nullable DSMRMeterDescriptor findCompatible(List availableCosemObjects) { + final Map<@Nullable Integer, AtomicInteger> channelCounter = new HashMap<>(3); + + for (final CosemObjectType objectType : requiredCosemObjects) { + final AtomicBoolean match = new AtomicBoolean(); + availableCosemObjects.stream().filter(a -> a.getType() == objectType).forEach(b -> { + match.set(true); + channelCounter.computeIfAbsent(b.getObisIdentifier().getChannel(), t -> new AtomicInteger()) + .incrementAndGet(); + }); + if (!match.get()) { + logger.trace("Required objectType {} not found for meter: {}", objectType, this); return null; - } else { - logger.trace("FOUND Required objectType {}", objectType); } - CosemObject cosemObject = availableCosemObjects.get(objectType); + } + DSMRMeterDescriptor meterDescriptor = null; - // Checking by reference is possible here due to comparing enums - if (cosemObjectTypeMeterId != CosemObjectType.UNKNOWN && objectType == cosemObjectTypeMeterId) { - meterDescriptor = new DSMRMeterDescriptor(this, cosemObject.getObisIdentifier().getGroupB()); + if (meterKind.isChannelRelevant()) { + final Optional> max = channelCounter.entrySet().stream() + .max((e1, e2) -> Integer.compare(e1.getValue().get(), e2.getValue().get())); + + if (max.isPresent()) { + final Integer channel = max.get().getKey(); + meterDescriptor = new DSMRMeterDescriptor(this, + channel == null ? DSMRMeterConstants.UNKNOWN_CHANNEL : channel); } + } else { + meterDescriptor = new DSMRMeterDescriptor(this, DSMRMeterConstants.UNKNOWN_CHANNEL); } + // Meter type is compatible, check if an identification exists if (meterDescriptor == null && cosemObjectTypeMeterId == CosemObjectType.UNKNOWN) { logger.trace("Meter type {} has no identification, but is compatible", this); diff --git a/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/binding/binding.xml index eb72076ccd8da..807ad5a20ccd6 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/binding/binding.xml +++ b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/binding/binding.xml @@ -4,5 +4,5 @@ xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd"> DSMR Binding - This binding integrates Dutch and Luxembourg Smart Meters + This binding integrates Dutch, Belgium, Luxembourg and Austrian Smart Meters diff --git a/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/config/configuration_parameters.xml b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/config/configuration_parameters.xml index 8b07743197eb5..c250895bec5c4 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/config/configuration_parameters.xml +++ b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/config/configuration_parameters.xml @@ -12,7 +12,7 @@ The serial port where the P1 port of the Smart Meter is connected (e.g. Linux: /dev/ttyUSB0, Windows: COM1) - + 30 The time period within results are expected in seconds @@ -76,7 +76,7 @@ The Luxembourgian Smart meter decryption key. Ask for your energy grid operator for your Smart meter P1 key. - + 30 The time period within results are expected in seconds @@ -84,7 +84,7 @@ - + 60 The time interval the data is refreshed in seconds diff --git a/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/channeltypes_electricity.xml b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/channeltypes_electricity.xml index bd34f7f647f2d..dd94465e5c742 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/channeltypes_electricity.xml +++ b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/channeltypes_electricity.xml @@ -90,25 +90,49 @@ Number:Energy - The Total imported energy register (P+). + The total imported energy register (P+). Number:Energy - The Total exported energy register (P-). + The total exported energy register (P-). Number:Energy - The Total imported energy register (Q+). + The total imported energy register (Q+). Number:Energy - The Total exported energy register (Q-). + The total exported energy register (Q-). + + + + Number:Energy + + The total exported energy register R Rate1. + + + + Number:Energy + + The total exported energy register R Rate2. + + + + Number:Energy + + The total imported energy register R Rate1. + + + + Number:Energy + + The total imported energy register R Rate2. diff --git a/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_dsmr_v4.xml b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_dsmr_v4.xml index a5b60f804b923..59dcd198be45c 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_dsmr_v4.xml +++ b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_dsmr_v4.xml @@ -19,7 +19,7 @@ - + diff --git a/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_dsmr_v4_0_4.xml b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_dsmr_v4_0_4.xml index a66b2d1f2a01c..ef2867869a3ba 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_dsmr_v4_0_4.xml +++ b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_dsmr_v4_0_4.xml @@ -19,7 +19,7 @@ - + diff --git a/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_emucs_v1_0.xml b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_emucs_v1_0.xml index 33f70144a7309..8402bb3dbf195 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_emucs_v1_0.xml +++ b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_emucs_v1_0.xml @@ -24,6 +24,12 @@ + + + + + + diff --git a/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_smarty_v1.xml b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_smarty_v1.xml index 44bc4d830b528..4acf3e97cdf38 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_smarty_v1.xml +++ b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_smarty_v1.xml @@ -23,6 +23,7 @@ + diff --git a/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_smarty_v1_austria.xml b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_smarty_v1_austria.xml new file mode 100644 index 0000000000000..daed1a2d19947 --- /dev/null +++ b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_smarty_v1_austria.xml @@ -0,0 +1,37 @@ + + + + + + + + + + This is an electricity meter that complies to the Austria's Smarty V1.0 specification. + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/TelegramReaderUtil.java b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/TelegramReaderUtil.java index a27edc4ae2441..a0de494696c80 100644 --- a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/TelegramReaderUtil.java +++ b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/TelegramReaderUtil.java @@ -12,7 +12,9 @@ */ package org.openhab.binding.dsmr.internal; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.io.InputStream; @@ -48,7 +50,7 @@ public static byte[] readRawTelegram(String telegramName) { fail("Could not find telegram file with name:" + telegramName + TELEGRAM_EXT); } return is.readAllBytes(); - } catch (IOException e) { + } catch (final IOException e) { throw new AssertionError("IOException reading telegram data: ", e); } } @@ -61,9 +63,9 @@ public static byte[] readRawTelegram(String telegramName) { * @return a P1Telegram object */ public static P1Telegram readTelegram(String telegramName, TelegramState expectedTelegramState) { - AtomicReference p1Telegram = new AtomicReference<>(); - byte[] telegram = readRawTelegram(telegramName); - P1TelegramParser parser = new P1TelegramParser(p1Telegram::set); + final AtomicReference p1Telegram = new AtomicReference<>(); + final byte[] telegram = readRawTelegram(telegramName); + final P1TelegramParser parser = new P1TelegramParser(p1Telegram::set, true); parser.setLenientMode(true); parser.parse(telegram, telegram.length); diff --git a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParserTest.java b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParserTest.java index c0ed6273512b1..da7e8d6f4fa07 100644 --- a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParserTest.java +++ b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParserTest.java @@ -38,8 +38,10 @@ public static final List data() { { "dsmr_40", 39, 0}, { "dsmr_42", 39, 0}, { "dsmr_50", 41, 0}, - { "flu5_invalid_gasmeter", 19, 1}, + { "dsmr_50_austria", 18, 0}, { "flu5", 21, 0}, + { "flu5_extra", 31, 0}, + { "flu5_invalid_gasmeter", 19, 1}, { "Iskra_AM550", 41, 0}, { "Landis_Gyr_E350", 10, 0}, { "Landis_Gyr_ZCF110", 25, 0}, @@ -53,7 +55,7 @@ public static final List data() { @ParameterizedTest @MethodSource("data") public void testParsing(final String telegramName, final int numberOfCosemObjects, final int unknownObjects) { - P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK); + final P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK); assertEquals(unknownObjects, telegram.getUnknownCosemObjects().size(), "Should not have other than " + unknownObjects + " unknown cosem objects"); assertEquals(numberOfCosemObjects, diff --git a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDetectorTest.java b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDetectorTest.java index 06b51caeb0c16..599857ec2aa1d 100644 --- a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDetectorTest.java +++ b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDetectorTest.java @@ -13,14 +13,26 @@ package org.openhab.binding.dsmr.internal.discovery; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.*; +import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.DEVICE_EMUCS_V1_0; +import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.DEVICE_V2_V3; +import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.DEVICE_V4; +import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.DEVICE_V5; +import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.ELECTRICITY_ACE4000; +import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.ELECTRICITY_EMUCS_V1_0; +import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.ELECTRICITY_SMARTY_V1_0; +import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.ELECTRICITY_SMARTY_V1_0_AUSTRIA; +import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.ELECTRICITY_V3_0; +import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.ELECTRICITY_V4_2; +import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.ELECTRICITY_V5_0; +import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.GAS_ACE4000; +import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.GAS_EMUCS_V1_0; +import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.M3_V4; +import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.M3_V5_0; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.EnumSet; import java.util.List; -import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -29,7 +41,6 @@ import org.junit.jupiter.params.provider.MethodSource; import org.openhab.binding.dsmr.internal.TelegramReaderUtil; import org.openhab.binding.dsmr.internal.device.cosem.CosemObject; -import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectType; import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram; import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState; import org.openhab.binding.dsmr.internal.meter.DSMRMeterDescriptor; @@ -46,17 +57,19 @@ public class DSMRMeterDetectorTest { // @formatter:off public static final List data() { return Arrays.asList(new Object[][] { - { "ace4000", EnumSet.of( ELECTRICITY_ACE4000, GAS_ACE4000)}, - { "dsmr_40", EnumSet.of( DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)}, - { "dsmr_42", EnumSet.of( DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)}, - { "dsmr_50", EnumSet.of( DEVICE_V5, ELECTRICITY_V5_0, M3_V5_0)}, - { "flu5", EnumSet.of( DEVICE_EMUCS_V1_0, ELECTRICITY_EMUCS_V1_0, GAS_EMUCS_V1_0)}, - { "Iskra_AM550", EnumSet.of( DEVICE_V5, ELECTRICITY_V5_0, M3_V5_0)}, - { "Landis_Gyr_E350", EnumSet.of( DEVICE_V2_V3, ELECTRICITY_V3_0)}, - { "Landis_Gyr_ZCF110", EnumSet.of( DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)}, - { "Sagemcom_XS210", EnumSet.of( DEVICE_V4, ELECTRICITY_V4_2)}, - { "smarty", EnumSet.of( DEVICE_V5, ELECTRICITY_SMARTY_V1_0)}, - { "smarty_with_units", EnumSet.of( DEVICE_V5, ELECTRICITY_SMARTY_V1_0, M3_V4)}, + { "ace4000", EnumSet.of(ELECTRICITY_ACE4000, GAS_ACE4000)}, + { "dsmr_40", EnumSet.of(DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)}, + { "dsmr_42", EnumSet.of(DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)}, + { "dsmr_50", EnumSet.of(DEVICE_V5, ELECTRICITY_V5_0, M3_V5_0)}, + { "dsmr_50_austria", EnumSet.of(ELECTRICITY_SMARTY_V1_0_AUSTRIA)}, + { "flu5", EnumSet.of(DEVICE_EMUCS_V1_0, ELECTRICITY_EMUCS_V1_0, GAS_EMUCS_V1_0)}, + { "flu5_extra", EnumSet.of(DEVICE_EMUCS_V1_0, ELECTRICITY_EMUCS_V1_0, GAS_EMUCS_V1_0)}, + { "Iskra_AM550", EnumSet.of(DEVICE_V5, ELECTRICITY_V5_0, M3_V5_0)}, + { "Landis_Gyr_E350", EnumSet.of(DEVICE_V2_V3, ELECTRICITY_V3_0)}, + { "Landis_Gyr_ZCF110", EnumSet.of(DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)}, + { "Sagemcom_XS210", EnumSet.of(DEVICE_V4, ELECTRICITY_V4_2)}, + { "smarty", EnumSet.of(DEVICE_V5, ELECTRICITY_SMARTY_V1_0)}, + { "smarty_with_units", EnumSet.of(DEVICE_V5, ELECTRICITY_SMARTY_V1_0, M3_V4)}, }); } // @formatter:on @@ -64,21 +77,17 @@ public static final List data() { @ParameterizedTest @MethodSource("data") public void testDetectMeters(final String telegramName, final Set expectedMeters) { - P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK); - DSMRMeterDetector detector = new DSMRMeterDetector(); - Entry, Map> entry = detector - .detectMeters(telegram); - Collection detectMeters = entry.getKey(); + final P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK); + final DSMRMeterDetector detector = new DSMRMeterDetector(); + final Entry, List> entry = detector.detectMeters(telegram); + final Collection detectMeters = entry.getKey(); assertEquals(expectedMeters.size(), detectMeters.size(), "Should detect correct number of meters: " + Arrays.toString(detectMeters.toArray())); - assertEquals(Collections.emptyMap(), entry.getValue(), "Should not have any undetected cosem objects: "); - assertEquals(Collections.emptyList(), telegram.getUnknownCosemObjects(), - "Should not have any unknown cosem objects"); - for (DSMRMeterType meter : expectedMeters) { - assertEquals( - - 1, detectMeters.stream().filter(e -> e.getMeterType() == meter).count(), + assertEquals(List.of(), entry.getValue(), "Should not have any undetected cosem objects: "); + assertEquals(List.of(), telegram.getUnknownCosemObjects(), "Should not have any unknown cosem objects"); + for (final DSMRMeterType meter : expectedMeters) { + assertEquals(1, detectMeters.stream().filter(e -> e.getMeterType() == meter).count(), String.format("Meter '%s' not found: %s", meter, Arrays.toString(detectMeters.toArray(new DSMRMeterDescriptor[0])))); } diff --git a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterTest.java b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterTest.java index 3220178accf0f..862a61c81de19 100644 --- a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterTest.java +++ b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterTest.java @@ -35,12 +35,19 @@ public class DSMRMeterTest { */ @Test public void testFilterMeterValues() { - DSMRMeterDescriptor descriptor = new DSMRMeterDescriptor(DSMRMeterType.DEVICE_V5, 0); - DSMRMeter meter = new DSMRMeter(descriptor); + final List cosemObjects = TelegramReaderUtil.readTelegram("dsmr_50", TelegramState.OK) + .getCosemObjects(); - List filterMeterValues = meter - .filterMeterValues(TelegramReaderUtil.readTelegram("dsmr_50", TelegramState.OK).getCosemObjects()); - assertEquals(DSMRMeterType.DEVICE_V5.requiredCosemObjects.length, filterMeterValues.size(), - "Filter should return all required objects"); + assertMeterValues(cosemObjects, DSMRMeterType.DEVICE_V5, DSMRMeterConstants.UNKNOWN_CHANNEL, 3); + assertMeterValues(cosemObjects, DSMRMeterType.ELECTRICITY_V5_0, 0, 29); + assertMeterValues(cosemObjects, DSMRMeterType.M3_V5_0, 1, 3); + } + + private void assertMeterValues(List cosemObjects, DSMRMeterType type, int channel, int expected) { + final DSMRMeterDescriptor descriptor = new DSMRMeterDescriptor(type, channel); + final DSMRMeter meter = new DSMRMeter(descriptor); + final List filterMeterValues = meter.filterMeterValues(cosemObjects, channel); + + assertEquals(expected, filterMeterValues.size(), "Filter should return all required objects"); } } diff --git a/bundles/org.openhab.binding.dsmr/src/test/resources/org/openhab/binding/dsmr/internal/dsmr_50_austria.telegram b/bundles/org.openhab.binding.dsmr/src/test/resources/org/openhab/binding/dsmr/internal/dsmr_50_austria.telegram new file mode 100644 index 0000000000000..b9175e8b808a3 --- /dev/null +++ b/bundles/org.openhab.binding.dsmr/src/test/resources/org/openhab/binding/dsmr/internal/dsmr_50_austria.telegram @@ -0,0 +1,21 @@ +/EST5\253740976_A + +1-3:0.2.8(50) +0-0:1.0.0(210902215713S) +1-0:1.8.0(000326641*Wh) +1-0:1.8.1(000230515*Wh) +1-0:1.8.2(000096126*Wh) +1-0:1.7.0(000000621*W) +1-0:2.8.0(000004998*Wh) +1-0:2.8.1(000001350*Wh) +1-0:2.8.2(000003648*Wh) +1-0:2.7.0(000000000*W) +1-0:3.8.0(000005672*varh) +1-0:3.8.1(000001954*varh) +1-0:3.8.2(000003718*varh) +1-0:3.7.0(000000000*var) +1-0:4.8.0(000253220*varh) +1-0:4.8.1(000168985*varh) +1-0:4.8.2(000084235*varh) +1-0:4.7.0(000000510*var) +!6744 diff --git a/bundles/org.openhab.binding.dsmr/src/test/resources/org/openhab/binding/dsmr/internal/flu5_extra.telegram b/bundles/org.openhab.binding.dsmr/src/test/resources/org/openhab/binding/dsmr/internal/flu5_extra.telegram new file mode 100644 index 0000000000000..905b576103b1c --- /dev/null +++ b/bundles/org.openhab.binding.dsmr/src/test/resources/org/openhab/binding/dsmr/internal/flu5_extra.telegram @@ -0,0 +1,33 @@ +/FLU5\123456789_A + +0-0:96.1.4(50215) +0-0:96.1.1(A234567890123456789012345678) +0-0:1.0.0(210227144933W) +1-0:1.8.1(001247.747kWh) +1-0:1.8.2(002330.879kWh) +1-0:2.8.1(002159.427kWh) +1-0:2.8.2(000812.563kWh) +0-0:96.14.0(0002) +1-0:1.7.0(00.000kW) +1-0:2.7.0(00.149kW) +1-0:21.7.0(00.013kW) +1-0:41.7.0(00.108kW) +1-0:61.7.0(00.000kW) +1-0:22.7.0(00.000kW) +1-0:42.7.0(00.000kW) +1-0:62.7.0(00.271kW) +1-0:32.7.0(234.5V) +1-0:52.7.0(234.0V) +1-0:72.7.0(235.1V) +1-0:31.7.0(000.19A) +1-0:51.7.0(000.80A) +1-0:71.7.0(001.39A) +0-0:96.3.10(1) +0-0:17.0.0(999.9kW) +1-0:31.4.0(999A) +0-0:96.13.0() +0-1:24.1.0(003) +0-1:96.1.1(1234567890123456789012345678) +0-1:24.4.0(1) +0-1:24.2.3(210227144600W)(00996.617*m3) +!70C0