[dsmr] Add support for Austrian meters, Fix for channel id detection (#11458)

* Fix fix for channel id detection, Added missing channels to emucs electra

- M-bus channels are dynamic and present in the obis id.
The binding had most channel types fixed because most of the time these channels are the same.
However the device identifier is the same for multiple devices.
But the binding only registered only one and while the channel id was derived from this obis data.
For detected meters this resulted in the channel id to be the same if there are multiple devices.
This change looks at the channel id to assign it to the found device.
This is a bit tricky because the general device has no channel and has channels that have different id's.
So the binding needs to cover that case.

This change also adds some optional channels to the emucs electra meter.

Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl>

* [dsmr] Add support for Austrian meters

Improved the work done in pr #11193

Also-by: Thomas <thomas@knaller.info>
Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl>

* [dsmr] Added Null handling annotations.
This commit is contained in:
Hilbrand Bouwkamp 2021-10-30 18:04:42 +02:00 committed by GitHub
parent 73b4dbe14f
commit 90525c5150
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 655 additions and 465 deletions

View File

@ -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.
* Luxembourgs 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 <name> "<description>" (<Group>) {channel="<Channel identifier>"}
**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

View File

@ -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,

View File

@ -123,16 +123,14 @@ public class CosemObject {
int cosemValueItr = 0;
while (cosemValueMatcher.find()) {
Entry<String, CosemValueDescriptor<?>> valueDescriptorEntry = type.getDescriptor(cosemValueItr);
State cosemValue = valueDescriptorEntry.getValue().getStateValue(cosemValueMatcher.group(2));
final Entry<String, CosemValueDescriptor<?>> valueDescriptorEntry = type.getDescriptor(cosemValueItr);
final 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);
}
if (!cosemValues.containsKey(valueDescriptorEntry.getKey())) {
cosemValues.put(valueDescriptorEntry.getKey(), cosemValue);
} else {
logger.warn("Value for descriptor {} already exists, dropping value {}", valueDescriptorEntry,
cosemValue);
}
cosemValueItr++;
}

View File

@ -42,16 +42,6 @@ public class CosemObjectFactory {
*/
private final Map<OBISIdentifier, List<CosemObjectType>> obisLookupTableMultipleFixed = new HashMap<>();
/**
* Lookup cache for dynamic OBIS Identifiers
*/
private final Map<OBISIdentifier, CosemObjectType> obisLookupTableDynamic = new HashMap<>();
/**
* Lookup cache for wild card Cosem Object types
*/
private final List<CosemObjectType> obisWildcardCosemTypeList = new ArrayList<>();
/**
* Creates a new CosemObjectFactory
*/
@ -64,7 +54,7 @@ public class 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 class 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 class 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;

View File

@ -14,11 +14,11 @@ package org.openhab.binding.dsmr.internal.device.cosem;
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 @@ import org.openhab.core.library.unit.Units;
* @author M. Volaart - Initial contribution
* @author Hilbrand Bouwkamp - Cosem subclasses made into factory classes and introduced quantity type
*/
@NonNullByDefault
public enum CosemObjectType {
UNKNOWN(new OBISIdentifier(-1, DSMRMeterConstants.UNKNOWN_CHANNEL, -1, -1, -1, null), CosemString.INSTANCE),
UNKNOWN(new OBISIdentifier(-1, -1, -1, -1), CosemString.INSTANCE),
/* General messages */
P1_VERSION_OUTPUT(new OBISIdentifier(1, 3, 0, 2, 8, null), CosemString.INSTANCE),
P1_EMUCS_VERSION_OUTPUT(new OBISIdentifier(0, 0, 96, 1, 4, null), CosemString.INSTANCE),
P1_TIMESTAMP(new OBISIdentifier(0, 0, 1, 0, 0, null), new CosemDate("")),
P1_TEXT_CODE(new OBISIdentifier(0, 0, 96, 13, 1, null), CosemHexString.INSTANCE),
P1_TEXT_STRING(new OBISIdentifier(0, 0, 96, 13, 0, null), CosemHexString.INSTANCE),
P1_TEXT_STRING_LONG(new OBISIdentifier(0, 0, 96, 13, null, null), CosemHexString.INSTANCE),
P1_VERSION_OUTPUT(new OBISIdentifier(1, 0, 2, 8), CosemString.INSTANCE),
P1_EMUCS_VERSION_OUTPUT(new OBISIdentifier(0, 96, 1, 4), CosemString.INSTANCE),
P1_TIMESTAMP(new OBISIdentifier(0, 1, 0, 0), new CosemDate("")),
P1_TEXT_CODE(new OBISIdentifier(0, 96, 13, 1), CosemHexString.INSTANCE),
P1_TEXT_STRING(new OBISIdentifier(0, 96, 13, 0), CosemHexString.INSTANCE),
P1_TEXT_STRING_LONG(new OBISIdentifier(0, 96, 13, null), CosemHexString.INSTANCE),
/* Generic Meter Cosem Object types */
METER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, null, 96, 1, 0, null), CosemHexString.INSTANCE),
METER_DEVICE_TYPE(new OBISIdentifier(0, null, 24, 1, 0, null), CosemString.INSTANCE),
METER_VALVE_SWITCH_POSITION(new OBISIdentifier(0, null, 24, 4, 0, null), CosemDecimal.INSTANCE),
METER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, 96, 1, 0), CosemHexString.INSTANCE),
METER_DEVICE_TYPE(new OBISIdentifier(0, 24, 1, 0), CosemString.INSTANCE),
METER_VALVE_SWITCH_POSITION(new OBISIdentifier(0, 24, 4, 0), CosemDecimal.INSTANCE),
/* Electricity Meter */
EMETER_EQUIPMENT_IDENTIFIER_V2_X(new OBISIdentifier(0, 0, 42, 0, 0, null), CosemString.INSTANCE),
EMETER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, null, 96, 1, 1, null), CosemHexString.INSTANCE),
EMETER_VALUE(new OBISIdentifier(0, null, 24, 2, 1, null), CosemDate.INSTANCE, CosemQuantity.KILO_WATT_HOUR),
EMETER_DELIVERY_TARIFF0(new OBISIdentifier(1, null, 1, 8, 0, null), CosemQuantity.KILO_WATT_HOUR),
EMETER_DELIVERY_TARIFF1(new OBISIdentifier(1, null, 1, 8, 1, null), CosemQuantity.KILO_WATT_HOUR),
EMETER_DELIVERY_TARIFF2(new OBISIdentifier(1, null, 1, 8, 2, null), CosemQuantity.KILO_WATT_HOUR),
EMETER_DELIVERY_TARIFF0_ANTIFRAUD(new OBISIdentifier(1, null, 15, 8, 0, null), CosemQuantity.KILO_WATT_HOUR),
EMETER_DELIVERY_TARIFF1_ANTIFRAUD(new OBISIdentifier(1, null, 15, 8, 1, null), CosemQuantity.KILO_WATT_HOUR),
EMETER_DELIVERY_TARIFF2_ANTIFRAUD(new OBISIdentifier(1, null, 15, 8, 2, null), CosemQuantity.KILO_WATT_HOUR),
EMETER_PRODUCTION_TARIFF0(new OBISIdentifier(1, null, 2, 8, 0, null), CosemQuantity.KILO_WATT_HOUR),
EMETER_PRODUCTION_TARIFF1(new OBISIdentifier(1, null, 2, 8, 1, null), CosemQuantity.KILO_WATT_HOUR),
EMETER_PRODUCTION_TARIFF2(new OBISIdentifier(1, null, 2, 8, 2, null), CosemQuantity.KILO_WATT_HOUR),
EMETER_TARIFF_INDICATOR(new OBISIdentifier(0, null, 96, 14, 0, null), CosemString.INSTANCE),
EMETER_ACTIVE_IMPORT_POWER(new OBISIdentifier(1, null, 15, 7, 0, null), CosemQuantity.WATT),
EMETER_ACTUAL_DELIVERY(new OBISIdentifier(1, 0, 1, 7, 0, null), CosemQuantity.KILO_WATT),
EMETER_ACTUAL_PRODUCTION(new OBISIdentifier(1, 0, 2, 7, 0, null), CosemQuantity.KILO_WATT),
EMETER_TRESHOLD_A_V2_1(new OBISIdentifier(1, 0, 17, 0, 0, null), CosemQuantity.AMPERE),
EMETER_TRESHOLD_A(new OBISIdentifier(0, 0, 17, 0, 0, null, true), CosemQuantity.AMPERE),
EMETER_FUSE_THRESHOLD_A(new OBISIdentifier(1, 0, 31, 4, 0, null), CosemQuantity.AMPERE),
EMETER_TRESHOLD_KWH(new OBISIdentifier(0, 0, 17, 0, 0, null, true), CosemQuantity.KILO_WATT),
EMETER_SWITCH_POSITION_V2_1(new OBISIdentifier(1, 0, 96, 3, 10, null), CosemDecimal.INSTANCE),
EMETER_SWITCH_POSITION(new OBISIdentifier(0, 0, 96, 3, 10, null), CosemDecimal.INSTANCE),
EMETER_POWER_FAILURES(new OBISIdentifier(0, 0, 96, 7, 21, null), CosemDecimal.INSTANCE),
EMETER_LONG_POWER_FAILURES(new OBISIdentifier(0, 0, 96, 7, 9, null), CosemDecimal.INSTANCE),
EMETER_POWER_FAILURE_LOG(new OBISIdentifier(1, 0, 99, 97, 0, null), 2, new CosemDecimal("entries"),
EMETER_EQUIPMENT_IDENTIFIER_V2_X(new OBISIdentifier(0, 42, 0, 0), CosemString.INSTANCE),
EMETER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, 96, 1, 1), CosemHexString.INSTANCE),
EMETER_VALUE(new OBISIdentifier(0, 24, 2, 1, true), CosemDate.INSTANCE, CosemQuantity.KILO_WATT_HOUR),
EMETER_DELIVERY_TARIFF0(new OBISIdentifier(1, 1, 8, 0), CosemQuantity.KILO_WATT_HOUR),
EMETER_DELIVERY_TARIFF1(new OBISIdentifier(1, 1, 8, 1), CosemQuantity.KILO_WATT_HOUR),
EMETER_DELIVERY_TARIFF2(new OBISIdentifier(1, 1, 8, 2), CosemQuantity.KILO_WATT_HOUR),
EMETER_DELIVERY_TARIFF0_ANTIFRAUD(new OBISIdentifier(1, 15, 8, 0), CosemQuantity.KILO_WATT_HOUR),
EMETER_DELIVERY_TARIFF1_ANTIFRAUD(new OBISIdentifier(1, 15, 8, 1), CosemQuantity.KILO_WATT_HOUR),
EMETER_DELIVERY_TARIFF2_ANTIFRAUD(new OBISIdentifier(1, 15, 8, 2), CosemQuantity.KILO_WATT_HOUR),
EMETER_PRODUCTION_TARIFF0(new OBISIdentifier(1, 2, 8, 0), CosemQuantity.KILO_WATT_HOUR),
EMETER_PRODUCTION_TARIFF1(new OBISIdentifier(1, 2, 8, 1), CosemQuantity.KILO_WATT_HOUR),
EMETER_PRODUCTION_TARIFF2(new OBISIdentifier(1, 2, 8, 2), CosemQuantity.KILO_WATT_HOUR),
EMETER_TARIFF_INDICATOR(new OBISIdentifier(0, 96, 14, 0), CosemString.INSTANCE),
EMETER_ACTIVE_IMPORT_POWER(new OBISIdentifier(1, 15, 7, 0), CosemQuantity.WATT),
EMETER_ACTUAL_DELIVERY(new OBISIdentifier(1, 1, 7, 0), CosemQuantity.KILO_WATT),
EMETER_ACTUAL_PRODUCTION(new OBISIdentifier(1, 2, 7, 0), CosemQuantity.KILO_WATT),
EMETER_TRESHOLD_A_V2_1(new OBISIdentifier(1, 17, 0, 0), CosemQuantity.AMPERE),
EMETER_TRESHOLD_A(new OBISIdentifier(0, 17, 0, 0, true), CosemQuantity.AMPERE),
EMETER_FUSE_THRESHOLD_A(new OBISIdentifier(1, 31, 4, 0), CosemQuantity.AMPERE),
EMETER_TRESHOLD_KW(new OBISIdentifier(0, 17, 0, 0, true), CosemQuantity.KILO_WATT),
EMETER_SWITCH_POSITION_V2_1(new OBISIdentifier(1, 96, 3, 10), CosemDecimal.INSTANCE),
EMETER_SWITCH_POSITION(new OBISIdentifier(0, 96, 3, 10), CosemDecimal.INSTANCE),
EMETER_POWER_FAILURES(new OBISIdentifier(0, 96, 7, 21), CosemDecimal.INSTANCE),
EMETER_LONG_POWER_FAILURES(new OBISIdentifier(0, 96, 7, 9), CosemDecimal.INSTANCE),
EMETER_POWER_FAILURE_LOG(new OBISIdentifier(1, 99, 97, 0), 2, new CosemDecimal("entries"),
new CosemString("obisId"),
/* Next 2 descriptors are repeating */
CosemDate.INSTANCE, new CosemQuantity<>(Units.SECOND, "duration")),
EMETER_VOLTAGE_SAGS_L1(new OBISIdentifier(1, 0, 32, 32, 0, null), CosemDecimal.INSTANCE),
EMETER_VOLTAGE_SAGS_L2(new OBISIdentifier(1, 0, 52, 32, 0, null), CosemDecimal.INSTANCE),
EMETER_VOLTAGE_SAGS_L3(new OBISIdentifier(1, 0, 72, 32, 0, null), CosemDecimal.INSTANCE),
EMETER_VOLTAGE_SWELLS_L1(new OBISIdentifier(1, 0, 32, 36, 0, null), CosemDecimal.INSTANCE),
EMETER_VOLTAGE_SWELLS_L2(new OBISIdentifier(1, 0, 52, 36, 0, null), CosemDecimal.INSTANCE),
EMETER_VOLTAGE_SWELLS_L3(new OBISIdentifier(1, 0, 72, 36, 0, null), CosemDecimal.INSTANCE),
EMETER_INSTANT_CURRENT_L1(new OBISIdentifier(1, 0, 31, 7, 0, null), CosemQuantity.AMPERE),
EMETER_INSTANT_CURRENT_L2(new OBISIdentifier(1, 0, 51, 7, 0, null), CosemQuantity.AMPERE),
EMETER_INSTANT_CURRENT_L3(new OBISIdentifier(1, 0, 71, 7, 0, null), CosemQuantity.AMPERE),
EMETER_INSTANT_POWER_DELIVERY_L1(new OBISIdentifier(1, 0, 21, 7, 0, null), CosemQuantity.KILO_WATT),
EMETER_INSTANT_POWER_DELIVERY_L2(new OBISIdentifier(1, 0, 41, 7, 0, null), CosemQuantity.KILO_WATT),
EMETER_INSTANT_POWER_DELIVERY_L3(new OBISIdentifier(1, 0, 61, 7, 0, null), CosemQuantity.KILO_WATT),
EMETER_INSTANT_POWER_PRODUCTION_L1(new OBISIdentifier(1, 0, 22, 7, 0, null), CosemQuantity.KILO_WATT),
EMETER_INSTANT_POWER_PRODUCTION_L2(new OBISIdentifier(1, 0, 42, 7, 0, null), CosemQuantity.KILO_WATT),
EMETER_INSTANT_POWER_PRODUCTION_L3(new OBISIdentifier(1, 0, 62, 7, 0, null), CosemQuantity.KILO_WATT),
EMETER_INSTANT_VOLTAGE_L1(new OBISIdentifier(1, 0, 32, 7, 0, null), CosemQuantity.VOLT),
EMETER_INSTANT_VOLTAGE_L2(new OBISIdentifier(1, 0, 52, 7, 0, null), CosemQuantity.VOLT),
EMETER_INSTANT_VOLTAGE_L3(new OBISIdentifier(1, 0, 72, 7, 0, null), CosemQuantity.VOLT),
EMETER_VOLTAGE_SAGS_L1(new OBISIdentifier(1, 32, 32, 0), CosemDecimal.INSTANCE),
EMETER_VOLTAGE_SAGS_L2(new OBISIdentifier(1, 52, 32, 0), CosemDecimal.INSTANCE),
EMETER_VOLTAGE_SAGS_L3(new OBISIdentifier(1, 72, 32, 0), CosemDecimal.INSTANCE),
EMETER_VOLTAGE_SWELLS_L1(new OBISIdentifier(1, 32, 36, 0), CosemDecimal.INSTANCE),
EMETER_VOLTAGE_SWELLS_L2(new OBISIdentifier(1, 52, 36, 0), CosemDecimal.INSTANCE),
EMETER_VOLTAGE_SWELLS_L3(new OBISIdentifier(1, 72, 36, 0), CosemDecimal.INSTANCE),
EMETER_INSTANT_CURRENT_L1(new OBISIdentifier(1, 31, 7, 0), CosemQuantity.AMPERE),
EMETER_INSTANT_CURRENT_L2(new OBISIdentifier(1, 51, 7, 0), CosemQuantity.AMPERE),
EMETER_INSTANT_CURRENT_L3(new OBISIdentifier(1, 71, 7, 0), CosemQuantity.AMPERE),
EMETER_INSTANT_POWER_DELIVERY_L1(new OBISIdentifier(1, 21, 7, 0), CosemQuantity.KILO_WATT),
EMETER_INSTANT_POWER_DELIVERY_L2(new OBISIdentifier(1, 41, 7, 0), CosemQuantity.KILO_WATT),
EMETER_INSTANT_POWER_DELIVERY_L3(new OBISIdentifier(1, 61, 7, 0), CosemQuantity.KILO_WATT),
EMETER_INSTANT_POWER_PRODUCTION_L1(new OBISIdentifier(1, 22, 7, 0), CosemQuantity.KILO_WATT),
EMETER_INSTANT_POWER_PRODUCTION_L2(new OBISIdentifier(1, 42, 7, 0), CosemQuantity.KILO_WATT),
EMETER_INSTANT_POWER_PRODUCTION_L3(new OBISIdentifier(1, 62, 7, 0), CosemQuantity.KILO_WATT),
EMETER_INSTANT_VOLTAGE_L1(new OBISIdentifier(1, 32, 7, 0), CosemQuantity.VOLT),
EMETER_INSTANT_VOLTAGE_L2(new OBISIdentifier(1, 52, 7, 0), CosemQuantity.VOLT),
EMETER_INSTANT_VOLTAGE_L3(new OBISIdentifier(1, 72, 7, 0), CosemQuantity.VOLT),
/* Gas Meter */
GMETER_EQUIPMENT_IDENTIFIER_V2(new OBISIdentifier(7, 0, 0, 0, 0, null), CosemString.INSTANCE),
GMETER_24H_DELIVERY_V2(new OBISIdentifier(7, 0, 23, 1, 0, null), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE),
GMETER_24H_DELIVERY_COMPENSATED_V2(new OBISIdentifier(7, 0, 23, 2, 0, null), CosemQuantity.CUBIC_METRE,
CosemDate.INSTANCE),
GMETER_LAST_VALUE(new OBISIdentifier(0, null, 24, 2, 3, null), CosemDate.INSTANCE, CosemQuantity.CUBIC_METRE),
GMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null), CosemDate.INSTANCE, // Time stamp off the reading
GMETER_EQUIPMENT_IDENTIFIER_V2(new OBISIdentifier(7, 0, 0, 0), CosemString.INSTANCE),
GMETER_24H_DELIVERY_V2(new OBISIdentifier(7, 23, 1, 0), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE),
GMETER_24H_DELIVERY_COMPENSATED_V2(new OBISIdentifier(7, 23, 2, 0), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE),
GMETER_LAST_VALUE(new OBISIdentifier(0, 24, 2, 3), CosemDate.INSTANCE, CosemQuantity.CUBIC_METRE),
GMETER_VALUE_V3(new OBISIdentifier(0, 24, 3, 0, true), CosemDate.INSTANCE, // Time stamp off the reading
new CosemString("val1"), // Specification is not clear what this value is
new CosemDecimal("val2"), // Specification is not clear what this value is
new CosemDecimal("val3"), // Specification is not clear what this value is
new CosemString("obisId"), // String containing a OBIS Identifier
new CosemString("unit"), // String containing the type (m3)
CosemDecimal.INSTANCE),
GMETER_VALVE_POSITION_V2_1(new OBISIdentifier(7, 0, 96, 3, 10, null), CosemDecimal.INSTANCE),
GMETER_VALVE_POSITION_V2_2(new OBISIdentifier(7, 0, 24, 4, 0, null), CosemDecimal.INSTANCE),
GMETER_VALVE_POSITION_V2_1(new OBISIdentifier(7, 96, 3, 10), CosemDecimal.INSTANCE),
GMETER_VALVE_POSITION_V2_2(new OBISIdentifier(7, 24, 4, 0), CosemDecimal.INSTANCE),
/* Heating Meter */
HMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(5, 0, 0, 0, 0, null), CosemString.INSTANCE),
HMETER_VALUE_V2(new OBISIdentifier(5, 0, 1, 0, 0, null), CosemQuantity.GIGA_JOULE, CosemDate.INSTANCE),
HMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(5, 0, 0, 0), CosemString.INSTANCE),
HMETER_VALUE_V2(new OBISIdentifier(5, 1, 0, 0), CosemQuantity.GIGA_JOULE, CosemDate.INSTANCE),
/* Cooling Meter */
CMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(6, 0, 0, 0, 0, null), CosemString.INSTANCE),
CMETER_VALUE_V2(new OBISIdentifier(6, 0, 1, 0, 0, null), CosemQuantity.GIGA_JOULE, CosemDate.INSTANCE),
CMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(6, 0, 0, 0), CosemString.INSTANCE),
CMETER_VALUE_V2(new OBISIdentifier(6, 1, 0, 0), CosemQuantity.GIGA_JOULE, CosemDate.INSTANCE),
/* Water Meter */
WMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(8, 0, 0, 0, 0, null), CosemString.INSTANCE),
WMETER_VALUE_V2(new OBISIdentifier(8, 0, 1, 0, 0, null), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE),
WMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null), CosemQuantity.CUBIC_METRE),
WMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(8, 0, 0, 0), CosemString.INSTANCE),
WMETER_VALUE_V2(new OBISIdentifier(8, 1, 0, 0), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE),
WMETER_VALUE_V3(new OBISIdentifier(0, 24, 3, 0, true), CosemQuantity.CUBIC_METRE),
/* M3 Meter (Gas, Water) */
M3METER_VALUE(new OBISIdentifier(0, null, 24, 2, 1, null), CosemDate.INSTANCE, CosemQuantity.CUBIC_METRE),
M3METER_VALUE(new OBISIdentifier(0, 24, 2, 1, true), CosemDate.INSTANCE, CosemQuantity.CUBIC_METRE),
/* GJ Meter (Heating, Cooling) */
GJMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null), CosemQuantity.GIGA_JOULE),
GJMETER_VALUE_V4(new OBISIdentifier(0, null, 24, 2, 1, null), CosemDate.INSTANCE, CosemQuantity.GIGA_JOULE),
GJMETER_VALUE_V3(new OBISIdentifier(0, 24, 3, 0, true), CosemQuantity.GIGA_JOULE),
GJMETER_VALUE_V4(new OBISIdentifier(0, 24, 2, 1, true), CosemDate.INSTANCE, CosemQuantity.GIGA_JOULE),
/* Generic Meter (DSMR v3 only) */
GENMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null), CosemDecimal.INSTANCE),
GENMETER_VALUE_V3(new OBISIdentifier(0, 24, 3, 0, true), CosemDecimal.INSTANCE),
/* Additional Luxembourgish Smarty Electricity */
EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_Q(new OBISIdentifier(1, null, 3, 8, 0, null), CosemQuantity.KILO_VAR_HOUR),
EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_Q(new OBISIdentifier(1, null, 4, 8, 0, null), CosemQuantity.KILO_VAR_HOUR),
EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_Q(new OBISIdentifier(1, 3, 8, 0), CosemQuantity.KILO_VAR_HOUR),
EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_R_RATE1(new OBISIdentifier(1, 3, 8, 1), CosemQuantity.KILO_VAR_HOUR),
EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_R_RATE2(new OBISIdentifier(1, 3, 8, 2), CosemQuantity.KILO_VAR_HOUR),
EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_Q(new OBISIdentifier(1, 4, 8, 0), CosemQuantity.KILO_VAR_HOUR),
EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_R_RATE1(new OBISIdentifier(1, 4, 8, 1), CosemQuantity.KILO_VAR_HOUR),
EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_R_RATE2(new OBISIdentifier(1, 4, 8, 2), CosemQuantity.KILO_VAR_HOUR),
// The actual reactive's and threshold have no unit in the data and therefore are not quantity types.
EMETER_ACTUAL_REACTIVE_DELIVERY(new OBISIdentifier(1, 0, 3, 7, 0, null), CosemDecimal.INSTANCE_WITH_UNITS),
EMETER_ACTUAL_REACTIVE_PRODUCTION(new OBISIdentifier(1, 0, 4, 7, 0, null), CosemDecimal.INSTANCE_WITH_UNITS),
EMETER_ACTIVE_THRESHOLD_SMAX(new OBISIdentifier(0, 0, 17, 0, 0, null, true), CosemDecimal.INSTANCE_WITH_UNITS),
EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L1(new OBISIdentifier(1, 0, 23, 7, 0, null), CosemQuantity.KILO_VAR),
EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L2(new OBISIdentifier(1, 0, 43, 7, 0, null), CosemQuantity.KILO_VAR),
EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L3(new OBISIdentifier(1, 0, 63, 7, 0, null), CosemQuantity.KILO_VAR),
EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L1(new OBISIdentifier(1, 0, 24, 7, 0, null), CosemQuantity.KILO_VAR),
EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L2(new OBISIdentifier(1, 0, 44, 7, 0, null), CosemQuantity.KILO_VAR),
EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L3(new OBISIdentifier(1, 0, 64, 7, 0, null), CosemQuantity.KILO_VAR);
EMETER_ACTUAL_REACTIVE_DELIVERY(new OBISIdentifier(1, 3, 7, 0), CosemDecimal.INSTANCE_WITH_UNITS),
EMETER_ACTUAL_REACTIVE_PRODUCTION(new OBISIdentifier(1, 4, 7, 0), CosemDecimal.INSTANCE_WITH_UNITS),
EMETER_ACTIVE_THRESHOLD_SMAX(new OBISIdentifier(0, 17, 0, 0, true), CosemDecimal.INSTANCE_WITH_UNITS),
EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L1(new OBISIdentifier(1, 23, 7, 0), CosemQuantity.KILO_VAR),
EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L2(new OBISIdentifier(1, 43, 7, 0), CosemQuantity.KILO_VAR),
EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L3(new OBISIdentifier(1, 63, 7, 0), CosemQuantity.KILO_VAR),
EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L1(new OBISIdentifier(1, 24, 7, 0), CosemQuantity.KILO_VAR),
EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L2(new OBISIdentifier(1, 44, 7, 0), CosemQuantity.KILO_VAR),
EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L3(new OBISIdentifier(1, 64, 7, 0), CosemQuantity.KILO_VAR);
/** OBIS reduced identifier */
public final OBISIdentifier obisId;
@ -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<CosemValueDescriptor<?>> allDescriptors = Arrays.asList(descriptors);
final List<CosemValueDescriptor<?>> 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<String, CosemValueDescriptor<?>> getDescriptor(int idx) {
public @Nullable Entry<String, CosemValueDescriptor<?>> getDescriptor(int idx) {
if (idx >= descriptors.size() && !repeatingDescriptors.isEmpty()) {
/* We have a repeating list, find the correct repeating descriptor */
int repeatingIdx = (idx - descriptors.size()) % repeatingDescriptors.size();
final int repeatingIdx = (idx - descriptors.size()) % repeatingDescriptors.size();
CosemValueDescriptor<?> descriptor = repeatingDescriptors.get(repeatingIdx);
final CosemValueDescriptor<?> descriptor = repeatingDescriptors.get(repeatingIdx);
/* The repeating descriptor must have a specific channel */
int repeatCount = (idx - descriptors.size()) / repeatingDescriptors.size();
final int repeatCount = (idx - descriptors.size()) / repeatingDescriptors.size();
return new SimpleEntry<>(descriptor.getChannelId() + repeatCount, descriptor);
} else if (idx < descriptors.size()) {
CosemValueDescriptor<?> descriptor = descriptors.get(idx);
final CosemValueDescriptor<?> descriptor = descriptors.get(idx);
return new SimpleEntry<>(descriptor.getChannelId(), descriptor);
} else {

View File

@ -50,6 +50,7 @@ class CosemQuantity<Q extends @Nullable Quantity<Q>> extends CosemValueDescripto
public static final CosemQuantity<Power> WATT = new CosemQuantity<>(Units.WATT);
public static final CosemQuantity<Power> KILO_VAR = new CosemQuantity<>(Units.KILOVAR);
public static final CosemQuantity<Energy> KILO_VAR_HOUR = new CosemQuantity<>(Units.KILOVAR_HOUR);
public static final CosemQuantity<Power> KILO_VA = new CosemQuantity<>(MetricPrefix.KILO(Units.VOLT_AMPERE));
/**
* Pattern to convert a cosem value to a value that can be parsed by {@link QuantityType}.
@ -100,13 +101,14 @@ class CosemQuantity<Q extends @Nullable Quantity<Q>> extends CosemValueDescripto
@Override
protected QuantityType<Q> getStateValue(String cosemValue) throws ParseException {
try {
QuantityType<Q> qt = new QuantityType<>(prepare(cosemValue));
final QuantityType<Q> it = new QuantityType<>(prepare(cosemValue));
final @Nullable QuantityType<Q> qt = it.toUnit(unit);
if (!unit.equals(qt.getUnit())) {
if (qt == null) {
throw new ParseException("Failed to parse value '" + cosemValue + "' as unit " + unit, 0);
}
return qt;
} catch (IllegalArgumentException nfe) {
} catch (final IllegalArgumentException nfe) {
throw new ParseException("Failed to parse value '" + cosemValue + "' as unit " + unit, 0);
}
}
@ -123,7 +125,7 @@ class CosemQuantity<Q extends @Nullable Quantity<Q>> extends CosemValueDescripto
* 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", ""));
final Matcher matcher = COSEM_VALUE_WITH_UNIT_PATTERN.matcher(cosemValue.replace("m3", ""));
if (!matcher.find()) {
return cosemValue;
}
@ -131,7 +133,7 @@ class CosemQuantity<Q extends @Nullable Quantity<Q>> extends CosemValueDescripto
try {
Integer.parseInt(matcher.group(2));
return cosemValue;
} catch (NumberFormatException e) {
} catch (final NumberFormatException e) {
return matcher.group(1) + ' ' + matcher.group(2);
}
}

View File

@ -24,12 +24,12 @@ import org.eclipse.jdt.annotation.Nullable;
* 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 class OBISIdentifier {
* @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 class OBISIdentifier {
}
/**
* @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 class OBISIdentifier {
@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 class OBISIdentifier {
* @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 class OBISIdentifier {
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 class OBISIdentifier {
*
* @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 class OBISIdentifier {
@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);
}
}

View File

@ -128,13 +128,23 @@ public class P1TelegramParser implements TelegramParser {
*/
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 class P1TelegramParser implements TelegramParser {
@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 class P1TelegramParser implements TelegramParser {
}
}
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 class P1TelegramParser implements TelegramParser {
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 class P1TelegramParser implements TelegramParser {
logger.trace("State after parsing: {}", state);
}
private TelegramState checkCRC(TelegramState currentState) {
final TelegramState telegramState;
if (Pattern.matches(CRC_PATTERN, crcValue)) {
final int crcP1Telegram = Integer.parseInt(crcValue.toString(), 16);
final int calculatedCRC = crc.getCurrentCRCCode();
if (logger.isDebugEnabled()) {
logger.trace("received CRC value: {}, calculated CRC value: 0x{}", crcValue,
String.format("%04X", calculatedCRC));
}
if (crcP1Telegram != calculatedCRC) {
if (test) {
throw new IllegalArgumentException(
String.format("Invalid CRC. Read: %s, expected: %04X", crcValue, calculatedCRC));
}
logger.trace("CRC value does not match, p1 Telegram failed");
telegramState = TelegramState.CRC_ERROR;
} else {
telegramState = currentState;
}
} else {
telegramState = TelegramState.CRC_ERROR;
}
return telegramState;
}
private P1Telegram constructTelegram() {
final List<CosemObject> cosemObjectsCopy = new ArrayList<>(cosemObjects);
@ -375,11 +398,11 @@ public class P1TelegramParser implements TelegramParser {
* 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) {

View File

@ -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<Collection<DSMRMeterDescriptor>, Map<CosemObjectType, CosemObject>> detectMeters(P1Telegram telegram) {
public Entry<Collection<DSMRMeterDescriptor>, List<CosemObject>> detectMeters(P1Telegram telegram) {
final Map<DSMRMeterKind, DSMRMeterDescriptor> detectedMeters = new HashMap<>();
final Map<CosemObjectType, CosemObject> availableCosemObjects = new HashMap<>();
final Map<CosemObjectType, CosemObject> undetectedCosemObjects = new HashMap<>();
// Fill hashmap for fast comparing the set of received Cosem objects to the required set of Cosem Objects
telegram.getCosemObjects().forEach(msg -> availableCosemObjects.put(msg.getType(), msg));
undetectedCosemObjects.putAll(availableCosemObjects);
final List<CosemObject> availableCosemObjects = List.copyOf(telegram.getCosemObjects());
final List<CosemObject> undetectedCosemObjects = new ArrayList<>(telegram.getCosemObjects());
// Find compatible meters
for (DSMRMeterType meterType : DSMRMeterType.values()) {
logger.trace("Trying if meter type {} is compatible", meterType);
final DSMRMeterDescriptor meterDescriptor = meterType.isCompatible(availableCosemObjects);
final DSMRMeterDescriptor meterDescriptor = meterType.findCompatible(availableCosemObjects);
if (meterDescriptor == null) {
logger.trace("Meter type {} is not compatible", meterType);
@ -74,7 +73,9 @@ class DSMRMeterDetector {
logger.debug("New compatible meter: {}", meterDescriptor);
detectedMeters.put(meterType.meterKind, meterDescriptor);
for (CosemObjectType cot : meterDescriptor.getMeterType().supportedCosemObjects) {
undetectedCosemObjects.remove(cot);
List<CosemObject> collect = undetectedCosemObjects.stream().filter(u -> cot == u.getType())
.collect(Collectors.toList());
collect.forEach(undetectedCosemObjects::remove);
}
}
}

View File

@ -14,7 +14,6 @@ package org.openhab.binding.dsmr.internal.discovery;
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 class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
if (logger.isDebugEnabled()) {
logger.debug("Detect meters from #{} objects", telegram.getCosemObjects().size());
}
final Entry<Collection<DSMRMeterDescriptor>, Map<CosemObjectType, CosemObject>> detectedMeters = meterDetector
final Entry<Collection<DSMRMeterDescriptor>, List<CosemObject>> detectedMeters = meterDetector
.detectMeters(telegram);
verifyUnregisteredCosemObjects(telegram, detectedMeters.getValue());
validateConfiguredMeters(dsmrBridgeHandler.getThing().getThings(),
@ -109,17 +108,16 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
detectedMeters.getKey().forEach(m -> meterDiscovered(m, dsmrBridgeHandler.getThing().getUID()));
}
protected void verifyUnregisteredCosemObjects(P1Telegram telegram,
Map<CosemObjectType, CosemObject> undetectedCosemObjects) {
if (!undetectedCosemObjects.isEmpty()) {
if (undetectedCosemObjects.entrySet().stream()
.anyMatch(e -> e.getKey() == CosemObjectType.METER_EQUIPMENT_IDENTIFIER
&& e.getValue().getCosemValues().entrySet().stream().anyMatch(
protected void verifyUnregisteredCosemObjects(P1Telegram telegram, List<CosemObject> list) {
if (!list.isEmpty()) {
if (list.stream()
.anyMatch(e -> e.getType() == CosemObjectType.METER_EQUIPMENT_IDENTIFIER
&& e.getCosemValues().entrySet().stream().anyMatch(
cv -> cv.getValue() instanceof StringType && cv.getValue().toString().isEmpty()))) {
// Unregistered meter detected. log to the user.
reportUnregisteredMeters();
} else {
reportUnrecognizedCosemObjects(undetectedCosemObjects);
reportUnrecognizedCosemObjects(list);
logger.info("There are unrecognized cosem values in the data received from the meter,"
+ " which means some meters might not be detected. Please report your raw data as reference: {}",
telegram.getRawTelegram());
@ -138,11 +136,10 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
/**
* Called when Unrecognized cosem objects where found. This can be a bug or a new meter not yet supported.
*
* @param unidentifiedCosemObjects Map with the unrecognized.
* @param list Map with the unrecognized.
*/
protected void reportUnrecognizedCosemObjects(Map<CosemObjectType, CosemObject> unidentifiedCosemObjects) {
unidentifiedCosemObjects
.forEach((k, v) -> logger.info("Unrecognized cosem object '{}' found in the data: {}", k, v));
protected void reportUnrecognizedCosemObjects(List<CosemObject> list) {
list.forEach(c -> logger.info("Unrecognized cosem object '{}' found in the data: {}", c.getType(), c));
}
/**

View File

@ -220,7 +220,7 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
*/
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 class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
* @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);

View File

@ -25,6 +25,7 @@ import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
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 class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
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 class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
if (localMeter == null) {
return;
}
List<CosemObject> filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects());
List<CosemObject> filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects(), channel);
if (filteredValues.isEmpty()) {
if (getThing().getStatus() == ThingStatus.ONLINE) {

View File

@ -56,9 +56,9 @@ public class DSMRMeter {
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 class DSMRMeter {
* @param cosemObjects list of CosemObject that must be processed and where the objects of this meter are removed
* @return List of CosemObject that this meter can process
*/
public List<CosemObject> filterMeterValues(List<CosemObject> cosemObjects) {
public List<CosemObject> filterMeterValues(List<CosemObject> cosemObjects, int channel) {
logger.trace("supported identifiers: {}, searching for objects {}", supportedIdentifiers, cosemObjects);
List<CosemObject> filteredValues = cosemObjects.stream()
.filter(cosemObject -> supportedIdentifiers
.contains(cosemObject.getObisIdentifier().getReducedOBISIdentifier()))
.filter(cosemObject -> (DSMRMeterConstants.UNKNOWN_CHANNEL == channel
|| cosemObject.getObisIdentifier().getChannel() == channel)
&& supportedIdentifiers.contains(cosemObject.getObisIdentifier().getReducedOBISIdentifier()))
.collect(Collectors.toList());
return filteredValues;
}

View File

@ -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;

View File

@ -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.
*/

View File

@ -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 @@ import org.slf4j.LoggerFactory;
*
* @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<CosemObjectType, CosemObject> availableCosemObjects) {
DSMRMeterDescriptor meterDescriptor = null;
public @Nullable DSMRMeterDescriptor findCompatible(List<CosemObject> availableCosemObjects) {
final Map<@Nullable Integer, AtomicInteger> channelCounter = new HashMap<>(3);
for (CosemObjectType objectType : requiredCosemObjects) {
if (!availableCosemObjects.containsKey(objectType)) {
logger.trace("Required objectType {} not found", objectType);
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);
// Checking by reference is possible here due to comparing enums
if (cosemObjectTypeMeterId != CosemObjectType.UNKNOWN && objectType == cosemObjectTypeMeterId) {
meterDescriptor = new DSMRMeterDescriptor(this, cosemObject.getObisIdentifier().getGroupB());
}
}
DSMRMeterDescriptor meterDescriptor = null;
if (meterKind.isChannelRelevant()) {
final Optional<Entry<@Nullable Integer, AtomicInteger>> max = channelCounter.entrySet().stream()
.max((e1, e2) -> Integer.compare(e1.getValue().get(), e2.getValue().get()));
if (max.isPresent()) {
final Integer channel = max.get().getKey();
meterDescriptor = new DSMRMeterDescriptor(this,
channel == null ? DSMRMeterConstants.UNKNOWN_CHANNEL : channel);
}
} else {
meterDescriptor = new DSMRMeterDescriptor(this, DSMRMeterConstants.UNKNOWN_CHANNEL);
}
// Meter type is compatible, check if an identification exists
if (meterDescriptor == null && cosemObjectTypeMeterId == CosemObjectType.UNKNOWN) {
logger.trace("Meter type {} has no identification, but is compatible", this);

View File

@ -4,5 +4,5 @@
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>DSMR Binding</name>
<description>This binding integrates Dutch and Luxembourg Smart Meters</description>
<description>This binding integrates Dutch, Belgium, Luxembourg and Austrian Smart Meters</description>
</binding:binding>

View File

@ -12,7 +12,7 @@
<description>The serial port where the P1 port of the Smart Meter is connected (e.g. Linux: /dev/ttyUSB0, Windows:
COM1)</description>
</parameter>
<parameter name="receivedTimeout" type="integer" min="1">
<parameter name="receivedTimeout" type="integer" min="1" unit="s">
<default>30</default>
<label>Received Timeout</label>
<description>The time period within results are expected in seconds</description>
@ -76,7 +76,7 @@
<description>The Luxembourgian Smart meter decryption key. Ask for your energy grid operator for your Smart meter P1
key.</description>
</parameter>
<parameter name="receivedTimeout" type="integer" min="1">
<parameter name="receivedTimeout" type="integer" min="1" unit="s">
<default>30</default>
<label>Received Timeout</label>
<description>The time period within results are expected in seconds</description>
@ -84,7 +84,7 @@
</config-description>
<config-description uri="thing-type:dsmr:meterdescriptor">
<parameter name="refresh" type="integer" min="1">
<parameter name="refresh" type="integer" min="1" unit="s">
<default>60</default>
<label>Refresh</label>
<description>The time interval the data is refreshed in seconds</description>

View File

@ -90,25 +90,49 @@
<channel-type id="totalImportedEnergyRegisterPType">
<item-type>Number:Energy</item-type>
<label>Total Imported Energy (P+)</label>
<description>The Total imported energy register (P+).</description>
<description>The total imported energy register (P+).</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="totalExportedEnergyRegisterPType">
<item-type>Number:Energy</item-type>
<label>Total Exported Energy (P+)</label>
<description>The Total exported energy register (P-).</description>
<description>The total exported energy register (P-).</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="totalImportedEnergyRegisterQType">
<item-type>Number:Energy</item-type>
<label>Total Imported Energy (Q+)</label>
<description>The Total imported energy register (Q+).</description>
<description>The total imported energy register (Q+).</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="totalExportedEnergyRegisterQType">
<item-type>Number:Energy</item-type>
<label>Total Exported Energy (Q-)</label>
<description>The Total exported energy register (Q-).</description>
<description>The total exported energy register (Q-).</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="totalExportedEnergyRegisterRRate1Type" advanced="true">
<item-type>Number:Energy</item-type>
<label>Total Exported Energy R1</label>
<description>The total exported energy register R Rate1.</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="totalExportedEnergyRegisterRRate2Type" advanced="true">
<item-type>Number:Energy</item-type>
<label>Total Exported Energy R</label>
<description>The total exported energy register R Rate2.</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="totalImportedEnergyRegisterRRate1Type" advanced="true">
<item-type>Number:Energy</item-type>
<label>Total Imported Energy R1</label>
<description>The total imported energy register R Rate1.</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="totalImportedEnergyRegisterRRate2Type" advanced="true">
<item-type>Number:Energy</item-type>
<label>Total Imported Energy R2</label>
<description>The total imported energy register R Rate2.</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="tariffIndicatorType" advanced="true">

View File

@ -19,7 +19,7 @@
<channel id="emeter_production_tariff1" typeId="productionTariff1Type"/>
<channel id="emeter_production_tariff2" typeId="productionTariff2Type"/>
<channel id="emeter_tariff_indicator" typeId="tariffIndicatorType"/>
<channel id="emeter_treshold_kwh" typeId="actualTresholdkWType"/>
<channel id="emeter_treshold_kw" typeId="actualTresholdkWType"/>
<channel id="emeter_switch_position" typeId="switchPositionType"/>
<channel id="emeter_actual_delivery" typeId="actualDeliveryType"/>
<channel id="emeter_actual_production" typeId="actualProductionType"/>

View File

@ -19,7 +19,7 @@
<channel id="emeter_production_tariff1" typeId="productionTariff1Type"/>
<channel id="emeter_production_tariff2" typeId="productionTariff2Type"/>
<channel id="emeter_tariff_indicator" typeId="tariffIndicatorType"/>
<channel id="emeter_treshold_kwh" typeId="actualTresholdkWType"/>
<channel id="emeter_treshold_kw" typeId="actualTresholdkWType"/>
<channel id="emeter_switch_position" typeId="switchPositionType"/>
<channel id="emeter_actual_delivery" typeId="actualDeliveryType"/>
<channel id="emeter_actual_production" typeId="actualProductionType"/>

View File

@ -24,6 +24,12 @@
<channel id="emeter_switch_position" typeId="switchPositionType"/>
<channel id="emeter_fuse_threshold_a" typeId="actualFuseThresholdAType"/>
<channel id="emeter_treshold_kw" typeId="actualTresholdkWType"/>
<channel id="emeter_instant_power_delivery_l1" typeId="instantPowerDeliveryL1Type"/>
<channel id="emeter_instant_power_delivery_l2" typeId="instantPowerDeliveryL2Type"/>
<channel id="emeter_instant_power_delivery_l3" typeId="instantPowerDeliveryL3Type"/>
<channel id="emeter_instant_power_production_l1" typeId="instantPowerProductionL1Type"/>
<channel id="emeter_instant_power_production_l2" typeId="instantPowerProductionL2Type"/>
<channel id="emeter_instant_power_production_l3" typeId="instantPowerProductionL3Type"/>
<channel id="emeter_instant_current_l1" typeId="instantCurrentL1Type"/>
<channel id="emeter_instant_current_l2" typeId="instantCurrentL2Type"/>
<channel id="emeter_instant_current_l3" typeId="instantCurrentL3Type"/>

View File

@ -23,6 +23,7 @@
<channel id="emeter_actual_reactive_delivery" typeId="actualReactiveDeliveryType"/>
<channel id="emeter_actual_reactive_production" typeId="actualReactiveProductionType"/>
<channel id="emeter_active_threshold_smax" typeId="activeThresholdSmax"/>
<channel id="emeter_threshold_kw" typeId="actualTresholdkWType"/>
<channel id="emeter_switch_position" typeId="switchPositionType"/>
<channel id="emeter_power_failure_log_entries" typeId="powerFailureLogEntriesType"/>
<channel id="emeter_power_failure_log_timestamp0" typeId="powerFailureLogEndType"/>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="dsmr"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="electricity_smarty_v1_0_austria" listed="false">
<supported-bridge-type-refs>
<bridge-type-ref id="smartyBridge"/>
</supported-bridge-type-refs>
<label>Electricity Meter Austria</label>
<description>This is an electricity meter that complies to the Austria's Smarty V1.0 specification.</description>
<channels>
<channel id="p1_timestamp" typeId="p1TimestampType"/>
<channel id="p1_version_output" typeId="p1VersionType"/>
<channel id="emeter_actual_delivery" typeId="actualDeliveryType"/>
<channel id="emeter_actual_production" typeId="actualProductionType"/>
<channel id="emeter_delivery_tariff0" typeId="deliveryTariff0Type"/>
<channel id="emeter_delivery_tariff1" typeId="deliveryTariff1Type"/>
<channel id="emeter_delivery_tariff2" typeId="deliveryTariff2Type"/>
<channel id="emeter_production_tariff0" typeId="productionTariff0Type"/>
<channel id="emeter_production_tariff1" typeId="productionTariff1Type"/>
<channel id="emeter_production_tariff2" typeId="productionTariff2Type"/>
<channel id="emeter_actual_reactive_delivery" typeId="actualReactiveDeliveryType"/>
<channel id="emeter_actual_reactive_production" typeId="actualReactiveProductionType"/>
<channel id="emeter_total_imported_energy_register_q" typeId="totalImportedEnergyRegisterQType"/>
<channel id="emeter_total_imported_energy_register_r_rate1" typeId="totalImportedEnergyRegisterRRate1Type"/>
<channel id="emeter_total_imported_energy_register_r_rate2" typeId="totalImportedEnergyRegisterRRate2Type"/>
<channel id="emeter_total_exported_energy_register_q" typeId="totalExportedEnergyRegisterQType"/>
<channel id="emeter_total_exported_energy_register_r_rate1" typeId="totalExportedEnergyRegisterRRate1Type"/>
<channel id="emeter_total_exported_energy_register_r_rate2" typeId="totalExportedEnergyRegisterRRate2Type"/>
</channels>
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
</thing-type>
</thing:thing-descriptions>

View File

@ -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 final class TelegramReaderUtil {
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 final class TelegramReaderUtil {
* @return a P1Telegram object
*/
public static P1Telegram readTelegram(String telegramName, TelegramState expectedTelegramState) {
AtomicReference<P1Telegram> p1Telegram = new AtomicReference<>();
byte[] telegram = readRawTelegram(telegramName);
P1TelegramParser parser = new P1TelegramParser(p1Telegram::set);
final AtomicReference<P1Telegram> p1Telegram = new AtomicReference<>();
final byte[] telegram = readRawTelegram(telegramName);
final P1TelegramParser parser = new P1TelegramParser(p1Telegram::set, true);
parser.setLenientMode(true);
parser.parse(telegram, telegram.length);

View File

@ -38,8 +38,10 @@ public class P1TelegramParserTest {
{ "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 class P1TelegramParserTest {
@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,

View File

@ -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.ParameterizedTest;
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<Object[]> data() {
return Arrays.asList(new Object[][] {
{ "ace4000", EnumSet.of( ELECTRICITY_ACE4000, GAS_ACE4000)},
{ "dsmr_40", EnumSet.of( DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)},
{ "dsmr_42", EnumSet.of( DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)},
{ "dsmr_50", EnumSet.of( DEVICE_V5, ELECTRICITY_V5_0, M3_V5_0)},
{ "flu5", EnumSet.of( DEVICE_EMUCS_V1_0, ELECTRICITY_EMUCS_V1_0, GAS_EMUCS_V1_0)},
{ "Iskra_AM550", EnumSet.of( DEVICE_V5, ELECTRICITY_V5_0, M3_V5_0)},
{ "Landis_Gyr_E350", EnumSet.of( DEVICE_V2_V3, ELECTRICITY_V3_0)},
{ "Landis_Gyr_ZCF110", EnumSet.of( DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)},
{ "Sagemcom_XS210", EnumSet.of( DEVICE_V4, ELECTRICITY_V4_2)},
{ "smarty", EnumSet.of( DEVICE_V5, ELECTRICITY_SMARTY_V1_0)},
{ "smarty_with_units", EnumSet.of( DEVICE_V5, ELECTRICITY_SMARTY_V1_0, M3_V4)},
{ "ace4000", EnumSet.of(ELECTRICITY_ACE4000, GAS_ACE4000)},
{ "dsmr_40", EnumSet.of(DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)},
{ "dsmr_42", EnumSet.of(DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)},
{ "dsmr_50", EnumSet.of(DEVICE_V5, ELECTRICITY_V5_0, M3_V5_0)},
{ "dsmr_50_austria", EnumSet.of(ELECTRICITY_SMARTY_V1_0_AUSTRIA)},
{ "flu5", EnumSet.of(DEVICE_EMUCS_V1_0, ELECTRICITY_EMUCS_V1_0, GAS_EMUCS_V1_0)},
{ "flu5_extra", EnumSet.of(DEVICE_EMUCS_V1_0, ELECTRICITY_EMUCS_V1_0, GAS_EMUCS_V1_0)},
{ "Iskra_AM550", EnumSet.of(DEVICE_V5, ELECTRICITY_V5_0, M3_V5_0)},
{ "Landis_Gyr_E350", EnumSet.of(DEVICE_V2_V3, ELECTRICITY_V3_0)},
{ "Landis_Gyr_ZCF110", EnumSet.of(DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)},
{ "Sagemcom_XS210", EnumSet.of(DEVICE_V4, ELECTRICITY_V4_2)},
{ "smarty", EnumSet.of(DEVICE_V5, ELECTRICITY_SMARTY_V1_0)},
{ "smarty_with_units", EnumSet.of(DEVICE_V5, ELECTRICITY_SMARTY_V1_0, M3_V4)},
});
}
// @formatter:on
@ -64,21 +77,17 @@ public class DSMRMeterDetectorTest {
@ParameterizedTest
@MethodSource("data")
public void testDetectMeters(final String telegramName, final Set<DSMRMeterType> expectedMeters) {
P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK);
DSMRMeterDetector detector = new DSMRMeterDetector();
Entry<Collection<DSMRMeterDescriptor>, Map<CosemObjectType, CosemObject>> entry = detector
.detectMeters(telegram);
Collection<DSMRMeterDescriptor> detectMeters = entry.getKey();
final P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK);
final DSMRMeterDetector detector = new DSMRMeterDetector();
final Entry<Collection<DSMRMeterDescriptor>, List<CosemObject>> entry = detector.detectMeters(telegram);
final Collection<DSMRMeterDescriptor> detectMeters = entry.getKey();
assertEquals(expectedMeters.size(), detectMeters.size(),
"Should detect correct number of meters: " + Arrays.toString(detectMeters.toArray()));
assertEquals(Collections.emptyMap(), entry.getValue(), "Should not have any undetected cosem objects: ");
assertEquals(Collections.emptyList(), telegram.getUnknownCosemObjects(),
"Should not have any unknown cosem objects");
for (DSMRMeterType meter : expectedMeters) {
assertEquals(
1, detectMeters.stream().filter(e -> e.getMeterType() == meter).count(),
assertEquals(List.of(), entry.getValue(), "Should not have any undetected cosem objects: ");
assertEquals(List.of(), telegram.getUnknownCosemObjects(), "Should not have any unknown cosem objects");
for (final DSMRMeterType meter : expectedMeters) {
assertEquals(1, detectMeters.stream().filter(e -> e.getMeterType() == meter).count(),
String.format("Meter '%s' not found: %s", meter,
Arrays.toString(detectMeters.toArray(new DSMRMeterDescriptor[0]))));
}

View File

@ -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<CosemObject> cosemObjects = TelegramReaderUtil.readTelegram("dsmr_50", TelegramState.OK)
.getCosemObjects();
List<CosemObject> filterMeterValues = meter
.filterMeterValues(TelegramReaderUtil.readTelegram("dsmr_50", TelegramState.OK).getCosemObjects());
assertEquals(DSMRMeterType.DEVICE_V5.requiredCosemObjects.length, filterMeterValues.size(),
"Filter should return all required objects");
assertMeterValues(cosemObjects, DSMRMeterType.DEVICE_V5, DSMRMeterConstants.UNKNOWN_CHANNEL, 3);
assertMeterValues(cosemObjects, DSMRMeterType.ELECTRICITY_V5_0, 0, 29);
assertMeterValues(cosemObjects, DSMRMeterType.M3_V5_0, 1, 3);
}
private void assertMeterValues(List<CosemObject> cosemObjects, DSMRMeterType type, int channel, int expected) {
final DSMRMeterDescriptor descriptor = new DSMRMeterDescriptor(type, channel);
final DSMRMeter meter = new DSMRMeter(descriptor);
final List<CosemObject> filterMeterValues = meter.filterMeterValues(cosemObjects, channel);
assertEquals(expected, filterMeterValues.size(), "Filter should return all required objects");
}
}

View File

@ -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

View File

@ -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