]> git.basschouten.com Git - openhab-addons.git/commitdiff
[dsmr] Add support for Austrian meters, Fix for channel id detection (#11458)
authorHilbrand Bouwkamp <hilbrand@h72.nl>
Sat, 30 Oct 2021 16:04:42 +0000 (18:04 +0200)
committerGitHub <noreply@github.com>
Sat, 30 Oct 2021 16:04:42 +0000 (18:04 +0200)
* 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.

30 files changed:
bundles/org.openhab.binding.dsmr/README.md
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRConnectorErrorEvent.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemObject.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemObjectFactory.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemObjectType.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/CosemQuantity.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/OBISIdentifier.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParser.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDetector.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDiscoveryService.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRBridgeHandler.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRMeterHandler.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeter.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterDescriptor.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterKind.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterType.java
bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/binding/binding.xml
bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/config/configuration_parameters.xml
bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/channeltypes_electricity.xml
bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_dsmr_v4.xml
bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_dsmr_v4_0_4.xml
bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_emucs_v1_0.xml
bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_smarty_v1.xml
bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/thing/meter_electricity_smarty_v1_austria.xml [new file with mode: 0644]
bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/TelegramReaderUtil.java
bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParserTest.java
bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDetectorTest.java
bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterTest.java
bundles/org.openhab.binding.dsmr/src/test/resources/org/openhab/binding/dsmr/internal/dsmr_50_austria.telegram [new file with mode: 0644]
bundles/org.openhab.binding.dsmr/src/test/resources/org/openhab/binding/dsmr/internal/flu5_extra.telegram [new file with mode: 0644]

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