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