[dsmr] Add support for Austrian meters, Fix for channel id detection (#11458)
* Fix fix for channel id detection, Added missing channels to emucs electra - M-bus channels are dynamic and present in the obis id. The binding had most channel types fixed because most of the time these channels are the same. However the device identifier is the same for multiple devices. But the binding only registered only one and while the channel id was derived from this obis data. For detected meters this resulted in the channel id to be the same if there are multiple devices. This change looks at the channel id to assign it to the found device. This is a bit tricky because the general device has no channel and has channels that have different id's. So the binding needs to cover that case. This change also adds some optional channels to the emucs electra meter. Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl> * [dsmr] Add support for Austrian meters Improved the work done in pr #11193 Also-by: Thomas <thomas@knaller.info> Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl> * [dsmr] Added Null handling annotations.
This commit is contained in:
committed by
GitHub
parent
73b4dbe14f
commit
90525c5150
@@ -12,12 +12,15 @@
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.connector;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Error events from a connector.
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Reduced number of event to only errors
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum DSMRConnectorErrorEvent {
|
||||
DONT_EXISTS,
|
||||
IN_USE,
|
||||
|
||||
@@ -123,16 +123,14 @@ public class CosemObject {
|
||||
|
||||
int cosemValueItr = 0;
|
||||
while (cosemValueMatcher.find()) {
|
||||
Entry<String, CosemValueDescriptor<?>> valueDescriptorEntry = type.getDescriptor(cosemValueItr);
|
||||
State cosemValue = valueDescriptorEntry.getValue().getStateValue(cosemValueMatcher.group(2));
|
||||
final Entry<String, CosemValueDescriptor<?>> valueDescriptorEntry = type.getDescriptor(cosemValueItr);
|
||||
final State cosemValue = valueDescriptorEntry.getValue().getStateValue(cosemValueMatcher.group(2));
|
||||
|
||||
if (cosemValue != null) {
|
||||
if (!cosemValues.containsKey(valueDescriptorEntry.getKey())) {
|
||||
cosemValues.put(valueDescriptorEntry.getKey(), cosemValue);
|
||||
} else {
|
||||
logger.warn("Value for descriptor {} already exists, dropping value {}", valueDescriptorEntry,
|
||||
cosemValue);
|
||||
}
|
||||
if (!cosemValues.containsKey(valueDescriptorEntry.getKey())) {
|
||||
cosemValues.put(valueDescriptorEntry.getKey(), cosemValue);
|
||||
} else {
|
||||
logger.warn("Value for descriptor {} already exists, dropping value {}", valueDescriptorEntry,
|
||||
cosemValue);
|
||||
}
|
||||
cosemValueItr++;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -13,10 +13,13 @@
|
||||
package org.openhab.binding.dsmr.internal.discovery;
|
||||
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
|
||||
@@ -46,19 +49,15 @@ class DSMRMeterDetector {
|
||||
* @param telegram The received telegram
|
||||
* @return collection of detected {@link DSMRMeterDescriptor}
|
||||
*/
|
||||
public Entry<Collection<DSMRMeterDescriptor>, Map<CosemObjectType, CosemObject>> detectMeters(P1Telegram telegram) {
|
||||
public Entry<Collection<DSMRMeterDescriptor>, List<CosemObject>> detectMeters(P1Telegram telegram) {
|
||||
final Map<DSMRMeterKind, DSMRMeterDescriptor> detectedMeters = new HashMap<>();
|
||||
final Map<CosemObjectType, CosemObject> availableCosemObjects = new HashMap<>();
|
||||
final Map<CosemObjectType, CosemObject> undetectedCosemObjects = new HashMap<>();
|
||||
|
||||
// Fill hashmap for fast comparing the set of received Cosem objects to the required set of Cosem Objects
|
||||
telegram.getCosemObjects().forEach(msg -> availableCosemObjects.put(msg.getType(), msg));
|
||||
undetectedCosemObjects.putAll(availableCosemObjects);
|
||||
final List<CosemObject> availableCosemObjects = List.copyOf(telegram.getCosemObjects());
|
||||
final List<CosemObject> undetectedCosemObjects = new ArrayList<>(telegram.getCosemObjects());
|
||||
|
||||
// Find compatible meters
|
||||
for (DSMRMeterType meterType : DSMRMeterType.values()) {
|
||||
logger.trace("Trying if meter type {} is compatible", meterType);
|
||||
final DSMRMeterDescriptor meterDescriptor = meterType.isCompatible(availableCosemObjects);
|
||||
final DSMRMeterDescriptor meterDescriptor = meterType.findCompatible(availableCosemObjects);
|
||||
|
||||
if (meterDescriptor == null) {
|
||||
logger.trace("Meter type {} is not compatible", meterType);
|
||||
@@ -74,7 +73,9 @@ class DSMRMeterDetector {
|
||||
logger.debug("New compatible meter: {}", meterDescriptor);
|
||||
detectedMeters.put(meterType.meterKind, meterDescriptor);
|
||||
for (CosemObjectType cot : meterDescriptor.getMeterType().supportedCosemObjects) {
|
||||
undetectedCosemObjects.remove(cot);
|
||||
List<CosemObject> collect = undetectedCosemObjects.stream().filter(u -> cot == u.getType())
|
||||
.collect(Collectors.toList());
|
||||
collect.forEach(undetectedCosemObjects::remove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -12,14 +12,17 @@
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.meter;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* This class describes the kind of meters the binding supports
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum DSMRMeterKind {
|
||||
INVALID,
|
||||
DEVICE,
|
||||
DEVICE(false),
|
||||
MAIN_ELECTRICITY,
|
||||
GAS,
|
||||
HEATING,
|
||||
@@ -31,6 +34,20 @@ public enum DSMRMeterKind {
|
||||
SLAVE_ELECTRICITY1,
|
||||
SLAVE_ELECTRICITY2;
|
||||
|
||||
private final boolean channelRelevant;
|
||||
|
||||
private DSMRMeterKind() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
private DSMRMeterKind(final boolean channelRelevant) {
|
||||
this.channelRelevant = channelRelevant;
|
||||
}
|
||||
|
||||
public boolean isChannelRelevant() {
|
||||
return channelRelevant;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the i18n label key for this meter.
|
||||
*/
|
||||
|
||||
@@ -13,10 +13,18 @@
|
||||
package org.openhab.binding.dsmr.internal.meter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dsmr.internal.DSMRBindingConstants;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectType;
|
||||
@@ -29,6 +37,7 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum DSMRMeterType {
|
||||
// Don't auto format the enum list. For readability the format for the enum is:
|
||||
// First line parameters; DSMRMeterKind and CosemObjectType (identification object type)
|
||||
@@ -143,7 +152,7 @@ public enum DSMRMeterType {
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR,
|
||||
CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION},
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_TRESHOLD_A, CosemObjectType.EMETER_TRESHOLD_KWH,
|
||||
CosemObjectType.EMETER_TRESHOLD_A, CosemObjectType.EMETER_TRESHOLD_KW,
|
||||
CosemObjectType.EMETER_SWITCH_POSITION}),
|
||||
|
||||
/** DSMR V3.0 Gas meter */
|
||||
@@ -175,7 +184,7 @@ public enum DSMRMeterType {
|
||||
CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER, CosemObjectType.EMETER_DELIVERY_TARIFF1,
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1,
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR,
|
||||
CosemObjectType.EMETER_TRESHOLD_KWH, CosemObjectType.EMETER_SWITCH_POSITION,
|
||||
CosemObjectType.EMETER_TRESHOLD_KW, CosemObjectType.EMETER_SWITCH_POSITION,
|
||||
CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION,
|
||||
CosemObjectType.EMETER_POWER_FAILURES, CosemObjectType.EMETER_LONG_POWER_FAILURES,
|
||||
CosemObjectType.EMETER_VOLTAGE_SAGS_L1, CosemObjectType.EMETER_VOLTAGE_SWELLS_L1 },
|
||||
@@ -205,7 +214,7 @@ public enum DSMRMeterType {
|
||||
CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER, CosemObjectType.EMETER_DELIVERY_TARIFF1,
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1,
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR,
|
||||
CosemObjectType.EMETER_TRESHOLD_KWH, CosemObjectType.EMETER_SWITCH_POSITION,
|
||||
CosemObjectType.EMETER_TRESHOLD_KW, CosemObjectType.EMETER_SWITCH_POSITION,
|
||||
CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION,
|
||||
CosemObjectType.EMETER_POWER_FAILURES, CosemObjectType.EMETER_LONG_POWER_FAILURES,
|
||||
CosemObjectType.EMETER_VOLTAGE_SAGS_L1, CosemObjectType.EMETER_VOLTAGE_SWELLS_L1,
|
||||
@@ -280,9 +289,10 @@ public enum DSMRMeterType {
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF0, CosemObjectType.EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_Q,
|
||||
CosemObjectType.EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_Q, CosemObjectType.EMETER_ACTUAL_DELIVERY,
|
||||
CosemObjectType.EMETER_ACTUAL_PRODUCTION, CosemObjectType.EMETER_ACTUAL_REACTIVE_DELIVERY,
|
||||
CosemObjectType.EMETER_ACTUAL_REACTIVE_PRODUCTION, CosemObjectType.EMETER_ACTIVE_THRESHOLD_SMAX,
|
||||
CosemObjectType.EMETER_ACTUAL_REACTIVE_PRODUCTION,
|
||||
CosemObjectType.EMETER_SWITCH_POSITION },
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_TRESHOLD_KW, CosemObjectType.EMETER_ACTIVE_THRESHOLD_SMAX,
|
||||
CosemObjectType.EMETER_POWER_FAILURES, CosemObjectType.EMETER_VOLTAGE_SAGS_L1,
|
||||
CosemObjectType.EMETER_VOLTAGE_SAGS_L2, CosemObjectType.EMETER_VOLTAGE_SAGS_L3,
|
||||
CosemObjectType.EMETER_VOLTAGE_SWELLS_L1, CosemObjectType.EMETER_VOLTAGE_SWELLS_L2,
|
||||
@@ -294,7 +304,23 @@ public enum DSMRMeterType {
|
||||
CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L1, CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L2,
|
||||
CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L3, CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L1,
|
||||
CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L2, CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L3,
|
||||
}),
|
||||
}),
|
||||
/** Austrian "Smarty" meter */
|
||||
ELECTRICITY_SMARTY_V1_0_AUSTRIA(DSMRMeterKind.MAIN_ELECTRICITY, CosemObjectType.UNKNOWN,
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF0, CosemObjectType.EMETER_DELIVERY_TARIFF1,
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF0,
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF1, CosemObjectType.EMETER_PRODUCTION_TARIFF2,
|
||||
CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION,
|
||||
CosemObjectType.EMETER_ACTUAL_REACTIVE_DELIVERY, CosemObjectType.EMETER_ACTUAL_REACTIVE_PRODUCTION,
|
||||
CosemObjectType.EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_Q, CosemObjectType.EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_Q,
|
||||
CosemObjectType.EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_R_RATE1, CosemObjectType.EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_R_RATE2,
|
||||
CosemObjectType.EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_R_RATE1, CosemObjectType.EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_R_RATE2,
|
||||
},
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.P1_VERSION_OUTPUT, CosemObjectType.P1_TIMESTAMP,
|
||||
}),
|
||||
|
||||
/** Belgium Smart Meter for the e-MUCS specification */
|
||||
DEVICE_EMUCS_V1_0(DSMRMeterKind.DEVICE, CosemObjectType.UNKNOWN,
|
||||
CosemObjectType.P1_TEXT_STRING, CosemObjectType.P1_TEXT_STRING, CosemObjectType.P1_EMUCS_VERSION_OUTPUT,
|
||||
@@ -307,13 +333,16 @@ public enum DSMRMeterType {
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1,
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR,
|
||||
CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION,
|
||||
CosemObjectType.EMETER_TRESHOLD_KWH, CosemObjectType.EMETER_FUSE_THRESHOLD_A,
|
||||
CosemObjectType.EMETER_TRESHOLD_KW, CosemObjectType.EMETER_FUSE_THRESHOLD_A,
|
||||
CosemObjectType.EMETER_SWITCH_POSITION},
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L1, CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L2,
|
||||
CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L3, CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L1,
|
||||
CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L2, CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L3,
|
||||
CosemObjectType.EMETER_INSTANT_CURRENT_L1, CosemObjectType.EMETER_INSTANT_CURRENT_L2,
|
||||
CosemObjectType.EMETER_INSTANT_CURRENT_L3, CosemObjectType.EMETER_INSTANT_VOLTAGE_L1,
|
||||
CosemObjectType.EMETER_INSTANT_VOLTAGE_L2, CosemObjectType.EMETER_INSTANT_VOLTAGE_L3
|
||||
}),
|
||||
}),
|
||||
|
||||
/** Belgium Smart Gas Meter for the e-MUCS specification */
|
||||
GAS_EMUCS_V1_0(DSMRMeterKind.GAS, CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER,
|
||||
@@ -398,23 +427,36 @@ public enum DSMRMeterType {
|
||||
* @param availableCosemObjects the Cosem Objects to detect if the current meter compatible
|
||||
* @return {@link DSMRMeterDescriptor} containing the identification of the compatible meter
|
||||
*/
|
||||
public DSMRMeterDescriptor isCompatible(Map<CosemObjectType, CosemObject> availableCosemObjects) {
|
||||
DSMRMeterDescriptor meterDescriptor = null;
|
||||
public @Nullable DSMRMeterDescriptor findCompatible(List<CosemObject> availableCosemObjects) {
|
||||
final Map<@Nullable Integer, AtomicInteger> channelCounter = new HashMap<>(3);
|
||||
|
||||
for (CosemObjectType objectType : requiredCosemObjects) {
|
||||
if (!availableCosemObjects.containsKey(objectType)) {
|
||||
logger.trace("Required objectType {} not found", objectType);
|
||||
for (final CosemObjectType objectType : requiredCosemObjects) {
|
||||
final AtomicBoolean match = new AtomicBoolean();
|
||||
availableCosemObjects.stream().filter(a -> a.getType() == objectType).forEach(b -> {
|
||||
match.set(true);
|
||||
channelCounter.computeIfAbsent(b.getObisIdentifier().getChannel(), t -> new AtomicInteger())
|
||||
.incrementAndGet();
|
||||
});
|
||||
if (!match.get()) {
|
||||
logger.trace("Required objectType {} not found for meter: {}", objectType, this);
|
||||
return null;
|
||||
} else {
|
||||
logger.trace("FOUND Required objectType {}", objectType);
|
||||
}
|
||||
CosemObject cosemObject = availableCosemObjects.get(objectType);
|
||||
|
||||
// Checking by reference is possible here due to comparing enums
|
||||
if (cosemObjectTypeMeterId != CosemObjectType.UNKNOWN && objectType == cosemObjectTypeMeterId) {
|
||||
meterDescriptor = new DSMRMeterDescriptor(this, cosemObject.getObisIdentifier().getGroupB());
|
||||
}
|
||||
}
|
||||
DSMRMeterDescriptor meterDescriptor = null;
|
||||
|
||||
if (meterKind.isChannelRelevant()) {
|
||||
final Optional<Entry<@Nullable Integer, AtomicInteger>> max = channelCounter.entrySet().stream()
|
||||
.max((e1, e2) -> Integer.compare(e1.getValue().get(), e2.getValue().get()));
|
||||
|
||||
if (max.isPresent()) {
|
||||
final Integer channel = max.get().getKey();
|
||||
meterDescriptor = new DSMRMeterDescriptor(this,
|
||||
channel == null ? DSMRMeterConstants.UNKNOWN_CHANNEL : channel);
|
||||
}
|
||||
} else {
|
||||
meterDescriptor = new DSMRMeterDescriptor(this, DSMRMeterConstants.UNKNOWN_CHANNEL);
|
||||
}
|
||||
|
||||
// Meter type is compatible, check if an identification exists
|
||||
if (meterDescriptor == null && cosemObjectTypeMeterId == CosemObjectType.UNKNOWN) {
|
||||
logger.trace("Meter type {} has no identification, but is compatible", this);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -90,25 +90,49 @@
|
||||
<channel-type id="totalImportedEnergyRegisterPType">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Imported Energy (P+)</label>
|
||||
<description>The Total imported energy register (P+).</description>
|
||||
<description>The total imported energy register (P+).</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="totalExportedEnergyRegisterPType">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Exported Energy (P+)</label>
|
||||
<description>The Total exported energy register (P-).</description>
|
||||
<description>The total exported energy register (P-).</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="totalImportedEnergyRegisterQType">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Imported Energy (Q+)</label>
|
||||
<description>The Total imported energy register (Q+).</description>
|
||||
<description>The total imported energy register (Q+).</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="totalExportedEnergyRegisterQType">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Exported Energy (Q-)</label>
|
||||
<description>The Total exported energy register (Q-).</description>
|
||||
<description>The total exported energy register (Q-).</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="totalExportedEnergyRegisterRRate1Type" advanced="true">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Exported Energy R1</label>
|
||||
<description>The total exported energy register R Rate1.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="totalExportedEnergyRegisterRRate2Type" advanced="true">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Exported Energy R</label>
|
||||
<description>The total exported energy register R Rate2.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="totalImportedEnergyRegisterRRate1Type" advanced="true">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Imported Energy R1</label>
|
||||
<description>The total imported energy register R Rate1.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="totalImportedEnergyRegisterRRate2Type" advanced="true">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Imported Energy R2</label>
|
||||
<description>The total imported energy register R Rate2.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="tariffIndicatorType" advanced="true">
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -24,6 +24,12 @@
|
||||
<channel id="emeter_switch_position" typeId="switchPositionType"/>
|
||||
<channel id="emeter_fuse_threshold_a" typeId="actualFuseThresholdAType"/>
|
||||
<channel id="emeter_treshold_kw" typeId="actualTresholdkWType"/>
|
||||
<channel id="emeter_instant_power_delivery_l1" typeId="instantPowerDeliveryL1Type"/>
|
||||
<channel id="emeter_instant_power_delivery_l2" typeId="instantPowerDeliveryL2Type"/>
|
||||
<channel id="emeter_instant_power_delivery_l3" typeId="instantPowerDeliveryL3Type"/>
|
||||
<channel id="emeter_instant_power_production_l1" typeId="instantPowerProductionL1Type"/>
|
||||
<channel id="emeter_instant_power_production_l2" typeId="instantPowerProductionL2Type"/>
|
||||
<channel id="emeter_instant_power_production_l3" typeId="instantPowerProductionL3Type"/>
|
||||
<channel id="emeter_instant_current_l1" typeId="instantCurrentL1Type"/>
|
||||
<channel id="emeter_instant_current_l2" typeId="instantCurrentL2Type"/>
|
||||
<channel id="emeter_instant_current_l3" typeId="instantCurrentL3Type"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user