# 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:
* 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.
### 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.
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
- 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,
**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
```
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
*/
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,
int cosemValueItr = 0;
while (cosemValueMatcher.find()) {
- Entry<String, CosemValueDescriptor<?>> 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<String, CosemValueDescriptor<?>> 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++;
}
*/
private final Map<OBISIdentifier, List<CosemObjectType>> obisLookupTableMultipleFixed = new HashMap<>();
- /**
- * Lookup cache for dynamic OBIS Identifiers
- */
- private final Map<OBISIdentifier, CosemObjectType> obisLookupTableDynamic = new HashMap<>();
-
- /**
- * Lookup cache for wild card Cosem Object types
- */
- private final List<CosemObjectType> obisWildcardCosemTypeList = new ArrayList<>();
-
/**
* Creates a new 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.
*
* 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);
}
}
- 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;
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;
/**
* @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;
this.obisId = obisId;
if (nrOfRepeatingDescriptors == 0) {
this.descriptors = Arrays.asList(descriptors);
- this.repeatingDescriptors = Collections.emptyList();
+ this.repeatingDescriptors = List.of();
} else {
- List<CosemValueDescriptor<?>> allDescriptors = Arrays.asList(descriptors);
+ final List<CosemValueDescriptor<?>> allDescriptors = List.of(descriptors);
/*
* The last nrOfRepeatingDescriptors CosemValueDescriptor will go into the repeatingDescriptor list.
* @param idx the CosemValueDescriptor to return
* @return the CosemValueDescriptor or null if not found.
*/
- public Entry<String, CosemValueDescriptor<?>> getDescriptor(int idx) {
+ public @Nullable Entry<String, CosemValueDescriptor<?>> 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 {
public static final CosemQuantity<Power> WATT = new CosemQuantity<>(Units.WATT);
public static final CosemQuantity<Power> KILO_VAR = new CosemQuantity<>(Units.KILOVAR);
public static final CosemQuantity<Energy> KILO_VAR_HOUR = new CosemQuantity<>(Units.KILOVAR_HOUR);
+ public static final CosemQuantity<Power> 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}.
@Override
protected QuantityType<Q> getStateValue(String cosemValue) throws ParseException {
try {
- QuantityType<Q> qt = new QuantityType<>(prepare(cosemValue));
+ final QuantityType<Q> it = new QuantityType<>(prepare(cosemValue));
+ final @Nullable QuantityType<Q> 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);
}
}
* 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;
}
try {
Integer.parseInt(matcher.group(2));
return cosemValue;
- } catch (NumberFormatException e) {
+ } catch (final NumberFormatException e) {
return matcher.group(1) + ' ' + matcher.group(2);
}
}
* 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+))?";
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;
}
* @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);
}
}
/**
- * @return the groupB
+ * @return the M-bus channel
*/
- public @Nullable Integer getGroupB() {
- return groupB;
+ public @Nullable Integer getChannel() {
+ return channel;
}
/**
@Override
public String toString() {
- return groupA + "-" + (groupB == null ? "" : (groupB + ":")) + groupC + "." + groupD
+ return groupA + "-" + (channel == null ? "" : (channel + ":")) + groupC + "." + groupD
+ (groupE == null ? "" : ("." + groupE)) + (groupF == null ? "" : ("*" + groupF));
}
* @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;
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;
*
* @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;
@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);
}
}
*/
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;
@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);
}
}
for (int i = 0; i < length; i++) {
- char c = (char) data[i];
+ final char c = (char) data[i];
switch (state) {
case WAIT_FOR_START:
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();
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<CosemObject> cosemObjectsCopy = new ArrayList<>(cosemObjects);
* 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) {
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;
* @param telegram The received telegram
* @return collection of detected {@link DSMRMeterDescriptor}
*/
- public Entry<Collection<DSMRMeterDescriptor>, Map<CosemObjectType, CosemObject>> detectMeters(P1Telegram telegram) {
+ public Entry<Collection<DSMRMeterDescriptor>, List<CosemObject>> detectMeters(P1Telegram telegram) {
final Map<DSMRMeterKind, DSMRMeterDescriptor> detectedMeters = new HashMap<>();
- final Map<CosemObjectType, CosemObject> availableCosemObjects = new HashMap<>();
- final Map<CosemObjectType, CosemObject> 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<CosemObject> availableCosemObjects = List.copyOf(telegram.getCosemObjects());
+ final List<CosemObject> 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);
logger.debug("New compatible meter: {}", meterDescriptor);
detectedMeters.put(meterType.meterKind, meterDescriptor);
for (CosemObjectType cot : meterDescriptor.getMeterType().supportedCosemObjects) {
- undetectedCosemObjects.remove(cot);
+ List<CosemObject> collect = undetectedCosemObjects.stream().filter(u -> cot == u.getType())
+ .collect(Collectors.toList());
+ collect.forEach(undetectedCosemObjects::remove);
}
}
}
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;
if (logger.isDebugEnabled()) {
logger.debug("Detect meters from #{} objects", telegram.getCosemObjects().size());
}
- final Entry<Collection<DSMRMeterDescriptor>, Map<CosemObjectType, CosemObject>> detectedMeters = meterDetector
+ final Entry<Collection<DSMRMeterDescriptor>, List<CosemObject>> detectedMeters = meterDetector
.detectMeters(telegram);
verifyUnregisteredCosemObjects(telegram, detectedMeters.getValue());
validateConfiguredMeters(dsmrBridgeHandler.getThing().getThings(),
detectedMeters.getKey().forEach(m -> meterDiscovered(m, dsmrBridgeHandler.getThing().getUID()));
}
- protected void verifyUnregisteredCosemObjects(P1Telegram telegram,
- Map<CosemObjectType, CosemObject> 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<CosemObject> 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());
/**
* 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<CosemObjectType, CosemObject> unidentifiedCosemObjects) {
- unidentifiedCosemObjects
- .forEach((k, v) -> logger.info("Unrecognized cosem object '{}' found in the data: {}", k, v));
+ protected void reportUnrecognizedCosemObjects(List<CosemObject> list) {
+ list.forEach(c -> logger.info("Unrecognized cosem object '{}' found in the data: {}", c.getType(), c));
}
/**
*/
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.",
* @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);
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;
*/
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.
*
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);
if (localMeter == null) {
return;
}
- List<CosemObject> filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects());
+ List<CosemObject> filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects(), channel);
if (filteredValues.isEmpty()) {
if (getThing().getStatus() == ThingStatus.ONLINE) {
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);
}
* @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<CosemObject> filterMeterValues(List<CosemObject> cosemObjects) {
+ public List<CosemObject> filterMeterValues(List<CosemObject> cosemObjects, int channel) {
logger.trace("supported identifiers: {}, searching for objects {}", supportedIdentifiers, cosemObjects);
List<CosemObject> 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;
}
*
* @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;
*/
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,
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.
*/
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;
*
* @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)
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 */
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 },
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,
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,
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,
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,
* @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<CosemObjectType, CosemObject> availableCosemObjects) {
- DSMRMeterDescriptor meterDescriptor = null;
-
- for (CosemObjectType objectType : requiredCosemObjects) {
- if (!availableCosemObjects.containsKey(objectType)) {
- logger.trace("Required objectType {} not found", objectType);
+ public @Nullable DSMRMeterDescriptor findCompatible(List<CosemObject> 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<Entry<@Nullable Integer, AtomicInteger>> 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);
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>DSMR Binding</name>
- <description>This binding integrates Dutch and Luxembourg Smart Meters</description>
+ <description>This binding integrates Dutch, Belgium, Luxembourg and Austrian Smart Meters</description>
</binding:binding>
<description>The serial port where the P1 port of the Smart Meter is connected (e.g. Linux: /dev/ttyUSB0, Windows:
COM1)</description>
</parameter>
- <parameter name="receivedTimeout" type="integer" min="1">
+ <parameter name="receivedTimeout" type="integer" min="1" unit="s">
<default>30</default>
<label>Received Timeout</label>
<description>The time period within results are expected in seconds</description>
<description>The Luxembourgian Smart meter decryption key. Ask for your energy grid operator for your Smart meter P1
key.</description>
</parameter>
- <parameter name="receivedTimeout" type="integer" min="1">
+ <parameter name="receivedTimeout" type="integer" min="1" unit="s">
<default>30</default>
<label>Received Timeout</label>
<description>The time period within results are expected in seconds</description>
</config-description>
<config-description uri="thing-type:dsmr:meterdescriptor">
- <parameter name="refresh" type="integer" min="1">
+ <parameter name="refresh" type="integer" min="1" unit="s">
<default>60</default>
<label>Refresh</label>
<description>The time interval the data is refreshed in seconds</description>
<channel-type id="totalImportedEnergyRegisterPType">
<item-type>Number:Energy</item-type>
<label>Total Imported Energy (P+)</label>
- <description>The Total imported energy register (P+).</description>
+ <description>The total imported energy register (P+).</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="totalExportedEnergyRegisterPType">
<item-type>Number:Energy</item-type>
<label>Total Exported Energy (P+)</label>
- <description>The Total exported energy register (P-).</description>
+ <description>The total exported energy register (P-).</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="totalImportedEnergyRegisterQType">
<item-type>Number:Energy</item-type>
<label>Total Imported Energy (Q+)</label>
- <description>The Total imported energy register (Q+).</description>
+ <description>The total imported energy register (Q+).</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="totalExportedEnergyRegisterQType">
<item-type>Number:Energy</item-type>
<label>Total Exported Energy (Q-)</label>
- <description>The Total exported energy register (Q-).</description>
+ <description>The total exported energy register (Q-).</description>
+ <state pattern="%.1f %unit%" readOnly="true"/>
+ </channel-type>
+ <channel-type id="totalExportedEnergyRegisterRRate1Type" advanced="true">
+ <item-type>Number:Energy</item-type>
+ <label>Total Exported Energy R1</label>
+ <description>The total exported energy register R Rate1.</description>
+ <state pattern="%.1f %unit%" readOnly="true"/>
+ </channel-type>
+ <channel-type id="totalExportedEnergyRegisterRRate2Type" advanced="true">
+ <item-type>Number:Energy</item-type>
+ <label>Total Exported Energy R</label>
+ <description>The total exported energy register R Rate2.</description>
+ <state pattern="%.1f %unit%" readOnly="true"/>
+ </channel-type>
+ <channel-type id="totalImportedEnergyRegisterRRate1Type" advanced="true">
+ <item-type>Number:Energy</item-type>
+ <label>Total Imported Energy R1</label>
+ <description>The total imported energy register R Rate1.</description>
+ <state pattern="%.1f %unit%" readOnly="true"/>
+ </channel-type>
+ <channel-type id="totalImportedEnergyRegisterRRate2Type" advanced="true">
+ <item-type>Number:Energy</item-type>
+ <label>Total Imported Energy R2</label>
+ <description>The total imported energy register R Rate2.</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="tariffIndicatorType" advanced="true">
<channel id="emeter_production_tariff1" typeId="productionTariff1Type"/>
<channel id="emeter_production_tariff2" typeId="productionTariff2Type"/>
<channel id="emeter_tariff_indicator" typeId="tariffIndicatorType"/>
- <channel id="emeter_treshold_kwh" typeId="actualTresholdkWType"/>
+ <channel id="emeter_treshold_kw" typeId="actualTresholdkWType"/>
<channel id="emeter_switch_position" typeId="switchPositionType"/>
<channel id="emeter_actual_delivery" typeId="actualDeliveryType"/>
<channel id="emeter_actual_production" typeId="actualProductionType"/>
<channel id="emeter_production_tariff1" typeId="productionTariff1Type"/>
<channel id="emeter_production_tariff2" typeId="productionTariff2Type"/>
<channel id="emeter_tariff_indicator" typeId="tariffIndicatorType"/>
- <channel id="emeter_treshold_kwh" typeId="actualTresholdkWType"/>
+ <channel id="emeter_treshold_kw" typeId="actualTresholdkWType"/>
<channel id="emeter_switch_position" typeId="switchPositionType"/>
<channel id="emeter_actual_delivery" typeId="actualDeliveryType"/>
<channel id="emeter_actual_production" typeId="actualProductionType"/>
<channel id="emeter_switch_position" typeId="switchPositionType"/>
<channel id="emeter_fuse_threshold_a" typeId="actualFuseThresholdAType"/>
<channel id="emeter_treshold_kw" typeId="actualTresholdkWType"/>
+ <channel id="emeter_instant_power_delivery_l1" typeId="instantPowerDeliveryL1Type"/>
+ <channel id="emeter_instant_power_delivery_l2" typeId="instantPowerDeliveryL2Type"/>
+ <channel id="emeter_instant_power_delivery_l3" typeId="instantPowerDeliveryL3Type"/>
+ <channel id="emeter_instant_power_production_l1" typeId="instantPowerProductionL1Type"/>
+ <channel id="emeter_instant_power_production_l2" typeId="instantPowerProductionL2Type"/>
+ <channel id="emeter_instant_power_production_l3" typeId="instantPowerProductionL3Type"/>
<channel id="emeter_instant_current_l1" typeId="instantCurrentL1Type"/>
<channel id="emeter_instant_current_l2" typeId="instantCurrentL2Type"/>
<channel id="emeter_instant_current_l3" typeId="instantCurrentL3Type"/>
<channel id="emeter_actual_reactive_delivery" typeId="actualReactiveDeliveryType"/>
<channel id="emeter_actual_reactive_production" typeId="actualReactiveProductionType"/>
<channel id="emeter_active_threshold_smax" typeId="activeThresholdSmax"/>
+ <channel id="emeter_threshold_kw" typeId="actualTresholdkWType"/>
<channel id="emeter_switch_position" typeId="switchPositionType"/>
<channel id="emeter_power_failure_log_entries" typeId="powerFailureLogEntriesType"/>
<channel id="emeter_power_failure_log_timestamp0" typeId="powerFailureLogEndType"/>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="dsmr"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <thing-type id="electricity_smarty_v1_0_austria" listed="false">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="smartyBridge"/>
+ </supported-bridge-type-refs>
+
+ <label>Electricity Meter Austria</label>
+ <description>This is an electricity meter that complies to the Austria's Smarty V1.0 specification.</description>
+
+ <channels>
+ <channel id="p1_timestamp" typeId="p1TimestampType"/>
+ <channel id="p1_version_output" typeId="p1VersionType"/>
+ <channel id="emeter_actual_delivery" typeId="actualDeliveryType"/>
+ <channel id="emeter_actual_production" typeId="actualProductionType"/>
+ <channel id="emeter_delivery_tariff0" typeId="deliveryTariff0Type"/>
+ <channel id="emeter_delivery_tariff1" typeId="deliveryTariff1Type"/>
+ <channel id="emeter_delivery_tariff2" typeId="deliveryTariff2Type"/>
+ <channel id="emeter_production_tariff0" typeId="productionTariff0Type"/>
+ <channel id="emeter_production_tariff1" typeId="productionTariff1Type"/>
+ <channel id="emeter_production_tariff2" typeId="productionTariff2Type"/>
+ <channel id="emeter_actual_reactive_delivery" typeId="actualReactiveDeliveryType"/>
+ <channel id="emeter_actual_reactive_production" typeId="actualReactiveProductionType"/>
+ <channel id="emeter_total_imported_energy_register_q" typeId="totalImportedEnergyRegisterQType"/>
+ <channel id="emeter_total_imported_energy_register_r_rate1" typeId="totalImportedEnergyRegisterRRate1Type"/>
+ <channel id="emeter_total_imported_energy_register_r_rate2" typeId="totalImportedEnergyRegisterRRate2Type"/>
+ <channel id="emeter_total_exported_energy_register_q" typeId="totalExportedEnergyRegisterQType"/>
+ <channel id="emeter_total_exported_energy_register_r_rate1" typeId="totalExportedEnergyRegisterRRate1Type"/>
+ <channel id="emeter_total_exported_energy_register_r_rate2" typeId="totalExportedEnergyRegisterRRate2Type"/>
+ </channels>
+ <config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
+ </thing-type>
+</thing:thing-descriptions>
*/
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;
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);
}
}
* @return a P1Telegram object
*/
public static P1Telegram readTelegram(String telegramName, TelegramState expectedTelegramState) {
- AtomicReference<P1Telegram> p1Telegram = new AtomicReference<>();
- byte[] telegram = readRawTelegram(telegramName);
- P1TelegramParser parser = new P1TelegramParser(p1Telegram::set);
+ final AtomicReference<P1Telegram> p1Telegram = new AtomicReference<>();
+ final byte[] telegram = readRawTelegram(telegramName);
+ final P1TelegramParser parser = new P1TelegramParser(p1Telegram::set, true);
parser.setLenientMode(true);
parser.parse(telegram, telegram.length);
{ "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},
@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,
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;
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;
// @formatter:off
public static final List<Object[]> 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
@ParameterizedTest
@MethodSource("data")
public void testDetectMeters(final String telegramName, final Set<DSMRMeterType> expectedMeters) {
- P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK);
- DSMRMeterDetector detector = new DSMRMeterDetector();
- Entry<Collection<DSMRMeterDescriptor>, Map<CosemObjectType, CosemObject>> entry = detector
- .detectMeters(telegram);
- Collection<DSMRMeterDescriptor> detectMeters = entry.getKey();
+ final P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK);
+ final DSMRMeterDetector detector = new DSMRMeterDetector();
+ final Entry<Collection<DSMRMeterDescriptor>, List<CosemObject>> entry = detector.detectMeters(telegram);
+ final Collection<DSMRMeterDescriptor> 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]))));
}
*/
@Test
public void testFilterMeterValues() {
- DSMRMeterDescriptor descriptor = new DSMRMeterDescriptor(DSMRMeterType.DEVICE_V5, 0);
- DSMRMeter meter = new DSMRMeter(descriptor);
+ final List<CosemObject> cosemObjects = TelegramReaderUtil.readTelegram("dsmr_50", TelegramState.OK)
+ .getCosemObjects();
- List<CosemObject> 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<CosemObject> cosemObjects, DSMRMeterType type, int channel, int expected) {
+ final DSMRMeterDescriptor descriptor = new DSMRMeterDescriptor(type, channel);
+ final DSMRMeter meter = new DSMRMeter(descriptor);
+ final List<CosemObject> filterMeterValues = meter.filterMeterValues(cosemObjects, channel);
+
+ assertEquals(expected, filterMeterValues.size(), "Filter should return all required objects");
}
}
--- /dev/null
+/EST5\253740976_A\r
+\r
+1-3:0.2.8(50)\r
+0-0:1.0.0(210902215713S)\r
+1-0:1.8.0(000326641*Wh)\r
+1-0:1.8.1(000230515*Wh)\r
+1-0:1.8.2(000096126*Wh)\r
+1-0:1.7.0(000000621*W)\r
+1-0:2.8.0(000004998*Wh)\r
+1-0:2.8.1(000001350*Wh)\r
+1-0:2.8.2(000003648*Wh)\r
+1-0:2.7.0(000000000*W)\r
+1-0:3.8.0(000005672*varh)\r
+1-0:3.8.1(000001954*varh)\r
+1-0:3.8.2(000003718*varh)\r
+1-0:3.7.0(000000000*var)\r
+1-0:4.8.0(000253220*varh)\r
+1-0:4.8.1(000168985*varh)\r
+1-0:4.8.2(000084235*varh)\r
+1-0:4.7.0(000000510*var)\r
+!6744\r
--- /dev/null
+/FLU5\123456789_A\r
+\r
+0-0:96.1.4(50215)\r
+0-0:96.1.1(A234567890123456789012345678)\r
+0-0:1.0.0(210227144933W)\r
+1-0:1.8.1(001247.747kWh)\r
+1-0:1.8.2(002330.879kWh)\r
+1-0:2.8.1(002159.427kWh)\r
+1-0:2.8.2(000812.563kWh)\r
+0-0:96.14.0(0002)\r
+1-0:1.7.0(00.000kW)\r
+1-0:2.7.0(00.149kW)\r
+1-0:21.7.0(00.013kW)\r
+1-0:41.7.0(00.108kW)\r
+1-0:61.7.0(00.000kW)\r
+1-0:22.7.0(00.000kW)\r
+1-0:42.7.0(00.000kW)\r
+1-0:62.7.0(00.271kW)\r
+1-0:32.7.0(234.5V)\r
+1-0:52.7.0(234.0V)\r
+1-0:72.7.0(235.1V)\r
+1-0:31.7.0(000.19A)\r
+1-0:51.7.0(000.80A)\r
+1-0:71.7.0(001.39A)\r
+0-0:96.3.10(1)\r
+0-0:17.0.0(999.9kW)\r
+1-0:31.4.0(999A)\r
+0-0:96.13.0()\r
+0-1:24.1.0(003)\r
+0-1:96.1.1(1234567890123456789012345678)\r
+0-1:24.4.0(1)\r
+0-1:24.2.3(210227144600W)(00996.617*m3)\r
+!70C0\r