[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 # DSMR Binding
The DSMR-binding is targeted towards Dutch, Belgium and Luxembourger users having a smart meter (Dutch: 'Slimme meter'). The DSMR-binding is targeted towards Dutch, Belgium, Luxembourger and Austrian users having a smart meter (Dutch: 'Slimme meter').
Data of Dutch/Belgium/Luxembourg smart meters can be obtained via the P1-port. 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. When connecting this port from a serial port the data can be read out.
This binding reads the P1-port of: 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. * 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. * Belgium Smart Meters that comply to e-MUCS v1.0.
* Luxembourgs electricity meter "Smarty" that comply to 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. 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 (The Netherlands/Belgium)
`dsmrBridge`: This is the device that communicated between the binding (serial) and its internal meters. `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. You always have to have a 'Dutch/Belgium Smart Meter'-bridge.
Specific meters are bound via the bridge to the smart meter. A smart meter consists typically out of minimal 2 meters. 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. 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. 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. 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. Belgium meters have `emucs` in the thing name.
Due to it similarities the bridge for Belgium meters is also a dsmrBridge. 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. `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 ## Discovery
@ -162,134 +166,139 @@ The following channels are supported:
- O channel is supported only if the device has this functionality - 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 | | 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** | | | | | | | | | | | | | | **Channels for the generic device** | | | | | | | | | | | |
| `p1_text_code` | String | Text code from the device | - | Y | Y | Y | Y | Y | Y | - | - | - | | `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_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_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_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 | | `p1_timestamp` | DateTime | Timestamp of the last device reading | - | - | - | - | Y | Y | Y | Y | Y | Y | Y |
| | | **Channels for the cooling meter** | | | | | | | | | | | | | | **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` | 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_value_v2_timestamp` | DateTime | Timestamp of the last meter reading | Y | - | Y | - | - | - | - | - | - | - | - |
| `cmeter_equipment_identifier_v2_2` | String | Equipment identifier | - | - | Y | - | - | - | - | - | - | - | | `cmeter_equipment_identifier_v2_2` | String | Equipment identifier | - | - | Y | - | - | - | - | - | - | - | - |
| | | **Channels for the main electricity meter** | | | | | | | | | | | | | | **Channels for the main electricity meter** | | | | | | | | | | | |
| `emeter_equipment_identifier_v2_x` | String | Electricity Equipment identifier | - | Y | Y | - | - | - | - | - | Y | - | | `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_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_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 | | `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 | | `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 | - | | `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 | | `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 | | `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_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_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_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_imported_energy_register_q` | Number:Energy | Total Imported Energy (Q+) (kvarh) | - | - | - | - | - | - | - | - | Y | - | Y |
| `emeter_total_exported_energy_register_q` | Number:Energy | Total Exported Energy (Q-) (kvarh) | - | - | - | - | - | - | - | - | Y | - | | `emeter_total_imported_energy_register_r_rate1` | Number:Energy | Total Imported Energy Rate 1 (kvarh) | - | - | - | - | - | - | - | - | - | - | Y |
| `emeter_tariff_indicator` | String | Current tariff indicator | Y | Y | Y | Y | Y | Y | Y | Y | - | Y | | `emeter_total_imported_energy_register_r_rate2` | Number:Energy | Total Imported Energy Rate 2 (kvarh) | - | - | - | - | - | - | - | - | - | - | Y |
| `emeter_treshold_a_v2_1` | Number:ElectricCurrent | Actual threshold (A) | - | Y | - | - | - | - | - | - | - | - | | `emeter_total_exported_energy_register_q` | Number:Energy | Total Exported Energy (Q-) (kvarh) | - | - | - | - | - | - | - | - | Y | - | Y |
| `emeter_treshold_a` | Number:ElectricCurrent | Actual threshold (A) | Y | - | Y | Y | - | - | - | - | - | - | | `emeter_total_exported_energy_register_r_rate1` | Number:Energy | Total Exported Energy Rate 1 (kvarh) | - | - | - | - | - | - | - | - | - | - | Y |
| `emeter_fuse_threshold_a` | Number:ElectricCurrent | Active fuse threshold (A) | - | - | - | - | - | - | - | - | - | Y | | `emeter_total_exported_energy_register_r_rate2` | Number:Energy | Total Exported Energy Rate 2 (kvarh) | - | - | - | - | - | - | - | - | - | - | Y |
| `emeter_treshold_kwh` | Number:Power | Actual threshold (kW) | - | - | - | - | Y | Y | - | - | - | Y | | `emeter_tariff_indicator` | String | Current tariff indicator | Y | Y | Y | Y | Y | Y | Y | Y | - | Y | - |
| `emeter_switch_position_v2_1` | Number | Switch position | - | Y | - | - | - | - | - | - | - | - | | `emeter_treshold_a_v2_1` | Number:ElectricCurrent | Actual threshold (A) | - | Y | - | - | - | - | - | - | - | - | - |
| `emeter_switch_position` | Number | Switch position | Y | - | Y | Y | Y | Y | - | - | Y | Y | | `emeter_treshold_a` | Number:ElectricCurrent | Actual threshold (A) | Y | - | Y | Y | - | - | - | - | - | - | - |
| `emeter_active_import_power` | Number:Power | Aggregate active import power (W) | Y | - | - | - | - | - | - | - | - | - | | `emeter_fuse_threshold_a` | Number:ElectricCurrent | Active fuse threshold (A) | - | - | - | - | - | - | - | - | - | Y | - |
| `emeter_actual_delivery` | Number:Power | Current power delivery (kW) | - | Y | Y | Y | Y | Y | Y | Y | Y | Y | | `emeter_treshold_kwh` | Number:Power | Actual threshold (kW) | - | - | - | - | Y | Y | - | - | - | Y | - |
| `emeter_actual_production` | Number:Power | Current power production (kW) | - | - | - | Y | Y | Y | Y | Y | Y | Y | | `emeter_switch_position_v2_1` | Number | Switch position | - | Y | - | - | - | - | - | - | - | - | - |
| `emeter_actual_reactive_delivery` | Number | Actual Reactive Power Delivery (kvar) | - | - | - | - | - | - | - | - | Y | - | | `emeter_switch_position` | Number | Switch position | Y | - | Y | Y | Y | Y | - | - | Y | Y | - |
| `emeter_actual_reactive_production` | Number | Actual Reactive Power Production (kvar) | - | - | - | - | - | - | - | - | Y | - | | `emeter_active_import_power` | Number:Power | Aggregate active import power (W) | Y | - | - | - | - | - | - | - | - | - | - |
| `emeter_active_threshold_smax` | Number | Active threshold (SMAX) (kVA) | - | - | - | - | - | - | - | - | Y | - | | `emeter_actual_delivery` | Number:Power | Current power delivery (kW) | - | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| `emeter_power_failures` | Number | Number of power failures | - | - | - | - | Y | Y | Y | Y | Y | - | | `emeter_actual_production` | Number:Power | Current power production (kW) | - | - | - | Y | Y | Y | Y | Y | Y | Y | Y |
| `emeter_long_power_failures` | Number | Number of long power failures | - | - | - | - | Y | Y | Y | Y | - | - | | `emeter_actual_reactive_delivery` | Number | Actual Reactive Power Delivery (kvar) | - | - | - | - | - | - | - | - | Y | - | Y |
| `emeter_power_failure_log_entries` | Number | Number of entries in the power failure log | - | - | - | - | Y | Y | Y | Y | Y | - | | `emeter_actual_reactive_production` | Number | Actual Reactive Power Production (kvar) | - | - | - | - | - | - | - | - | 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_active_threshold_smax` | Number | Active threshold (SMAX) (kVA) | - | - | - | - | - | - | - | - | 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_threshold_kw` | Number:Power | Active threshold (SMAX) (kVA) | - | - | - | Y | Y | Y | - | - | Y | Y | - |
| `emeter_voltage_sags_l1` | Number | Number of voltage sags L1 | - | - | - | - | Y | Y | Y | Y | Y | - | | `emeter_power_failures` | Number | Number of power failures | - | - | - | - | Y | Y | Y | Y | Y | - | - |
| `emeter_voltage_sags_l2` | Number | Number of voltage sags L2 | - | - | - | - | O | O | O | O | O | - | | `emeter_long_power_failures` | Number | Number of long power failures | - | - | - | - | Y | Y | Y | Y | - | - | - |
| `emeter_voltage_sags_l3` | Number | Number of voltage sags L3 | - | - | - | - | O | O | O | O | O | - | | `emeter_power_failure_log_entries` | Number | Number of entries in the power failure log | - | - | - | - | Y | Y | Y | Y | Y | - | - |
| `emeter_voltage_swells_l1` | Number | Number of voltage swells L1 | - | - | - | - | 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_voltage_swells_l2` | Number | Number of voltage swells L2 | - | - | - | - | O | O | O | O | - | - | | `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_swells_l3` | Number | Number of voltage swells L3 | - | - | - | - | O | O | O | O | - | - | | `emeter_voltage_sags_l1` | Number | Number of voltage sags L1 | - | - | - | - | Y | Y | Y | Y | Y | - | - |
| `emeter_instant_current_l1` | Number:ElectricCurrent | Instant Current L1 (A) | - | - | - | - | Y | Y | Y | Y | Y | Y | | `emeter_voltage_sags_l2` | Number | Number of voltage sags L2 | - | - | - | - | O | O | O | O | O | - | - |
| `emeter_instant_current_l2` | Number:ElectricCurrent | Instant Current L2 (A) | - | - | - | - | O | O | O | O | O | Y | | `emeter_voltage_sags_l3` | Number | Number of voltage sags L3 | - | - | - | - | O | O | O | O | O | - | - |
| `emeter_instant_current_l3` | Number:ElectricCurrent | Instant Current L3 (A) | - | - | - | - | O | O | O | O | O | Y | | `emeter_voltage_swells_l1` | Number | Number of voltage swells L1 | - | - | - | - | Y | Y | Y | Y | - | - | - |
| `emeter_instant_power_delivery_l1` | Number:Power | Instant Power Delivery L1 (kW) | - | - | - | - | Y | Y | Y | Y | Y | - | | `emeter_voltage_swells_l2` | Number | Number of voltage swells L2 | - | - | - | - | O | O | O | O | - | - | - |
| `emeter_instant_power_delivery_l2` | Number:Power | Instant Power Delivery L2 (kW) | - | - | - | - | O | O | O | O | O | - | | `emeter_voltage_swells_l3` | Number | Number of voltage swells L3 | - | - | - | - | O | O | O | O | - | - | - |
| `emeter_instant_power_delivery_l3` | Number:Power | Instant Power Delivery L3 (kW) | - | - | - | - | O | O | O | O | O | - | | `emeter_instant_current_l1` | Number:ElectricCurrent | Instant Current L1 (A) | - | - | - | - | Y | Y | Y | Y | Y | Y | - |
| `emeter_instant_power_production_l1` | Number:Power | Instant Power Production L1 (kW) | - | - | - | - | Y | Y | Y | Y | Y | - | | `emeter_instant_current_l2` | Number:ElectricCurrent | Instant Current L2 (A) | - | - | - | - | O | O | O | O | O | Y | - |
| `emeter_instant_power_production_l2` | Number:Power | Instant Power Production L2 (kW) | - | - | - | - | O | O | O | O | O | - | | `emeter_instant_current_l3` | Number:ElectricCurrent | Instant Current L3 (A) | - | - | - | - | O | O | O | O | O | Y | - |
| `emeter_instant_power_production_l3` | Number:Power | Instant Power Production L3 (kW) | - | - | - | - | O | O | O | O | O | - | | `emeter_instant_power_delivery_l1` | Number:Power | Instant Power Delivery L1 (kW) | - | - | - | - | Y | Y | Y | Y | Y | - | - |
| `emeter_instant_reactive_power_delivery_l1` | Number:Power | Instant Reactive Power Delivery L1 (kvar) | - | - | - | - | - | - | - | - | Y | - | | `emeter_instant_power_delivery_l2` | Number:Power | Instant Power Delivery L2 (kW) | - | - | - | - | O | O | O | O | O | - | - |
| `emeter_instant_reactive_power_delivery_l2` | Number:Power | Instant Reactive Power Delivery L2 (kvar) | - | - | - | - | - | - | - | - | Y | - | | `emeter_instant_power_delivery_l3` | Number:Power | Instant Power Delivery L3 (kW) | - | - | - | - | O | O | O | O | O | - | - |
| `emeter_instant_reactive_power_delivery_l3` | Number:Power | Instant Reactive Power Delivery L3 (kvar) | - | - | - | - | - | - | - | - | Y | - | | `emeter_instant_power_production_l1` | Number:Power | Instant Power Production L1 (kW) | - | - | - | - | Y | Y | Y | Y | Y | - | - |
| `emeter_instant_reactive_power_production_l1` | Number:Power | Instant Reactive Power Prodcution L1 (kvar) | - | - | - | - | - | - | - | - | Y | - | | `emeter_instant_power_production_l2` | Number:Power | Instant Power Production L2 (kW) | - | - | - | - | O | O | O | O | O | - | - |
| `emeter_instant_reactive_power_production_l2` | Number:Power | Instant Reactive Power Prodcution L2 (kvar) | - | - | - | - | - | - | - | - | Y | - | | `emeter_instant_power_production_l3` | Number:Power | Instant Power Production L3 (kW) | - | - | - | - | O | O | O | O | O | - | - |
| `emeter_instant_reactive_power_production_l3` | Number:Power | Instant Reactive Power Prodcution L3 (kvar) | - | - | - | - | - | - | - | - | Y | - | | `emeter_instant_reactive_power_delivery_l1` | Number:Power | Instant Reactive Power Delivery L1 (kvar) | - | - | - | - | - | - | - | - | Y | - | - |
| `emeter_instant_voltage_l1` | Number:ElectricPotential | Instant Voltage L1 (V) | - | - | - | - | - | - | - | Y | - | Y | | `emeter_instant_reactive_power_delivery_l2` | Number:Power | Instant Reactive Power Delivery L2 (kvar) | - | - | - | - | - | - | - | - | Y | - | - |
| `emeter_instant_voltage_l2` | Number:ElectricPotential | Instant Voltage L2 (V) | - | - | - | - | - | - | - | O | - | Y | | `emeter_instant_reactive_power_delivery_l3` | Number:Power | Instant Reactive Power Delivery L3 (kvar) | - | - | - | - | - | - | - | - | Y | - | - |
| `emeter_instant_voltage_l3` | Number:ElectricPotential | Instant Voltage L3 (V) | - | - | - | - | - | - | - | O | - | Y | | `emeter_instant_reactive_power_production_l1` | Number:Power | Instant Reactive Power Prodcution L1 (kvar) | - | - | - | - | - | - | - | - | Y | - | - |
| | | **Channels for the slave electricity meter** | | | | | | | | | | | | `emeter_instant_reactive_power_production_l2` | Number:Power | Instant Reactive Power Prodcution L2 (kvar) | - | - | - | - | - | - | - | - | Y | - | - |
| `meter_device_type` | String | Slave Electricity Meter Device Type | - | - | - | - | Y | Y | Y | Y | - | - | | `emeter_instant_reactive_power_production_l3` | Number:Power | Instant Reactive Power Prodcution L3 (kvar) | - | - | - | - | - | - | - | - | Y | - | - |
| `meter_equipment_identifier` | String | Slave Electricity Meter ID | - | - | - | - | Y | Y | Y | Y | - | - | | `emeter_instant_voltage_l1` | Number:ElectricPotential | Instant Voltage L1 (V) | - | - | - | - | - | - | - | Y | - | Y | - |
| `emeter_delivery_tariff0` | Number:Energy | Total amount of slave electricity used for tariff 0 (kWh) | Y | - | - | - | - | - | - | - | - | - | | `emeter_instant_voltage_l2` | Number:ElectricPotential | Instant Voltage L2 (V) | - | - | - | - | - | - | - | O | - | Y | - |
| `emeter_delivery_tariff1` | Number:Energy | Total amount of slave electricity used for tariff 1 (kWh) | Y | - | - | - | - | - | - | - | - | - | | `emeter_instant_voltage_l3` | Number:ElectricPotential | Instant Voltage L3 (V) | - | - | - | - | - | - | - | O | - | Y | - |
| `emeter_delivery_tariff2` | Number:Energy | Total amount of slave electricity used for tariff 2 (kWh) | Y | - | - | - | - | - | - | - | - | - | | | | **Channels for the slave electricity meter** | | | | | | | | | | | |
| `emeter_production_tariff0` | Number:Energy | Total amount of slave electricity produced for tariff 0 (kWh) | Y | - | - | - | - | - | - | - | - | - | | `meter_device_type` | String | Slave Electricity Meter Device Type | - | - | - | - | Y | Y | Y | Y | - | - | - |
| `emeter_production_tariff1` | Number:Energy | Total amount of slave electricity produced for tariff 1 (kWh) | Y | - | - | - | - | - | - | - | - | - | | `meter_equipment_identifier` | String | Slave Electricity Meter ID | - | - | - | - | Y | Y | Y | Y | - | - | - |
| `emeter_production_tariff2` | Number:Energy | Total amount of slave electricity produced for tariff 2 (kWh) | Y | - | - | - | - | - | - | - | - | - | | `emeter_delivery_tariff0` | Number:Energy | Total amount of slave electricity used for tariff 0 (kWh) | Y | - | - | - | - | - | - | - | - | - | - |
| `emeter_tariff_indicator` | String | Current slave tariff indicator | Y | - | - | - | - | - | - | - | - | - | | `emeter_delivery_tariff1` | Number:Energy | Total amount of slave electricity used for tariff 1 (kWh) | Y | - | - | - | - | - | - | - | - | - | - |
| `emeter_treshold_a` | Number:ElectricCurrent | Actual slave threshold (A) | Y | - | - | - | - | - | - | - | - | - | | `emeter_delivery_tariff2` | Number:Energy | Total amount of slave electricity used for tariff 2 (kWh) | Y | - | - | - | - | - | - | - | - | - | - |
| `meter_switch_position` | Number | Slave electricity switch position | Y | - | - | Y | Y | Y | - | - | - | - | | `emeter_production_tariff0` | Number:Energy | Total amount of slave electricity produced for tariff 0 (kWh) | Y | - | - | - | - | - | - | - | - | - | - |
| `emeter_active_import_power` | Number:Power | Slave aggregate active import power (W) | Y | - | - | - | - | - | - | - | - | - | | `emeter_production_tariff1` | Number:Energy | Total amount of slave electricity produced for tariff 1 (kWh) | Y | - | - | - | - | - | - | - | - | - | - |
| `emeter_value` | Number:Energy | Slave electricity usage (kWh) in the past period | - | - | - | - | Y | Y | Y | Y | - | - | | `emeter_production_tariff2` | Number:Energy | Total amount of slave electricity produced for tariff 2 (kWh) | Y | - | - | - | - | - | - | - | - | - | - |
| `emeter_value_timestamp` | DateTime | Timestamp of the last reading | - | - | - | - | Y | Y | Y | Y | - | - | | `emeter_tariff_indicator` | String | Current slave tariff indicator | Y | - | - | - | - | - | - | - | - | - | - |
| | | **Channels for the gas meter** | | | | | | | | | | | | `emeter_treshold_a` | Number:ElectricCurrent | Actual slave threshold (A) | Y | - | - | - | - | - | - | - | - | - | - |
| `meter_device_type` | String | Gas Meter Device Type | - | - | - | Y | - | - | - | - | - | Y | | `meter_switch_position` | Number | Slave electricity switch position | Y | - | - | Y | Y | Y | - | - | - | - | - |
| `meter_equipment_identifier` | String | Gas Meter ID | Y | - | - | Y | - | - | - | - | - | Y | | `emeter_active_import_power` | Number:Power | Slave aggregate active import power (W) | Y | - | - | - | - | - | - | - | - | - | - |
| `gmeter_equipment_identifier_v2` | String | Gas Meter ID | - | Y | Y | - | - | - | - | - | - | - | | `emeter_value` | Number:Energy | Slave electricity usage (kWh) in the past period | - | - | - | - | Y | Y | Y | Y | - | - | - |
| `gmeter_24h_delivery_v2` | Number:Volume | Gas Delivery past 24 hours | Y | Y | Y | - | - | - | - | - | - | - | | `emeter_value_timestamp` | DateTime | Timestamp of the last reading | - | - | - | - | Y | Y | Y | Y | - | - | - |
| `gmeter_24h_delivery_v2_timestamp` | DateTime | Timestamp of the last reading | Y | Y | Y | - | - | - | - | - | - | - | | | | **Channels for the gas meter** | | | | | | | | | | | |
| `gmeter_24h_delivery_compensated_v2` | Number:Volume | Gas Delivery past 24 hours (compensated) | - | Y | Y | - | - | - | - | - | - | - | | `meter_device_type` | String | Gas Meter Device Type | - | - | - | Y | - | - | - | - | - | Y | - |
| `gmeter_24h_delivery_compensated_v2_timestamp` | DateTime | Timestamp of the last reading | - | Y | Y | - | - | - | - | - | - | - | | `meter_equipment_identifier` | String | Gas Meter ID | Y | - | - | Y | - | - | - | - | - | Y | - |
| `gmeter_value_v3` | Number:Volume | Gas Delivery past period | - | - | - | Y | - | - | - | - | - | - | | `gmeter_equipment_identifier_v2` | String | Gas Meter ID | - | Y | Y | - | - | - | - | - | - | - | - |
| `gmeter_value_v3_timestamp` | DateTime | Timestamp of the last reading | - | - | - | Y | - | - | - | - | - | - | | `gmeter_24h_delivery_v2` | Number:Volume | Gas Delivery past 24 hours | Y | Y | Y | - | - | - | - | - | - | - | - |
| `gmeter_last_value` | Number:Volume | Gas Delivery last reading value | - | - | - | - | - | - | - | - | - | Y | | `gmeter_24h_delivery_v2_timestamp` | DateTime | Timestamp of the last reading | Y | Y | Y | - | - | - | - | - | - | - | - |
| `gmeter_last_value_timestamp` | DateTime | Timesamp of last Gas Delivery reading | - | - | - | - | - | - | - | - | - | Y | | `gmeter_24h_delivery_compensated_v2` | Number:Volume | Gas Delivery past 24 hours (compensated) | - | Y | Y | - | - | - | - | - | - | - | - |
| `gmeter_valve_position_v2_1` | Number | Gas Valve position | - | Y | - | - | - | - | - | - | - | - | | `gmeter_24h_delivery_compensated_v2_timestamp` | DateTime | Timestamp of the last reading | - | Y | Y | - | - | - | - | - | - | - | - |
| `gmeter_valve_position_v2_2` | Number | Gas Valve position | Y | - | Y | - | - | - | - | - | - | - | | `gmeter_value_v3` | Number:Volume | Gas Delivery past period | - | - | - | Y | - | - | - | - | - | - | - |
| `meter_valve_switch_position` | Number | Gas Valve position | - | - | - | Y | - | - | - | - | - | Y | | `gmeter_value_v3_timestamp` | DateTime | Timestamp of the last reading | - | - | - | Y | - | - | - | - | - | - | - |
| | | **Channels for the generic meter** | | | | | | | | | | | | `gmeter_last_value` | Number:Volume | Gas Delivery last reading value | - | - | - | - | - | - | - | - | - | Y | - |
| `meter_device_type` | String | Generic Meter Device Type | - | - | - | Y | - | - | - | - | - | - | | `gmeter_last_value_timestamp` | DateTime | Timesamp of last Gas Delivery reading | - | - | - | - | - | - | - | - | - | Y | - |
| `gmeter_equipment_identifier` | String | Generic Meter ID | - | - | - | Y | - | - | - | - | - | - | | `gmeter_valve_position_v2_1` | Number | Gas Valve position | - | Y | - | - | - | - | - | - | - | - | - |
| `genmeter_value_v3` | Number | Delivery past period | - | - | - | Y | - | - | - | - | - | - | | `gmeter_valve_position_v2_2` | Number | Gas Valve position | Y | - | Y | - | - | - | - | - | - | - | - |
| `meter_valve_switch_position` | Number | Generic Meter Valve/Switch position | - | - | - | Y | - | - | - | - | - | - | | `meter_valve_switch_position` | Number | Gas Valve position | - | - | - | Y | - | - | - | - | - | Y | - |
| | | **Channels for the GJ meter (Heating or Cooling)** | | | | | | | | | | - | | | | **Channels for the generic meter** | | | | | | | | | | | |
| `meter_device_type` | String | GJ Meter Device Type | - | - | - | Y | Y | Y | Y | Y | - | | | `meter_device_type` | String | Generic Meter Device Type | - | - | - | Y | - | - | - | - | - | - | - |
| `meter_equipment_identifier` | Number | GJ Meter ID | - | - | - | Y | Y | Y | Y | Y | - | - | | `gmeter_equipment_identifier` | String | Generic Meter ID | - | - | - | Y | - | - | - | - | - | - | - |
| `gjmeter_value_v3` | Number:Energy | GJ Delivery past period | - | - | - | Y | - | - | - | - | - | - | | `genmeter_value_v3` | Number | Delivery past period | - | - | - | Y | - | - | - | - | - | - | - |
| `gjmeter_value_v3_timestamp` | DateTime | Timestamp of the last reading | - | - | - | Y | - | - | - | - | - | - | | `meter_valve_switch_position` | Number | Generic Meter Valve/Switch position | - | - | - | Y | - | - | - | - | - | - | - |
| `gjmeter_value_v4` | Number:Energy | GJ Delivery past period | - | - | - | - | Y | Y | Y | Y | - | - | | | | **Channels for the GJ meter (Heating or Cooling)** | | | | | | | | | | - | - |
| `gjmeter_value_v4_timestamp` | DateTime | Timestamp of the last reading | - | - | - | - | Y | Y | Y | Y | - | - | | `meter_device_type` | String | GJ Meter Device Type | - | - | - | Y | Y | Y | Y | Y | - | | |
| | | **Channels for the heating meter** | | | | | | | | | | | | `meter_equipment_identifier` | Number | GJ Meter ID | - | - | - | Y | Y | Y | Y | Y | - | - | - |
| `meter_valve_switch_position` | Number | GJ Meter Valve position | - | - | - | Y | Y | Y | Y | - | - | - | | `gjmeter_value_v3` | Number:Energy | GJ Delivery past period | - | - | - | Y | - | - | - | - | - | - | - |
| `meter_equipment_identifier` | String | Heating Meter ID | Y | - | - | - | - | - | - | - | - | - | | `gjmeter_value_v3_timestamp` | DateTime | Timestamp of the last reading | - | - | - | Y | - | - | - | - | - | - | - |
| `hmeter_equipment_identifier_v2_2` | String | Heating Meter ID | - | - | Y | - | - | - | - | - | - | - | | `gjmeter_value_v4` | Number:Energy | GJ Delivery past period | - | - | - | - | Y | Y | Y | Y | - | - | - |
| `hmeter_value_v2` | Number:Energy | Heating Delivery past period | Y | - | Y | - | - | - | - | - | - | - | | `gjmeter_value_v4_timestamp` | DateTime | Timestamp of the last reading | - | - | - | - | Y | Y | Y | Y | - | - | - |
| `hmeter_value_v2_timestamp` | DateTime | Timestamp of the last reading | Y | - | Y | - | - | - | - | - | - | - | | | | **Channels for the heating meter** | | | | | | | | | | | |
| | | m3 Meter Device Type | - | - | - | - | Y | Y | Y | Y | - | - | | `meter_valve_switch_position` | Number | GJ Meter Valve position | - | - | - | Y | Y | Y | Y | - | - | - | - |
| `meter_equipment_identifier` | String | m3 Meter ID | - | - | - | - | Y | Y | Y | Y | - | - | | `meter_equipment_identifier` | String | Heating Meter ID | Y | - | - | - | - | - | - | - | - | - | - |
| `m3meter_value` | Number:Volume | m3 Delivery past period | - | - | - | - | Y | Y | Y | Y | - | - | | `hmeter_equipment_identifier_v2_2` | String | Heating Meter ID | - | - | Y | - | - | - | - | - | - | - | - |
| `meter_valve_switch_position` | Number | m3 Meter Valve position | - | - | - | - | Y | Y | Y | - | - | - | | `hmeter_value_v2` | Number:Energy | Heating Delivery past period | Y | - | Y | - | - | - | - | - | - | - | - |
| | | **Channels for the water meter** | | | | | | | | | | | | `hmeter_value_v2_timestamp` | DateTime | Timestamp of the last reading | Y | - | Y | - | - | - | - | - | - | - | - |
| `meter_device_type` | String | Water Meter Device Type | - | - | - | Y | - | - | - | - | - | - | | | | m3 Meter Device Type | - | - | - | - | Y | Y | Y | Y | - | - | - |
| `meter_equipment_identifier` | String | Water Meter ID | Y | - | - | Y | - | - | - | - | - | - | | `meter_equipment_identifier` | String | m3 Meter ID | - | - | - | - | Y | Y | Y | Y | - | - | - |
| `wmeter_equipment_identifier_v2_2` | String | Water Meter ID | - | - | Y | - | - | - | - | - | - | - | | `m3meter_value` | Number:Volume | m3 Delivery past period | - | - | - | - | Y | Y | Y | Y | - | - | - |
| `wmeter_value_v2` | Number:Volume | Water Delivery past period | Y | - | Y | - | - | - | - | - | - | - | | `meter_valve_switch_position` | Number | m3 Meter Valve position | - | - | - | - | Y | Y | Y | - | - | - | - |
| `wmeter_value_v2_timestamp` | DateTime | Timestamp of the last reading | Y | - | Y | - | - | - | - | - | - | - | | | | **Channels for the water meter** | | | | | | | | | | | |
| `wmeter_value_v3` | Number:Volume | Water Delivery past period | - | - | - | Y | - | - | - | - | - | - | | `meter_device_type` | String | Water Meter Device Type | - | - | - | Y | - | - | - | - | - | - | - |
| `meter_valve_switch_position` | Number | Water Meter Valve position | - | - | - | 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`. *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, 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** **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 ## 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"} 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 MeterDeliveryTariff0 "Delivered Low Tariff [%.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 MeterDeliveryTariff1 "Delivered High Tariff [%.3f kWh]" {channel="dsmr:electricity_v5_0:mysmartmeter:electricityV5:emeter_delivery_tariff2"}
``` ```
## Determine M-Bus channel ## Determine M-Bus channel

View File

@ -12,12 +12,15 @@
*/ */
package org.openhab.binding.dsmr.internal.device.connector; package org.openhab.binding.dsmr.internal.device.connector;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* Error events from a connector. * Error events from a connector.
* *
* @author M. Volaart - Initial contribution * @author M. Volaart - Initial contribution
* @author Hilbrand Bouwkamp - Reduced number of event to only errors * @author Hilbrand Bouwkamp - Reduced number of event to only errors
*/ */
@NonNullByDefault
public enum DSMRConnectorErrorEvent { public enum DSMRConnectorErrorEvent {
DONT_EXISTS, DONT_EXISTS,
IN_USE, IN_USE,

View File

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

View File

@ -42,16 +42,6 @@ public class CosemObjectFactory {
*/ */
private final Map<OBISIdentifier, List<CosemObjectType>> obisLookupTableMultipleFixed = new HashMap<>(); 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 * Creates a new CosemObjectFactory
*/ */
@ -64,7 +54,7 @@ public class CosemObjectFactory {
* (i.e. groupA == null || groupB == null || groupC == null). This lookuptable will be filled * (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 * 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 * 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 * - obisWildCardCosemTypeList. This is the list of all wild card Cosem Object types. Multiple Cosem Object
* Types can have the same wild card OBISIdentifer. * 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. * correct OBISIdentifier is discovered for a certain OBISMsgType this is added to the obisLookupTableDynamic.
*/ */
for (CosemObjectType msgType : CosemObjectType.values()) { for (CosemObjectType msgType : CosemObjectType.values()) {
if (msgType.obisId.reducedOBISIdentifierIsWildCard()) { if (msgType.obisId.isConflict()) {
obisWildcardCosemTypeList.add(msgType);
} else if (msgType.obisId.isConflict()) {
obisLookupTableMultipleFixed.computeIfAbsent(msgType.obisId, r -> new ArrayList<>()).add(msgType); obisLookupTableMultipleFixed.computeIfAbsent(msgType.obisId, r -> new ArrayList<>()).add(msgType);
} else { } else {
obisLookupTableFixed.put(msgType.obisId, msgType); 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); objectType = obisLookupTableFixed.get(reducedObisIdGroupE);
if (objectType != null) { if (objectType != null) {
return getCosemObjectInternal(objectType, obisId, cosemStringValues); 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); logger.debug("Received unknown Cosem Object(OBIS id: {})", obisId);
return null; return null;

View File

@ -14,11 +14,11 @@ package org.openhab.binding.dsmr.internal.device.cosem;
import java.util.AbstractMap.SimpleEntry; import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map.Entry; 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; import org.openhab.core.library.unit.Units;
/** /**
@ -36,122 +36,126 @@ import org.openhab.core.library.unit.Units;
* @author M. Volaart - Initial contribution * @author M. Volaart - Initial contribution
* @author Hilbrand Bouwkamp - Cosem subclasses made into factory classes and introduced quantity type * @author Hilbrand Bouwkamp - Cosem subclasses made into factory classes and introduced quantity type
*/ */
@NonNullByDefault
public enum CosemObjectType { 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 */ /* General messages */
P1_VERSION_OUTPUT(new OBISIdentifier(1, 3, 0, 2, 8, null), CosemString.INSTANCE), P1_VERSION_OUTPUT(new OBISIdentifier(1, 0, 2, 8), CosemString.INSTANCE),
P1_EMUCS_VERSION_OUTPUT(new OBISIdentifier(0, 0, 96, 1, 4, null), CosemString.INSTANCE), P1_EMUCS_VERSION_OUTPUT(new OBISIdentifier(0, 96, 1, 4), CosemString.INSTANCE),
P1_TIMESTAMP(new OBISIdentifier(0, 0, 1, 0, 0, null), new CosemDate("")), P1_TIMESTAMP(new OBISIdentifier(0, 1, 0, 0), new CosemDate("")),
P1_TEXT_CODE(new OBISIdentifier(0, 0, 96, 13, 1, null), CosemHexString.INSTANCE), P1_TEXT_CODE(new OBISIdentifier(0, 96, 13, 1), CosemHexString.INSTANCE),
P1_TEXT_STRING(new OBISIdentifier(0, 0, 96, 13, 0, null), CosemHexString.INSTANCE), P1_TEXT_STRING(new OBISIdentifier(0, 96, 13, 0), CosemHexString.INSTANCE),
P1_TEXT_STRING_LONG(new OBISIdentifier(0, 0, 96, 13, null, null), CosemHexString.INSTANCE), P1_TEXT_STRING_LONG(new OBISIdentifier(0, 96, 13, null), CosemHexString.INSTANCE),
/* Generic Meter Cosem Object types */ /* Generic Meter Cosem Object types */
METER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, null, 96, 1, 0, null), CosemHexString.INSTANCE), METER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, 96, 1, 0), CosemHexString.INSTANCE),
METER_DEVICE_TYPE(new OBISIdentifier(0, null, 24, 1, 0, null), CosemString.INSTANCE), METER_DEVICE_TYPE(new OBISIdentifier(0, 24, 1, 0), CosemString.INSTANCE),
METER_VALVE_SWITCH_POSITION(new OBISIdentifier(0, null, 24, 4, 0, null), CosemDecimal.INSTANCE), METER_VALVE_SWITCH_POSITION(new OBISIdentifier(0, 24, 4, 0), CosemDecimal.INSTANCE),
/* Electricity Meter */ /* Electricity Meter */
EMETER_EQUIPMENT_IDENTIFIER_V2_X(new OBISIdentifier(0, 0, 42, 0, 0, null), CosemString.INSTANCE), EMETER_EQUIPMENT_IDENTIFIER_V2_X(new OBISIdentifier(0, 42, 0, 0), CosemString.INSTANCE),
EMETER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, null, 96, 1, 1, null), CosemHexString.INSTANCE), EMETER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, 96, 1, 1), CosemHexString.INSTANCE),
EMETER_VALUE(new OBISIdentifier(0, null, 24, 2, 1, null), CosemDate.INSTANCE, CosemQuantity.KILO_WATT_HOUR), EMETER_VALUE(new OBISIdentifier(0, 24, 2, 1, true), CosemDate.INSTANCE, CosemQuantity.KILO_WATT_HOUR),
EMETER_DELIVERY_TARIFF0(new OBISIdentifier(1, null, 1, 8, 0, null), CosemQuantity.KILO_WATT_HOUR), EMETER_DELIVERY_TARIFF0(new OBISIdentifier(1, 1, 8, 0), CosemQuantity.KILO_WATT_HOUR),
EMETER_DELIVERY_TARIFF1(new OBISIdentifier(1, null, 1, 8, 1, null), CosemQuantity.KILO_WATT_HOUR), EMETER_DELIVERY_TARIFF1(new OBISIdentifier(1, 1, 8, 1), CosemQuantity.KILO_WATT_HOUR),
EMETER_DELIVERY_TARIFF2(new OBISIdentifier(1, null, 1, 8, 2, null), CosemQuantity.KILO_WATT_HOUR), EMETER_DELIVERY_TARIFF2(new OBISIdentifier(1, 1, 8, 2), CosemQuantity.KILO_WATT_HOUR),
EMETER_DELIVERY_TARIFF0_ANTIFRAUD(new OBISIdentifier(1, null, 15, 8, 0, null), 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, null, 15, 8, 1, null), 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, null, 15, 8, 2, null), CosemQuantity.KILO_WATT_HOUR), EMETER_DELIVERY_TARIFF2_ANTIFRAUD(new OBISIdentifier(1, 15, 8, 2), CosemQuantity.KILO_WATT_HOUR),
EMETER_PRODUCTION_TARIFF0(new OBISIdentifier(1, null, 2, 8, 0, null), CosemQuantity.KILO_WATT_HOUR), EMETER_PRODUCTION_TARIFF0(new OBISIdentifier(1, 2, 8, 0), CosemQuantity.KILO_WATT_HOUR),
EMETER_PRODUCTION_TARIFF1(new OBISIdentifier(1, null, 2, 8, 1, null), CosemQuantity.KILO_WATT_HOUR), EMETER_PRODUCTION_TARIFF1(new OBISIdentifier(1, 2, 8, 1), CosemQuantity.KILO_WATT_HOUR),
EMETER_PRODUCTION_TARIFF2(new OBISIdentifier(1, null, 2, 8, 2, null), CosemQuantity.KILO_WATT_HOUR), EMETER_PRODUCTION_TARIFF2(new OBISIdentifier(1, 2, 8, 2), CosemQuantity.KILO_WATT_HOUR),
EMETER_TARIFF_INDICATOR(new OBISIdentifier(0, null, 96, 14, 0, null), CosemString.INSTANCE), EMETER_TARIFF_INDICATOR(new OBISIdentifier(0, 96, 14, 0), CosemString.INSTANCE),
EMETER_ACTIVE_IMPORT_POWER(new OBISIdentifier(1, null, 15, 7, 0, null), CosemQuantity.WATT), EMETER_ACTIVE_IMPORT_POWER(new OBISIdentifier(1, 15, 7, 0), CosemQuantity.WATT),
EMETER_ACTUAL_DELIVERY(new OBISIdentifier(1, 0, 1, 7, 0, null), CosemQuantity.KILO_WATT), EMETER_ACTUAL_DELIVERY(new OBISIdentifier(1, 1, 7, 0), CosemQuantity.KILO_WATT),
EMETER_ACTUAL_PRODUCTION(new OBISIdentifier(1, 0, 2, 7, 0, null), CosemQuantity.KILO_WATT), EMETER_ACTUAL_PRODUCTION(new OBISIdentifier(1, 2, 7, 0), CosemQuantity.KILO_WATT),
EMETER_TRESHOLD_A_V2_1(new OBISIdentifier(1, 0, 17, 0, 0, null), CosemQuantity.AMPERE), EMETER_TRESHOLD_A_V2_1(new OBISIdentifier(1, 17, 0, 0), CosemQuantity.AMPERE),
EMETER_TRESHOLD_A(new OBISIdentifier(0, 0, 17, 0, 0, null, true), CosemQuantity.AMPERE), EMETER_TRESHOLD_A(new OBISIdentifier(0, 17, 0, 0, true), CosemQuantity.AMPERE),
EMETER_FUSE_THRESHOLD_A(new OBISIdentifier(1, 0, 31, 4, 0, null), CosemQuantity.AMPERE), EMETER_FUSE_THRESHOLD_A(new OBISIdentifier(1, 31, 4, 0), CosemQuantity.AMPERE),
EMETER_TRESHOLD_KWH(new OBISIdentifier(0, 0, 17, 0, 0, null, true), CosemQuantity.KILO_WATT), EMETER_TRESHOLD_KW(new OBISIdentifier(0, 17, 0, 0, true), CosemQuantity.KILO_WATT),
EMETER_SWITCH_POSITION_V2_1(new OBISIdentifier(1, 0, 96, 3, 10, null), CosemDecimal.INSTANCE), EMETER_SWITCH_POSITION_V2_1(new OBISIdentifier(1, 96, 3, 10), CosemDecimal.INSTANCE),
EMETER_SWITCH_POSITION(new OBISIdentifier(0, 0, 96, 3, 10, null), CosemDecimal.INSTANCE), EMETER_SWITCH_POSITION(new OBISIdentifier(0, 96, 3, 10), CosemDecimal.INSTANCE),
EMETER_POWER_FAILURES(new OBISIdentifier(0, 0, 96, 7, 21, null), CosemDecimal.INSTANCE), EMETER_POWER_FAILURES(new OBISIdentifier(0, 96, 7, 21), CosemDecimal.INSTANCE),
EMETER_LONG_POWER_FAILURES(new OBISIdentifier(0, 0, 96, 7, 9, null), CosemDecimal.INSTANCE), EMETER_LONG_POWER_FAILURES(new OBISIdentifier(0, 96, 7, 9), CosemDecimal.INSTANCE),
EMETER_POWER_FAILURE_LOG(new OBISIdentifier(1, 0, 99, 97, 0, null), 2, new CosemDecimal("entries"), EMETER_POWER_FAILURE_LOG(new OBISIdentifier(1, 99, 97, 0), 2, new CosemDecimal("entries"),
new CosemString("obisId"), new CosemString("obisId"),
/* Next 2 descriptors are repeating */ /* Next 2 descriptors are repeating */
CosemDate.INSTANCE, new CosemQuantity<>(Units.SECOND, "duration")), CosemDate.INSTANCE, new CosemQuantity<>(Units.SECOND, "duration")),
EMETER_VOLTAGE_SAGS_L1(new OBISIdentifier(1, 0, 32, 32, 0, null), CosemDecimal.INSTANCE), EMETER_VOLTAGE_SAGS_L1(new OBISIdentifier(1, 32, 32, 0), CosemDecimal.INSTANCE),
EMETER_VOLTAGE_SAGS_L2(new OBISIdentifier(1, 0, 52, 32, 0, null), CosemDecimal.INSTANCE), EMETER_VOLTAGE_SAGS_L2(new OBISIdentifier(1, 52, 32, 0), CosemDecimal.INSTANCE),
EMETER_VOLTAGE_SAGS_L3(new OBISIdentifier(1, 0, 72, 32, 0, null), CosemDecimal.INSTANCE), EMETER_VOLTAGE_SAGS_L3(new OBISIdentifier(1, 72, 32, 0), CosemDecimal.INSTANCE),
EMETER_VOLTAGE_SWELLS_L1(new OBISIdentifier(1, 0, 32, 36, 0, null), CosemDecimal.INSTANCE), EMETER_VOLTAGE_SWELLS_L1(new OBISIdentifier(1, 32, 36, 0), CosemDecimal.INSTANCE),
EMETER_VOLTAGE_SWELLS_L2(new OBISIdentifier(1, 0, 52, 36, 0, null), CosemDecimal.INSTANCE), EMETER_VOLTAGE_SWELLS_L2(new OBISIdentifier(1, 52, 36, 0), CosemDecimal.INSTANCE),
EMETER_VOLTAGE_SWELLS_L3(new OBISIdentifier(1, 0, 72, 36, 0, null), CosemDecimal.INSTANCE), EMETER_VOLTAGE_SWELLS_L3(new OBISIdentifier(1, 72, 36, 0), CosemDecimal.INSTANCE),
EMETER_INSTANT_CURRENT_L1(new OBISIdentifier(1, 0, 31, 7, 0, null), CosemQuantity.AMPERE), EMETER_INSTANT_CURRENT_L1(new OBISIdentifier(1, 31, 7, 0), CosemQuantity.AMPERE),
EMETER_INSTANT_CURRENT_L2(new OBISIdentifier(1, 0, 51, 7, 0, null), CosemQuantity.AMPERE), EMETER_INSTANT_CURRENT_L2(new OBISIdentifier(1, 51, 7, 0), CosemQuantity.AMPERE),
EMETER_INSTANT_CURRENT_L3(new OBISIdentifier(1, 0, 71, 7, 0, null), CosemQuantity.AMPERE), EMETER_INSTANT_CURRENT_L3(new OBISIdentifier(1, 71, 7, 0), CosemQuantity.AMPERE),
EMETER_INSTANT_POWER_DELIVERY_L1(new OBISIdentifier(1, 0, 21, 7, 0, null), CosemQuantity.KILO_WATT), EMETER_INSTANT_POWER_DELIVERY_L1(new OBISIdentifier(1, 21, 7, 0), CosemQuantity.KILO_WATT),
EMETER_INSTANT_POWER_DELIVERY_L2(new OBISIdentifier(1, 0, 41, 7, 0, null), 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, 0, 61, 7, 0, null), 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, 0, 22, 7, 0, null), 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, 0, 42, 7, 0, null), 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, 0, 62, 7, 0, null), CosemQuantity.KILO_WATT), EMETER_INSTANT_POWER_PRODUCTION_L3(new OBISIdentifier(1, 62, 7, 0), CosemQuantity.KILO_WATT),
EMETER_INSTANT_VOLTAGE_L1(new OBISIdentifier(1, 0, 32, 7, 0, null), CosemQuantity.VOLT), EMETER_INSTANT_VOLTAGE_L1(new OBISIdentifier(1, 32, 7, 0), CosemQuantity.VOLT),
EMETER_INSTANT_VOLTAGE_L2(new OBISIdentifier(1, 0, 52, 7, 0, null), CosemQuantity.VOLT), EMETER_INSTANT_VOLTAGE_L2(new OBISIdentifier(1, 52, 7, 0), CosemQuantity.VOLT),
EMETER_INSTANT_VOLTAGE_L3(new OBISIdentifier(1, 0, 72, 7, 0, null), CosemQuantity.VOLT), EMETER_INSTANT_VOLTAGE_L3(new OBISIdentifier(1, 72, 7, 0), CosemQuantity.VOLT),
/* Gas Meter */ /* Gas Meter */
GMETER_EQUIPMENT_IDENTIFIER_V2(new OBISIdentifier(7, 0, 0, 0, 0, null), CosemString.INSTANCE), GMETER_EQUIPMENT_IDENTIFIER_V2(new OBISIdentifier(7, 0, 0, 0), CosemString.INSTANCE),
GMETER_24H_DELIVERY_V2(new OBISIdentifier(7, 0, 23, 1, 0, null), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE), GMETER_24H_DELIVERY_V2(new OBISIdentifier(7, 23, 1, 0), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE),
GMETER_24H_DELIVERY_COMPENSATED_V2(new OBISIdentifier(7, 0, 23, 2, 0, null), CosemQuantity.CUBIC_METRE, GMETER_24H_DELIVERY_COMPENSATED_V2(new OBISIdentifier(7, 23, 2, 0), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE),
CosemDate.INSTANCE), GMETER_LAST_VALUE(new OBISIdentifier(0, 24, 2, 3), CosemDate.INSTANCE, CosemQuantity.CUBIC_METRE),
GMETER_LAST_VALUE(new OBISIdentifier(0, null, 24, 2, 3, null), CosemDate.INSTANCE, CosemQuantity.CUBIC_METRE), GMETER_VALUE_V3(new OBISIdentifier(0, 24, 3, 0, true), CosemDate.INSTANCE, // Time stamp off the reading
GMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null), CosemDate.INSTANCE, // Time stamp off the reading
new CosemString("val1"), // Specification is not clear what this value is new CosemString("val1"), // Specification is not clear what this value is
new CosemDecimal("val2"), // 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 CosemDecimal("val3"), // Specification is not clear what this value is
new CosemString("obisId"), // String containing a OBIS Identifier new CosemString("obisId"), // String containing a OBIS Identifier
new CosemString("unit"), // String containing the type (m3) new CosemString("unit"), // String containing the type (m3)
CosemDecimal.INSTANCE), CosemDecimal.INSTANCE),
GMETER_VALVE_POSITION_V2_1(new OBISIdentifier(7, 0, 96, 3, 10, null), CosemDecimal.INSTANCE), GMETER_VALVE_POSITION_V2_1(new OBISIdentifier(7, 96, 3, 10), CosemDecimal.INSTANCE),
GMETER_VALVE_POSITION_V2_2(new OBISIdentifier(7, 0, 24, 4, 0, null), CosemDecimal.INSTANCE), GMETER_VALVE_POSITION_V2_2(new OBISIdentifier(7, 24, 4, 0), CosemDecimal.INSTANCE),
/* Heating Meter */ /* Heating Meter */
HMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(5, 0, 0, 0, 0, null), CosemString.INSTANCE), HMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(5, 0, 0, 0), CosemString.INSTANCE),
HMETER_VALUE_V2(new OBISIdentifier(5, 0, 1, 0, 0, null), CosemQuantity.GIGA_JOULE, CosemDate.INSTANCE), HMETER_VALUE_V2(new OBISIdentifier(5, 1, 0, 0), CosemQuantity.GIGA_JOULE, CosemDate.INSTANCE),
/* Cooling Meter */ /* Cooling Meter */
CMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(6, 0, 0, 0, 0, null), CosemString.INSTANCE), CMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(6, 0, 0, 0), CosemString.INSTANCE),
CMETER_VALUE_V2(new OBISIdentifier(6, 0, 1, 0, 0, null), CosemQuantity.GIGA_JOULE, CosemDate.INSTANCE), CMETER_VALUE_V2(new OBISIdentifier(6, 1, 0, 0), CosemQuantity.GIGA_JOULE, CosemDate.INSTANCE),
/* Water Meter */ /* Water Meter */
WMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(8, 0, 0, 0, 0, null), CosemString.INSTANCE), WMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(8, 0, 0, 0), CosemString.INSTANCE),
WMETER_VALUE_V2(new OBISIdentifier(8, 0, 1, 0, 0, null), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE), WMETER_VALUE_V2(new OBISIdentifier(8, 1, 0, 0), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE),
WMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null), CosemQuantity.CUBIC_METRE), WMETER_VALUE_V3(new OBISIdentifier(0, 24, 3, 0, true), CosemQuantity.CUBIC_METRE),
/* M3 Meter (Gas, Water) */ /* 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) */ /* GJ Meter (Heating, Cooling) */
GJMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null), CosemQuantity.GIGA_JOULE), GJMETER_VALUE_V3(new OBISIdentifier(0, 24, 3, 0, true), CosemQuantity.GIGA_JOULE),
GJMETER_VALUE_V4(new OBISIdentifier(0, null, 24, 2, 1, null), CosemDate.INSTANCE, CosemQuantity.GIGA_JOULE), GJMETER_VALUE_V4(new OBISIdentifier(0, 24, 2, 1, true), CosemDate.INSTANCE, CosemQuantity.GIGA_JOULE),
/* Generic Meter (DSMR v3 only) */ /* 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 */ /* Additional Luxembourgish Smarty Electricity */
EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_Q(new OBISIdentifier(1, null, 3, 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_EXPORTED_ENERGY_REGISTER_Q(new OBISIdentifier(1, null, 4, 8, 0, null), 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. // 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_DELIVERY(new OBISIdentifier(1, 3, 7, 0), CosemDecimal.INSTANCE_WITH_UNITS),
EMETER_ACTUAL_REACTIVE_PRODUCTION(new OBISIdentifier(1, 0, 4, 7, 0, null), 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, 0, 17, 0, 0, null, true), 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, 0, 23, 7, 0, null), CosemQuantity.KILO_VAR), EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L1(new OBISIdentifier(1, 23, 7, 0), 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_L2(new OBISIdentifier(1, 43, 7, 0), CosemQuantity.KILO_VAR),
EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L3(new OBISIdentifier(1, 0, 63, 7, 0, null), 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, 0, 24, 7, 0, null), 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, 0, 44, 7, 0, null), 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, 0, 64, 7, 0, null), CosemQuantity.KILO_VAR); EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L3(new OBISIdentifier(1, 64, 7, 0), CosemQuantity.KILO_VAR);
/** OBIS reduced identifier */ /** OBIS reduced identifier */
public final OBISIdentifier obisId; public final OBISIdentifier obisId;
@ -182,9 +186,9 @@ public enum CosemObjectType {
this.obisId = obisId; this.obisId = obisId;
if (nrOfRepeatingDescriptors == 0) { if (nrOfRepeatingDescriptors == 0) {
this.descriptors = Arrays.asList(descriptors); this.descriptors = Arrays.asList(descriptors);
this.repeatingDescriptors = Collections.emptyList(); this.repeatingDescriptors = List.of();
} else { } else {
List<CosemValueDescriptor<?>> allDescriptors = Arrays.asList(descriptors); final List<CosemValueDescriptor<?>> allDescriptors = List.of(descriptors);
/* /*
* The last nrOfRepeatingDescriptors CosemValueDescriptor will go into the repeatingDescriptor list. * The last nrOfRepeatingDescriptors CosemValueDescriptor will go into the repeatingDescriptor list.
@ -207,19 +211,19 @@ public enum CosemObjectType {
* @param idx the CosemValueDescriptor to return * @param idx the CosemValueDescriptor to return
* @return the CosemValueDescriptor or null if not found. * @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()) { if (idx >= descriptors.size() && !repeatingDescriptors.isEmpty()) {
/* We have a repeating list, find the correct repeating descriptor */ /* 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 */ /* 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); return new SimpleEntry<>(descriptor.getChannelId() + repeatCount, descriptor);
} else if (idx < descriptors.size()) { } else if (idx < descriptors.size()) {
CosemValueDescriptor<?> descriptor = descriptors.get(idx); final CosemValueDescriptor<?> descriptor = descriptors.get(idx);
return new SimpleEntry<>(descriptor.getChannelId(), descriptor); return new SimpleEntry<>(descriptor.getChannelId(), descriptor);
} else { } 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> WATT = new CosemQuantity<>(Units.WATT);
public static final CosemQuantity<Power> KILO_VAR = new CosemQuantity<>(Units.KILOVAR); 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<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}. * 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 @Override
protected QuantityType<Q> getStateValue(String cosemValue) throws ParseException { protected QuantityType<Q> getStateValue(String cosemValue) throws ParseException {
try { 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); throw new ParseException("Failed to parse value '" + cosemValue + "' as unit " + unit, 0);
} }
return qt; return qt;
} catch (IllegalArgumentException nfe) { } catch (final IllegalArgumentException nfe) {
throw new ParseException("Failed to parse value '" + cosemValue + "' as unit " + unit, 0); 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. * We also support unit that do not follow the exact case.
*/ */
private String prepare(String cosemValue) { 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()) { if (!matcher.find()) {
return cosemValue; return cosemValue;
} }
@ -131,7 +133,7 @@ class CosemQuantity<Q extends @Nullable Quantity<Q>> extends CosemValueDescripto
try { try {
Integer.parseInt(matcher.group(2)); Integer.parseInt(matcher.group(2));
return cosemValue; return cosemValue;
} catch (NumberFormatException e) { } catch (final NumberFormatException e) {
return matcher.group(1) + ' ' + matcher.group(2); return matcher.group(1) + ' ' + matcher.group(2);
} }
} }

View File

@ -24,12 +24,12 @@ import org.eclipse.jdt.annotation.Nullable;
* Class representing an OBISIdentifier * Class representing an OBISIdentifier
* *
* @author M. Volaart - Initial contribution * @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 @NonNullByDefault
public class OBISIdentifier { public class OBISIdentifier {
/** /**
* String representing a-b:c.d.e.f OBIS ID * String representing a-channel:c.d.e.f OBIS ID
*/ */
private static final String OBISID_REGEX = "((\\d+)\\-)?((\\d+):)?((\\d+)\\.)(\\d+)(\\.(\\d+))?(.(\\d+))?"; private static final 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); private static final Pattern OBIS_ID_PATTERN = Pattern.compile(OBISID_REGEX);
/* the six individual group values of the OBIS ID */ /* the six individual group values of the OBIS ID */
private int groupA; private final int groupA;
private @Nullable Integer groupB; private final @Nullable Integer channel;
private int groupC; private final int groupC;
private int groupD; private final int groupD;
private @Nullable Integer groupE; private final @Nullable Integer groupE;
private @Nullable Integer groupF; private final @Nullable Integer groupF;
private boolean conflict; 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 groupA A value
* @param groupB B value
* @param groupC C value * @param groupC C value
* @param groupD D value * @param groupD D value
* @param groupE E value * @param groupE E value
* @param groupF F value
*/ */
public OBISIdentifier(int groupA, @Nullable Integer groupB, int groupC, int groupD, @Nullable Integer groupE, public OBISIdentifier(final int groupA, final int groupC, final int groupD, @Nullable final Integer groupE) {
@Nullable Integer groupF) { this(groupA, groupC, groupD, groupE, false);
this(groupA, groupB, groupC, groupD, groupE, groupF, 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 groupA A value
* @param groupB B value
* @param groupC C value * @param groupC C value
* @param groupD D value * @param groupD D value
* @param groupE E value * @param groupE E value
* @param groupF F value
* @param conflict if true indicates this OBIS Identifier is used for different types of data. * @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, public OBISIdentifier(final int groupA, final int groupC, final int groupD, @Nullable final Integer groupE,
@Nullable Integer groupF, boolean conflict) { final boolean conflict) {
this.groupA = groupA; this.groupA = groupA;
this.groupB = groupB; this.channel = null;
this.groupC = groupC; this.groupC = groupC;
this.groupD = groupD; this.groupD = groupD;
this.groupE = groupE; this.groupE = groupE;
this.groupF = groupF; this.groupF = null;
this.conflict = conflict; this.conflict = conflict;
} }
@ -91,33 +86,25 @@ public class OBISIdentifier {
* @param obisIDString the OBIS String ID * @param obisIDString the OBIS String ID
* @throws ParseException if obisIDString is not a valid OBIS Identifier * @throws ParseException if obisIDString is not a valid OBIS Identifier
*/ */
public OBISIdentifier(String obisIDString) throws ParseException { public OBISIdentifier(final String obisIDString) throws ParseException {
Matcher m = OBIS_ID_PATTERN.matcher(obisIDString); final Matcher m = OBIS_ID_PATTERN.matcher(obisIDString);
if (m.matches()) { if (m.matches()) {
// Optional value A // Optional value A
if (m.group(2) != null) { this.groupA = m.group(2) == null ? null : Integer.parseInt(m.group(2));
this.groupA = Integer.parseInt(m.group(2));
}
// Optional value B // Optional value B
if (m.group(4) != null) { this.channel = m.group(4) == null ? null : Integer.valueOf(m.group(4));
this.groupB = Integer.valueOf(m.group(4));
}
// Required value C & D // Required value C & D
this.groupC = Integer.parseInt(m.group(6)); this.groupC = Integer.parseInt(m.group(6));
this.groupD = Integer.parseInt(m.group(7)); this.groupD = Integer.parseInt(m.group(7));
// Optional value E // Optional value E
if (m.group(9) != null) { this.groupE = m.group(9) == null ? null : Integer.valueOf(m.group(9));
this.groupE = Integer.valueOf(m.group(9));
}
// Optional value F // Optional value F
if (m.group(11) != null) { this.groupF = m.group(11) == null ? null : Integer.valueOf(m.group(11));
this.groupF = Integer.valueOf(m.group(11));
}
} else { } else {
throw new ParseException("Invalid OBIS identifier:" + obisIDString, 0); 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() { public @Nullable Integer getChannel() {
return groupB; return channel;
} }
/** /**
@ -171,7 +158,7 @@ public class OBISIdentifier {
@Override @Override
public String toString() { public String toString() {
return groupA + "-" + (groupB == null ? "" : (groupB + ":")) + groupC + "." + groupD return groupA + "-" + (channel == null ? "" : (channel + ":")) + groupC + "." + groupD
+ (groupE == null ? "" : ("." + groupE)) + (groupF == null ? "" : ("*" + groupF)); + (groupE == null ? "" : ("." + groupE)) + (groupF == null ? "" : ("*" + groupF));
} }
@ -184,7 +171,7 @@ public class OBISIdentifier {
* @return true if both OBISIdentifiers match, false otherwise * @return true if both OBISIdentifiers match, false otherwise
*/ */
@Override @Override
public boolean equals(@Nullable Object other) { public boolean equals(@Nullable final Object other) {
OBISIdentifier o; OBISIdentifier o;
if (other != null && other instanceof OBISIdentifier) { if (other != null && other instanceof OBISIdentifier) {
o = (OBISIdentifier) other; o = (OBISIdentifier) other;
@ -194,9 +181,9 @@ public class OBISIdentifier {
boolean result = true; boolean result = true;
result &= groupA == o.groupA; result &= groupA == o.groupA;
if (groupB != null && o.groupB != null) { if (channel != null && o.channel != null) {
result &= (groupB.equals(o.groupB)); result &= (channel.equals(o.channel));
} else if (!(groupB == null && o.groupB == null)) { } else if (!(channel == null && o.channel == null)) {
result = false; result = false;
} }
result &= groupC == o.groupC; result &= groupC == o.groupC;
@ -222,12 +209,12 @@ public class OBISIdentifier {
* *
* @return true if identifiers match fully or against a wildcard, false otherwise * @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; boolean result = true;
result &= groupA == o.groupA; result &= groupA == o.groupA;
if (groupB != null && o.groupB != null) { if (channel != null && o.channel != null) {
result &= (groupB.equals(o.groupB)); result &= (channel.equals(o.channel));
} }
result &= groupC == o.groupC; result &= groupC == o.groupC;
result &= groupD == o.groupD; result &= groupD == o.groupD;
@ -243,39 +230,25 @@ public class OBISIdentifier {
@Override @Override
public int hashCode() { 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)); (groupF != null ? groupF : 0));
} }
/** /**
* Returns an reduced OBIS Identifier. This means group F is set to null * Returns an reduced OBIS Identifier.
* (.i.e. not applicable)
* *
* @return reduced OBIS Identifier * @return reduced OBIS Identifier
*/ */
public OBISIdentifier getReducedOBISIdentifier() { 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 * Returns an reduced OBIS Identifier with group E set to null (.i.e. not applicable)
* (.i.e. not applicable)
* *
* @return reduced OBIS Identifier * @return reduced OBIS Identifier
*/ */
public OBISIdentifier getReducedOBISIdentifierGroupE() { public OBISIdentifier getReducedOBISIdentifierGroupE() {
return new OBISIdentifier(groupA, groupB, groupC, groupD, null, null); return new OBISIdentifier(groupA, groupC, groupD, 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;
} }
} }

View File

@ -128,13 +128,23 @@ public class P1TelegramParser implements TelegramParser {
*/ */
private final P1TelegramListener telegramListener; private final P1TelegramListener telegramListener;
/**
* Enable in tests. Will throw an exception on CRC error.
*/
private final boolean test;
/** /**
* Creates a new P1TelegramParser * Creates a new P1TelegramParser
* *
* @param telegramListener * @param telegramListener
*/ */
public P1TelegramParser(P1TelegramListener telegramListener) { public P1TelegramParser(P1TelegramListener telegramListener) {
this(telegramListener, false);
}
public P1TelegramParser(P1TelegramListener telegramListener, boolean test) {
this.telegramListener = telegramListener; this.telegramListener = telegramListener;
this.test = test;
factory = new CosemObjectFactory(); factory = new CosemObjectFactory();
state = State.WAIT_FOR_START; state = State.WAIT_FOR_START;
@ -151,7 +161,7 @@ public class P1TelegramParser implements TelegramParser {
@Override @Override
public void parse(byte[] data, int length) { public void parse(byte[] data, int length) {
if (lenientMode || logger.isTraceEnabled()) { 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) { if (lenientMode) {
rawData.append(rawBlock); rawData.append(rawBlock);
@ -161,7 +171,7 @@ public class P1TelegramParser implements TelegramParser {
} }
} }
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
char c = (char) data[i]; final char c = (char) data[i];
switch (state) { switch (state) {
case WAIT_FOR_START: case WAIT_FOR_START:
@ -245,22 +255,7 @@ public class P1TelegramParser implements TelegramParser {
logger.trace("telegramState {}, crcValue to check 0x{}", telegramState, crcValue); logger.trace("telegramState {}, crcValue to check 0x{}", telegramState, crcValue);
// Only perform CRC check if telegram is still ok // Only perform CRC check if telegram is still ok
if (telegramState == TelegramState.OK && crcValue.length() > 0) { if (telegramState == TelegramState.OK && crcValue.length() > 0) {
if (Pattern.matches(CRC_PATTERN, crcValue)) { telegramState = checkCRC(telegramState);
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;
}
} }
telegramListener.telegramReceived(constructTelegram()); telegramListener.telegramReceived(constructTelegram());
reset(); reset();
@ -280,6 +275,34 @@ public class P1TelegramParser implements TelegramParser {
logger.trace("State after parsing: {}", state); 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() { private P1Telegram constructTelegram() {
final List<CosemObject> cosemObjectsCopy = new ArrayList<>(cosemObjects); 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 * Store the current CosemObject in the list of received cosem Objects
*/ */
private void storeCurrentCosemObject() { private void storeCurrentCosemObject() {
String obisIdString = obisId.toString(); final String obisIdString = obisId.toString();
if (!obisIdString.isEmpty()) { if (!obisIdString.isEmpty()) {
final String obisValueString = obisValue.toString(); final String obisValueString = obisValue.toString();
CosemObject cosemObject = factory.getCosemObject(obisIdString, obisValueString); final CosemObject cosemObject = factory.getCosemObject(obisIdString, obisValueString);
if (cosemObject == null) { if (cosemObject == null) {
if (lenientMode) { if (lenientMode) {

View File

@ -13,10 +13,13 @@
package org.openhab.binding.dsmr.internal.discovery; package org.openhab.binding.dsmr.internal.discovery;
import java.util.AbstractMap.SimpleEntry; import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject; import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
@ -46,19 +49,15 @@ class DSMRMeterDetector {
* @param telegram The received telegram * @param telegram The received telegram
* @return collection of detected {@link DSMRMeterDescriptor} * @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<DSMRMeterKind, DSMRMeterDescriptor> detectedMeters = new HashMap<>();
final Map<CosemObjectType, CosemObject> availableCosemObjects = new HashMap<>(); final List<CosemObject> availableCosemObjects = List.copyOf(telegram.getCosemObjects());
final Map<CosemObjectType, CosemObject> undetectedCosemObjects = new HashMap<>(); final List<CosemObject> undetectedCosemObjects = new ArrayList<>(telegram.getCosemObjects());
// 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);
// Find compatible meters // Find compatible meters
for (DSMRMeterType meterType : DSMRMeterType.values()) { for (DSMRMeterType meterType : DSMRMeterType.values()) {
logger.trace("Trying if meter type {} is compatible", meterType); logger.trace("Trying if meter type {} is compatible", meterType);
final DSMRMeterDescriptor meterDescriptor = meterType.isCompatible(availableCosemObjects); final DSMRMeterDescriptor meterDescriptor = meterType.findCompatible(availableCosemObjects);
if (meterDescriptor == null) { if (meterDescriptor == null) {
logger.trace("Meter type {} is not compatible", meterType); logger.trace("Meter type {} is not compatible", meterType);
@ -74,7 +73,9 @@ class DSMRMeterDetector {
logger.debug("New compatible meter: {}", meterDescriptor); logger.debug("New compatible meter: {}", meterDescriptor);
detectedMeters.put(meterType.meterKind, meterDescriptor); detectedMeters.put(meterType.meterKind, meterDescriptor);
for (CosemObjectType cot : meterDescriptor.getMeterType().supportedCosemObjects) { 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.Collection;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@ -101,7 +100,7 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Detect meters from #{} objects", telegram.getCosemObjects().size()); 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); .detectMeters(telegram);
verifyUnregisteredCosemObjects(telegram, detectedMeters.getValue()); verifyUnregisteredCosemObjects(telegram, detectedMeters.getValue());
validateConfiguredMeters(dsmrBridgeHandler.getThing().getThings(), validateConfiguredMeters(dsmrBridgeHandler.getThing().getThings(),
@ -109,17 +108,16 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
detectedMeters.getKey().forEach(m -> meterDiscovered(m, dsmrBridgeHandler.getThing().getUID())); detectedMeters.getKey().forEach(m -> meterDiscovered(m, dsmrBridgeHandler.getThing().getUID()));
} }
protected void verifyUnregisteredCosemObjects(P1Telegram telegram, protected void verifyUnregisteredCosemObjects(P1Telegram telegram, List<CosemObject> list) {
Map<CosemObjectType, CosemObject> undetectedCosemObjects) { if (!list.isEmpty()) {
if (!undetectedCosemObjects.isEmpty()) { if (list.stream()
if (undetectedCosemObjects.entrySet().stream() .anyMatch(e -> e.getType() == CosemObjectType.METER_EQUIPMENT_IDENTIFIER
.anyMatch(e -> e.getKey() == CosemObjectType.METER_EQUIPMENT_IDENTIFIER && e.getCosemValues().entrySet().stream().anyMatch(
&& e.getValue().getCosemValues().entrySet().stream().anyMatch(
cv -> cv.getValue() instanceof StringType && cv.getValue().toString().isEmpty()))) { cv -> cv.getValue() instanceof StringType && cv.getValue().toString().isEmpty()))) {
// Unregistered meter detected. log to the user. // Unregistered meter detected. log to the user.
reportUnregisteredMeters(); reportUnregisteredMeters();
} else { } else {
reportUnrecognizedCosemObjects(undetectedCosemObjects); reportUnrecognizedCosemObjects(list);
logger.info("There are unrecognized cosem values in the data received from the meter," 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: {}", + " which means some meters might not be detected. Please report your raw data as reference: {}",
telegram.getRawTelegram()); 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. * 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) { protected void reportUnrecognizedCosemObjects(List<CosemObject> list) {
unidentifiedCosemObjects list.forEach(c -> logger.info("Unrecognized cosem object '{}' found in the data: {}", c.getType(), c));
.forEach((k, v) -> logger.info("Unrecognized cosem object '{}' found in the data: {}", k, v));
} }
/** /**

View File

@ -220,7 +220,7 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
*/ */
private void alive() { private void alive() {
logger.trace("Bridge alive check with #{} children.", getThing().getThings().size()); logger.trace("Bridge alive check with #{} children.", getThing().getThings().size());
long deltaLastReceived = System.nanoTime() - telegramReceivedTimeNanos; final long deltaLastReceived = System.nanoTime() - telegramReceivedTimeNanos;
if (deltaLastReceived > receivedTimeoutNanos) { if (deltaLastReceived > receivedTimeoutNanos) {
logger.debug("No data received for {} seconds, restarting port if possible.", 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. * @param telegram received meter values.
*/ */
private void meterValueReceived(P1Telegram telegram) { private void meterValueReceived(P1Telegram telegram) {
updateStatus(ThingStatus.ONLINE); if (isInitialized() && getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
getThing().getThings().forEach(child -> { getThing().getThings().forEach(child -> {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Update child:{} with {} objects", child.getThingTypeUID().getId(), logger.trace("Update child:{} with {} objects", child.getThingTypeUID().getId(),
telegram.getCosemObjects().size()); telegram.getCosemObjects().size());
} }
DSMRMeterHandler dsmrMeterHandler = (DSMRMeterHandler) child.getHandler(); final DSMRMeterHandler dsmrMeterHandler = (DSMRMeterHandler) child.getHandler();
if (dsmrMeterHandler instanceof DSMRMeterHandler) { if (dsmrMeterHandler instanceof DSMRMeterHandler) {
dsmrMeterHandler.telegramReceived(telegram); 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.device.p1telegram.P1TelegramListener;
import org.openhab.binding.dsmr.internal.meter.DSMRMeter; import org.openhab.binding.dsmr.internal.meter.DSMRMeter;
import org.openhab.binding.dsmr.internal.meter.DSMRMeterConfiguration; 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.DSMRMeterDescriptor;
import org.openhab.binding.dsmr.internal.meter.DSMRMeterType; import org.openhab.binding.dsmr.internal.meter.DSMRMeterType;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
@ -66,6 +67,11 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
*/ */
private @NonNullByDefault({}) ScheduledFuture<?> meterWatchdog; 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. * Creates a new MeterHandler for the given Thing.
* *
@ -106,7 +112,8 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
return; return;
} }
DSMRMeterConfiguration meterConfig = getConfigAs(DSMRMeterConfiguration.class); 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); meter = new DSMRMeter(meterDescriptor);
meterWatchdog = scheduler.scheduleWithFixedDelay(this::updateState, meterConfig.refresh, meterConfig.refresh, meterWatchdog = scheduler.scheduleWithFixedDelay(this::updateState, meterConfig.refresh, meterConfig.refresh,
TimeUnit.SECONDS); TimeUnit.SECONDS);
@ -163,7 +170,7 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
if (localMeter == null) { if (localMeter == null) {
return; return;
} }
List<CosemObject> filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects()); List<CosemObject> filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects(), channel);
if (filteredValues.isEmpty()) { if (filteredValues.isEmpty()) {
if (getThing().getStatus() == ThingStatus.ONLINE) { if (getThing().getStatus() == ThingStatus.ONLINE) {

View File

@ -56,9 +56,9 @@ public class DSMRMeter {
for (CosemObjectType msgType : meterDescriptor.getMeterType().supportedCosemObjects) { for (CosemObjectType msgType : meterDescriptor.getMeterType().supportedCosemObjects) {
OBISIdentifier obisId = msgType.obisId; OBISIdentifier obisId = msgType.obisId;
if (msgType.obisId.getGroupB() == null) { if (msgType.obisId.getChannel() == null) {
supportedIdentifiers.add(new OBISIdentifier(obisId.getGroupA(), meterDescriptor.getChannel(), supportedIdentifiers.add(new OBISIdentifier(obisId.getGroupA(), obisId.getGroupC(), obisId.getGroupD(),
obisId.getGroupC(), obisId.getGroupD(), obisId.getGroupE(), msgType.obisId.getGroupF())); obisId.getGroupE()));
} else { } else {
supportedIdentifiers.add(msgType.obisId); 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 * @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 * @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); logger.trace("supported identifiers: {}, searching for objects {}", supportedIdentifiers, cosemObjects);
List<CosemObject> filteredValues = cosemObjects.stream() List<CosemObject> filteredValues = cosemObjects.stream()
.filter(cosemObject -> supportedIdentifiers .filter(cosemObject -> (DSMRMeterConstants.UNKNOWN_CHANNEL == channel
.contains(cosemObject.getObisIdentifier().getReducedOBISIdentifier())) || cosemObject.getObisIdentifier().getChannel() == channel)
&& supportedIdentifiers.contains(cosemObject.getObisIdentifier().getReducedOBISIdentifier()))
.collect(Collectors.toList()); .collect(Collectors.toList());
return filteredValues; return filteredValues;
} }

View File

@ -44,7 +44,6 @@ public class DSMRMeterDescriptor {
* *
* @param meterType The meter type * @param meterType The meter type
* @param channel The M-Bus channel this meter is connected to * @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) { public DSMRMeterDescriptor(DSMRMeterType meterType, int channel) {
this.meterType = meterType; this.meterType = meterType;

View File

@ -12,14 +12,17 @@
*/ */
package org.openhab.binding.dsmr.internal.meter; package org.openhab.binding.dsmr.internal.meter;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* This class describes the kind of meters the binding supports * This class describes the kind of meters the binding supports
* *
* @author M. Volaart - Initial contribution * @author M. Volaart - Initial contribution
*/ */
@NonNullByDefault
public enum DSMRMeterKind { public enum DSMRMeterKind {
INVALID, INVALID,
DEVICE, DEVICE(false),
MAIN_ELECTRICITY, MAIN_ELECTRICITY,
GAS, GAS,
HEATING, HEATING,
@ -31,6 +34,20 @@ public enum DSMRMeterKind {
SLAVE_ELECTRICITY1, SLAVE_ELECTRICITY1,
SLAVE_ELECTRICITY2; 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. * @return Returns the i18n label key for this meter.
*/ */

View File

@ -13,10 +13,18 @@
package org.openhab.binding.dsmr.internal.meter; package org.openhab.binding.dsmr.internal.meter;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors; 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.DSMRBindingConstants;
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject; 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.cosem.CosemObjectType;
@ -29,6 +37,7 @@ import org.slf4j.LoggerFactory;
* *
* @author M. Volaart - Initial contribution * @author M. Volaart - Initial contribution
*/ */
@NonNullByDefault
public enum DSMRMeterType { public enum DSMRMeterType {
// Don't auto format the enum list. For readability the format for the enum is: // Don't auto format the enum list. For readability the format for the enum is:
// First line parameters; DSMRMeterKind and CosemObjectType (identification object type) // 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_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR,
CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION}, CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION},
new CosemObjectType[] { new CosemObjectType[] {
CosemObjectType.EMETER_TRESHOLD_A, CosemObjectType.EMETER_TRESHOLD_KWH, CosemObjectType.EMETER_TRESHOLD_A, CosemObjectType.EMETER_TRESHOLD_KW,
CosemObjectType.EMETER_SWITCH_POSITION}), CosemObjectType.EMETER_SWITCH_POSITION}),
/** DSMR V3.0 Gas meter */ /** DSMR V3.0 Gas meter */
@ -175,7 +184,7 @@ public enum DSMRMeterType {
CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER, CosemObjectType.EMETER_DELIVERY_TARIFF1, CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER, CosemObjectType.EMETER_DELIVERY_TARIFF1,
CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1, CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1,
CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR, 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_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION,
CosemObjectType.EMETER_POWER_FAILURES, CosemObjectType.EMETER_LONG_POWER_FAILURES, CosemObjectType.EMETER_POWER_FAILURES, CosemObjectType.EMETER_LONG_POWER_FAILURES,
CosemObjectType.EMETER_VOLTAGE_SAGS_L1, CosemObjectType.EMETER_VOLTAGE_SWELLS_L1 }, 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_EQUIPMENT_IDENTIFIER, CosemObjectType.EMETER_DELIVERY_TARIFF1,
CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1, CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1,
CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR, 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_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION,
CosemObjectType.EMETER_POWER_FAILURES, CosemObjectType.EMETER_LONG_POWER_FAILURES, CosemObjectType.EMETER_POWER_FAILURES, CosemObjectType.EMETER_LONG_POWER_FAILURES,
CosemObjectType.EMETER_VOLTAGE_SAGS_L1, CosemObjectType.EMETER_VOLTAGE_SWELLS_L1, 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_PRODUCTION_TARIFF0, CosemObjectType.EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_Q,
CosemObjectType.EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_Q, CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_Q, CosemObjectType.EMETER_ACTUAL_DELIVERY,
CosemObjectType.EMETER_ACTUAL_PRODUCTION, CosemObjectType.EMETER_ACTUAL_REACTIVE_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 }, CosemObjectType.EMETER_SWITCH_POSITION },
new CosemObjectType[] { new CosemObjectType[] {
CosemObjectType.EMETER_TRESHOLD_KW, CosemObjectType.EMETER_ACTIVE_THRESHOLD_SMAX,
CosemObjectType.EMETER_POWER_FAILURES, CosemObjectType.EMETER_VOLTAGE_SAGS_L1, CosemObjectType.EMETER_POWER_FAILURES, CosemObjectType.EMETER_VOLTAGE_SAGS_L1,
CosemObjectType.EMETER_VOLTAGE_SAGS_L2, CosemObjectType.EMETER_VOLTAGE_SAGS_L3, CosemObjectType.EMETER_VOLTAGE_SAGS_L2, CosemObjectType.EMETER_VOLTAGE_SAGS_L3,
CosemObjectType.EMETER_VOLTAGE_SWELLS_L1, CosemObjectType.EMETER_VOLTAGE_SWELLS_L2, 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_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_DELIVERY_L3, CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L1,
CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L2, CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L3, 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 */ /** Belgium Smart Meter for the e-MUCS specification */
DEVICE_EMUCS_V1_0(DSMRMeterKind.DEVICE, CosemObjectType.UNKNOWN, DEVICE_EMUCS_V1_0(DSMRMeterKind.DEVICE, CosemObjectType.UNKNOWN,
CosemObjectType.P1_TEXT_STRING, CosemObjectType.P1_TEXT_STRING, CosemObjectType.P1_EMUCS_VERSION_OUTPUT, 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_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1,
CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR, CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR,
CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION, 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}, CosemObjectType.EMETER_SWITCH_POSITION},
new CosemObjectType[] { 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_L1, CosemObjectType.EMETER_INSTANT_CURRENT_L2,
CosemObjectType.EMETER_INSTANT_CURRENT_L3, CosemObjectType.EMETER_INSTANT_VOLTAGE_L1, CosemObjectType.EMETER_INSTANT_CURRENT_L3, CosemObjectType.EMETER_INSTANT_VOLTAGE_L1,
CosemObjectType.EMETER_INSTANT_VOLTAGE_L2, CosemObjectType.EMETER_INSTANT_VOLTAGE_L3 CosemObjectType.EMETER_INSTANT_VOLTAGE_L2, CosemObjectType.EMETER_INSTANT_VOLTAGE_L3
}), }),
/** Belgium Smart Gas Meter for the e-MUCS specification */ /** Belgium Smart Gas Meter for the e-MUCS specification */
GAS_EMUCS_V1_0(DSMRMeterKind.GAS, CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER, 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 * @param availableCosemObjects the Cosem Objects to detect if the current meter compatible
* @return {@link DSMRMeterDescriptor} containing the identification of the compatible meter * @return {@link DSMRMeterDescriptor} containing the identification of the compatible meter
*/ */
public DSMRMeterDescriptor isCompatible(Map<CosemObjectType, CosemObject> availableCosemObjects) { public @Nullable DSMRMeterDescriptor findCompatible(List<CosemObject> availableCosemObjects) {
DSMRMeterDescriptor meterDescriptor = null; final Map<@Nullable Integer, AtomicInteger> channelCounter = new HashMap<>(3);
for (CosemObjectType objectType : requiredCosemObjects) { for (final CosemObjectType objectType : requiredCosemObjects) {
if (!availableCosemObjects.containsKey(objectType)) { final AtomicBoolean match = new AtomicBoolean();
logger.trace("Required objectType {} not found", objectType); 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; 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 // Meter type is compatible, check if an identification exists
if (meterDescriptor == null && cosemObjectTypeMeterId == CosemObjectType.UNKNOWN) { if (meterDescriptor == null && cosemObjectTypeMeterId == CosemObjectType.UNKNOWN) {
logger.trace("Meter type {} has no identification, but is compatible", this); 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"> xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>DSMR Binding</name> <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> </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: <description>The serial port where the P1 port of the Smart Meter is connected (e.g. Linux: /dev/ttyUSB0, Windows:
COM1)</description> COM1)</description>
</parameter> </parameter>
<parameter name="receivedTimeout" type="integer" min="1"> <parameter name="receivedTimeout" type="integer" min="1" unit="s">
<default>30</default> <default>30</default>
<label>Received Timeout</label> <label>Received Timeout</label>
<description>The time period within results are expected in seconds</description> <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 <description>The Luxembourgian Smart meter decryption key. Ask for your energy grid operator for your Smart meter P1
key.</description> key.</description>
</parameter> </parameter>
<parameter name="receivedTimeout" type="integer" min="1"> <parameter name="receivedTimeout" type="integer" min="1" unit="s">
<default>30</default> <default>30</default>
<label>Received Timeout</label> <label>Received Timeout</label>
<description>The time period within results are expected in seconds</description> <description>The time period within results are expected in seconds</description>
@ -84,7 +84,7 @@
</config-description> </config-description>
<config-description uri="thing-type:dsmr:meterdescriptor"> <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> <default>60</default>
<label>Refresh</label> <label>Refresh</label>
<description>The time interval the data is refreshed in seconds</description> <description>The time interval the data is refreshed in seconds</description>

View File

@ -90,25 +90,49 @@
<channel-type id="totalImportedEnergyRegisterPType"> <channel-type id="totalImportedEnergyRegisterPType">
<item-type>Number:Energy</item-type> <item-type>Number:Energy</item-type>
<label>Total Imported Energy (P+)</label> <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"/> <state pattern="%.1f %unit%" readOnly="true"/>
</channel-type> </channel-type>
<channel-type id="totalExportedEnergyRegisterPType"> <channel-type id="totalExportedEnergyRegisterPType">
<item-type>Number:Energy</item-type> <item-type>Number:Energy</item-type>
<label>Total Exported Energy (P+)</label> <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"/> <state pattern="%.1f %unit%" readOnly="true"/>
</channel-type> </channel-type>
<channel-type id="totalImportedEnergyRegisterQType"> <channel-type id="totalImportedEnergyRegisterQType">
<item-type>Number:Energy</item-type> <item-type>Number:Energy</item-type>
<label>Total Imported Energy (Q+)</label> <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"/> <state pattern="%.1f %unit%" readOnly="true"/>
</channel-type> </channel-type>
<channel-type id="totalExportedEnergyRegisterQType"> <channel-type id="totalExportedEnergyRegisterQType">
<item-type>Number:Energy</item-type> <item-type>Number:Energy</item-type>
<label>Total Exported Energy (Q-)</label> <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"/> <state pattern="%.1f %unit%" readOnly="true"/>
</channel-type> </channel-type>
<channel-type id="tariffIndicatorType" advanced="true"> <channel-type id="tariffIndicatorType" advanced="true">

View File

@ -19,7 +19,7 @@
<channel id="emeter_production_tariff1" typeId="productionTariff1Type"/> <channel id="emeter_production_tariff1" typeId="productionTariff1Type"/>
<channel id="emeter_production_tariff2" typeId="productionTariff2Type"/> <channel id="emeter_production_tariff2" typeId="productionTariff2Type"/>
<channel id="emeter_tariff_indicator" typeId="tariffIndicatorType"/> <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_switch_position" typeId="switchPositionType"/>
<channel id="emeter_actual_delivery" typeId="actualDeliveryType"/> <channel id="emeter_actual_delivery" typeId="actualDeliveryType"/>
<channel id="emeter_actual_production" typeId="actualProductionType"/> <channel id="emeter_actual_production" typeId="actualProductionType"/>

View File

@ -19,7 +19,7 @@
<channel id="emeter_production_tariff1" typeId="productionTariff1Type"/> <channel id="emeter_production_tariff1" typeId="productionTariff1Type"/>
<channel id="emeter_production_tariff2" typeId="productionTariff2Type"/> <channel id="emeter_production_tariff2" typeId="productionTariff2Type"/>
<channel id="emeter_tariff_indicator" typeId="tariffIndicatorType"/> <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_switch_position" typeId="switchPositionType"/>
<channel id="emeter_actual_delivery" typeId="actualDeliveryType"/> <channel id="emeter_actual_delivery" typeId="actualDeliveryType"/>
<channel id="emeter_actual_production" typeId="actualProductionType"/> <channel id="emeter_actual_production" typeId="actualProductionType"/>

View File

@ -24,6 +24,12 @@
<channel id="emeter_switch_position" typeId="switchPositionType"/> <channel id="emeter_switch_position" typeId="switchPositionType"/>
<channel id="emeter_fuse_threshold_a" typeId="actualFuseThresholdAType"/> <channel id="emeter_fuse_threshold_a" typeId="actualFuseThresholdAType"/>
<channel id="emeter_treshold_kw" typeId="actualTresholdkWType"/> <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_l1" typeId="instantCurrentL1Type"/>
<channel id="emeter_instant_current_l2" typeId="instantCurrentL2Type"/> <channel id="emeter_instant_current_l2" typeId="instantCurrentL2Type"/>
<channel id="emeter_instant_current_l3" typeId="instantCurrentL3Type"/> <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_delivery" typeId="actualReactiveDeliveryType"/>
<channel id="emeter_actual_reactive_production" typeId="actualReactiveProductionType"/> <channel id="emeter_actual_reactive_production" typeId="actualReactiveProductionType"/>
<channel id="emeter_active_threshold_smax" typeId="activeThresholdSmax"/> <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_switch_position" typeId="switchPositionType"/>
<channel id="emeter_power_failure_log_entries" typeId="powerFailureLogEntriesType"/> <channel id="emeter_power_failure_log_entries" typeId="powerFailureLogEntriesType"/>
<channel id="emeter_power_failure_log_timestamp0" typeId="powerFailureLogEndType"/> <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; 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.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -48,7 +50,7 @@ public final class TelegramReaderUtil {
fail("Could not find telegram file with name:" + telegramName + TELEGRAM_EXT); fail("Could not find telegram file with name:" + telegramName + TELEGRAM_EXT);
} }
return is.readAllBytes(); return is.readAllBytes();
} catch (IOException e) { } catch (final IOException e) {
throw new AssertionError("IOException reading telegram data: ", e); throw new AssertionError("IOException reading telegram data: ", e);
} }
} }
@ -61,9 +63,9 @@ public final class TelegramReaderUtil {
* @return a P1Telegram object * @return a P1Telegram object
*/ */
public static P1Telegram readTelegram(String telegramName, TelegramState expectedTelegramState) { public static P1Telegram readTelegram(String telegramName, TelegramState expectedTelegramState) {
AtomicReference<P1Telegram> p1Telegram = new AtomicReference<>(); final AtomicReference<P1Telegram> p1Telegram = new AtomicReference<>();
byte[] telegram = readRawTelegram(telegramName); final byte[] telegram = readRawTelegram(telegramName);
P1TelegramParser parser = new P1TelegramParser(p1Telegram::set); final P1TelegramParser parser = new P1TelegramParser(p1Telegram::set, true);
parser.setLenientMode(true); parser.setLenientMode(true);
parser.parse(telegram, telegram.length); parser.parse(telegram, telegram.length);

View File

@ -38,8 +38,10 @@ public class P1TelegramParserTest {
{ "dsmr_40", 39, 0}, { "dsmr_40", 39, 0},
{ "dsmr_42", 39, 0}, { "dsmr_42", 39, 0},
{ "dsmr_50", 41, 0}, { "dsmr_50", 41, 0},
{ "flu5_invalid_gasmeter", 19, 1}, { "dsmr_50_austria", 18, 0},
{ "flu5", 21, 0}, { "flu5", 21, 0},
{ "flu5_extra", 31, 0},
{ "flu5_invalid_gasmeter", 19, 1},
{ "Iskra_AM550", 41, 0}, { "Iskra_AM550", 41, 0},
{ "Landis_Gyr_E350", 10, 0}, { "Landis_Gyr_E350", 10, 0},
{ "Landis_Gyr_ZCF110", 25, 0}, { "Landis_Gyr_ZCF110", 25, 0},
@ -53,7 +55,7 @@ public class P1TelegramParserTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("data") @MethodSource("data")
public void testParsing(final String telegramName, final int numberOfCosemObjects, final int unknownObjects) { 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(), assertEquals(unknownObjects, telegram.getUnknownCosemObjects().size(),
"Should not have other than " + unknownObjects + " unknown cosem objects"); "Should not have other than " + unknownObjects + " unknown cosem objects");
assertEquals(numberOfCosemObjects, assertEquals(numberOfCosemObjects,

View File

@ -13,14 +13,26 @@
package org.openhab.binding.dsmr.internal.discovery; package org.openhab.binding.dsmr.internal.discovery;
import static org.junit.jupiter.api.Assertions.assertEquals; 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.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
@ -29,7 +41,6 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.openhab.binding.dsmr.internal.TelegramReaderUtil; import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject; 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;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState; import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
import org.openhab.binding.dsmr.internal.meter.DSMRMeterDescriptor; import org.openhab.binding.dsmr.internal.meter.DSMRMeterDescriptor;
@ -46,17 +57,19 @@ public class DSMRMeterDetectorTest {
// @formatter:off // @formatter:off
public static final List<Object[]> data() { public static final List<Object[]> data() {
return Arrays.asList(new Object[][] { return Arrays.asList(new Object[][] {
{ "ace4000", EnumSet.of( ELECTRICITY_ACE4000, GAS_ACE4000)}, { "ace4000", EnumSet.of(ELECTRICITY_ACE4000, GAS_ACE4000)},
{ "dsmr_40", EnumSet.of( DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)}, { "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_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", 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)}, { "dsmr_50_austria", EnumSet.of(ELECTRICITY_SMARTY_V1_0_AUSTRIA)},
{ "Iskra_AM550", 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)},
{ "Landis_Gyr_E350", EnumSet.of( DEVICE_V2_V3, ELECTRICITY_V3_0)}, { "flu5_extra", EnumSet.of(DEVICE_EMUCS_V1_0, ELECTRICITY_EMUCS_V1_0, GAS_EMUCS_V1_0)},
{ "Landis_Gyr_ZCF110", EnumSet.of( DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)}, { "Iskra_AM550", EnumSet.of(DEVICE_V5, ELECTRICITY_V5_0, M3_V5_0)},
{ "Sagemcom_XS210", EnumSet.of( DEVICE_V4, ELECTRICITY_V4_2)}, { "Landis_Gyr_E350", EnumSet.of(DEVICE_V2_V3, ELECTRICITY_V3_0)},
{ "smarty", EnumSet.of( DEVICE_V5, ELECTRICITY_SMARTY_V1_0)}, { "Landis_Gyr_ZCF110", EnumSet.of(DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)},
{ "smarty_with_units", EnumSet.of( DEVICE_V5, ELECTRICITY_SMARTY_V1_0, M3_V4)}, { "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 // @formatter:on
@ -64,21 +77,17 @@ public class DSMRMeterDetectorTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("data") @MethodSource("data")
public void testDetectMeters(final String telegramName, final Set<DSMRMeterType> expectedMeters) { public void testDetectMeters(final String telegramName, final Set<DSMRMeterType> expectedMeters) {
P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK); final P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK);
DSMRMeterDetector detector = new DSMRMeterDetector(); final DSMRMeterDetector detector = new DSMRMeterDetector();
Entry<Collection<DSMRMeterDescriptor>, Map<CosemObjectType, CosemObject>> entry = detector final Entry<Collection<DSMRMeterDescriptor>, List<CosemObject>> entry = detector.detectMeters(telegram);
.detectMeters(telegram); final Collection<DSMRMeterDescriptor> detectMeters = entry.getKey();
Collection<DSMRMeterDescriptor> detectMeters = entry.getKey();
assertEquals(expectedMeters.size(), detectMeters.size(), assertEquals(expectedMeters.size(), detectMeters.size(),
"Should detect correct number of meters: " + Arrays.toString(detectMeters.toArray())); "Should detect correct number of meters: " + Arrays.toString(detectMeters.toArray()));
assertEquals(Collections.emptyMap(), entry.getValue(), "Should not have any undetected cosem objects: "); assertEquals(List.of(), entry.getValue(), "Should not have any undetected cosem objects: ");
assertEquals(Collections.emptyList(), telegram.getUnknownCosemObjects(), assertEquals(List.of(), telegram.getUnknownCosemObjects(), "Should not have any unknown cosem objects");
"Should not have any unknown cosem objects"); for (final DSMRMeterType meter : expectedMeters) {
for (DSMRMeterType meter : expectedMeters) { assertEquals(1, detectMeters.stream().filter(e -> e.getMeterType() == meter).count(),
assertEquals(
1, detectMeters.stream().filter(e -> e.getMeterType() == meter).count(),
String.format("Meter '%s' not found: %s", meter, String.format("Meter '%s' not found: %s", meter,
Arrays.toString(detectMeters.toArray(new DSMRMeterDescriptor[0])))); Arrays.toString(detectMeters.toArray(new DSMRMeterDescriptor[0]))));
} }

View File

@ -35,12 +35,19 @@ public class DSMRMeterTest {
*/ */
@Test @Test
public void testFilterMeterValues() { public void testFilterMeterValues() {
DSMRMeterDescriptor descriptor = new DSMRMeterDescriptor(DSMRMeterType.DEVICE_V5, 0); final List<CosemObject> cosemObjects = TelegramReaderUtil.readTelegram("dsmr_50", TelegramState.OK)
DSMRMeter meter = new DSMRMeter(descriptor); .getCosemObjects();
List<CosemObject> filterMeterValues = meter assertMeterValues(cosemObjects, DSMRMeterType.DEVICE_V5, DSMRMeterConstants.UNKNOWN_CHANNEL, 3);
.filterMeterValues(TelegramReaderUtil.readTelegram("dsmr_50", TelegramState.OK).getCosemObjects()); assertMeterValues(cosemObjects, DSMRMeterType.ELECTRICITY_V5_0, 0, 29);
assertEquals(DSMRMeterType.DEVICE_V5.requiredCosemObjects.length, filterMeterValues.size(), assertMeterValues(cosemObjects, DSMRMeterType.M3_V5_0, 1, 3);
"Filter should return all required objects"); }
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