2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.dsmr.internal.device.cosem;
15 import java.util.AbstractMap.SimpleEntry;
16 import java.util.Arrays;
17 import java.util.List;
18 import java.util.Map.Entry;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.core.library.unit.Units;
25 * Enumeration Cosem Object types
27 * Each Cosem Object type consists of the following attributes:
30 * <li>OBIS Identifier (reduced form)
31 * <li>List of value descriptors (See {@link CosemValueDescriptor})
32 * <li>List of repeating value descriptors (See {@link CosemValueDescriptor}). Repeating value descriptors will always
33 * be the last descriptors.
36 * @author M. Volaart - Initial contribution
37 * @author Hilbrand Bouwkamp - Cosem subclasses made into factory classes and introduced quantity type
40 public enum CosemObjectType {
41 UNKNOWN(new OBISIdentifier(-1, -1, -1, -1), CosemString.INSTANCE),
43 /* General messages */
44 P1_VERSION_OUTPUT(new OBISIdentifier(1, 0, 2, 8), CosemString.INSTANCE),
45 P1_EMUCS_VERSION_OUTPUT(new OBISIdentifier(0, 96, 1, 4), CosemString.INSTANCE),
46 P1_TIMESTAMP(new OBISIdentifier(0, 1, 0, 0), new CosemDate("")),
47 P1_TEXT_CODE(new OBISIdentifier(0, 96, 13, 1), CosemHexString.INSTANCE),
48 P1_TEXT_STRING(new OBISIdentifier(0, 96, 13, 0), CosemHexString.INSTANCE),
49 P1_TEXT_STRING_LONG(new OBISIdentifier(0, 96, 13, null), CosemHexString.INSTANCE),
51 /* Generic Meter Cosem Object types */
52 METER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, 96, 1, 0), CosemHexString.INSTANCE),
53 METER_DEVICE_TYPE(new OBISIdentifier(0, 24, 1, 0), CosemString.INSTANCE),
54 METER_VALVE_SWITCH_POSITION(new OBISIdentifier(0, 24, 4, 0), CosemDecimal.INSTANCE),
56 /* Electricity Meter */
57 EMETER_EQUIPMENT_IDENTIFIER_V2_X(new OBISIdentifier(0, 42, 0, 0), CosemHexString.INSTANCE),
58 EMETER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, 96, 1, 1), CosemHexString.INSTANCE),
59 EMETER_VALUE(new OBISIdentifier(0, 24, 2, 1, true), CosemDate.INSTANCE, CosemQuantity.KILO_WATT_HOUR),
60 EMETER_DELIVERY_TARIFF0(new OBISIdentifier(1, 1, 8, 0), CosemQuantity.KILO_WATT_HOUR),
61 EMETER_DELIVERY_TARIFF1(new OBISIdentifier(1, 1, 8, 1), CosemQuantity.KILO_WATT_HOUR),
62 EMETER_DELIVERY_TARIFF2(new OBISIdentifier(1, 1, 8, 2), CosemQuantity.KILO_WATT_HOUR),
63 EMETER_DELIVERY_TARIFF0_ANTIFRAUD(new OBISIdentifier(1, 15, 8, 0), CosemQuantity.KILO_WATT_HOUR),
64 EMETER_DELIVERY_TARIFF1_ANTIFRAUD(new OBISIdentifier(1, 15, 8, 1), CosemQuantity.KILO_WATT_HOUR),
65 EMETER_DELIVERY_TARIFF2_ANTIFRAUD(new OBISIdentifier(1, 15, 8, 2), CosemQuantity.KILO_WATT_HOUR),
66 EMETER_PRODUCTION_TARIFF0(new OBISIdentifier(1, 2, 8, 0), CosemQuantity.KILO_WATT_HOUR),
67 EMETER_PRODUCTION_TARIFF1(new OBISIdentifier(1, 2, 8, 1), CosemQuantity.KILO_WATT_HOUR),
68 EMETER_PRODUCTION_TARIFF2(new OBISIdentifier(1, 2, 8, 2), CosemQuantity.KILO_WATT_HOUR),
69 EMETER_TARIFF_INDICATOR(new OBISIdentifier(0, 96, 14, 0), CosemString.INSTANCE),
70 EMETER_ACTIVE_IMPORT_POWER(new OBISIdentifier(1, 15, 7, 0), CosemQuantity.WATT),
71 EMETER_ACTUAL_DELIVERY(new OBISIdentifier(1, 1, 7, 0), CosemQuantity.KILO_WATT),
72 EMETER_ACTUAL_PRODUCTION(new OBISIdentifier(1, 2, 7, 0), CosemQuantity.KILO_WATT),
73 EMETER_TRESHOLD_A_V2_1(new OBISIdentifier(1, 17, 0, 0), CosemQuantity.AMPERE),
74 EMETER_TRESHOLD_A(new OBISIdentifier(0, 17, 0, 0, true), CosemQuantity.AMPERE),
75 EMETER_FUSE_THRESHOLD_A(new OBISIdentifier(1, 31, 4, 0), CosemQuantity.AMPERE),
76 EMETER_TRESHOLD_KW(new OBISIdentifier(0, 17, 0, 0, true), CosemQuantity.KILO_WATT),
77 EMETER_SWITCH_POSITION_V2_1(new OBISIdentifier(1, 96, 3, 10), CosemDecimal.INSTANCE),
78 EMETER_SWITCH_POSITION(new OBISIdentifier(0, 96, 3, 10), CosemDecimal.INSTANCE),
79 EMETER_POWER_FAILURES(new OBISIdentifier(0, 96, 7, 21), CosemDecimal.INSTANCE),
80 EMETER_LONG_POWER_FAILURES(new OBISIdentifier(0, 96, 7, 9), CosemDecimal.INSTANCE),
81 EMETER_POWER_FAILURE_LOG(new OBISIdentifier(1, 99, 97, 0), 2, new CosemDecimal("entries"),
82 new CosemString("obisId"),
83 /* Next 2 descriptors are repeating */
84 CosemDate.INSTANCE, new CosemQuantity<>(Units.SECOND, "duration")),
85 EMETER_VOLTAGE_SAGS_L1(new OBISIdentifier(1, 32, 32, 0), CosemDecimal.INSTANCE),
86 EMETER_VOLTAGE_SAGS_L2(new OBISIdentifier(1, 52, 32, 0), CosemDecimal.INSTANCE),
87 EMETER_VOLTAGE_SAGS_L3(new OBISIdentifier(1, 72, 32, 0), CosemDecimal.INSTANCE),
88 EMETER_VOLTAGE_SWELLS_L1(new OBISIdentifier(1, 32, 36, 0), CosemDecimal.INSTANCE),
89 EMETER_VOLTAGE_SWELLS_L2(new OBISIdentifier(1, 52, 36, 0), CosemDecimal.INSTANCE),
90 EMETER_VOLTAGE_SWELLS_L3(new OBISIdentifier(1, 72, 36, 0), CosemDecimal.INSTANCE),
91 EMETER_INSTANT_CURRENT_L1(new OBISIdentifier(1, 31, 7, 0), CosemQuantity.AMPERE),
92 EMETER_INSTANT_CURRENT_L2(new OBISIdentifier(1, 51, 7, 0), CosemQuantity.AMPERE),
93 EMETER_INSTANT_CURRENT_L3(new OBISIdentifier(1, 71, 7, 0), CosemQuantity.AMPERE),
94 EMETER_INSTANT_POWER_DELIVERY_L1(new OBISIdentifier(1, 21, 7, 0), CosemQuantity.KILO_WATT),
95 EMETER_INSTANT_POWER_DELIVERY_L2(new OBISIdentifier(1, 41, 7, 0), CosemQuantity.KILO_WATT),
96 EMETER_INSTANT_POWER_DELIVERY_L3(new OBISIdentifier(1, 61, 7, 0), CosemQuantity.KILO_WATT),
97 EMETER_INSTANT_POWER_PRODUCTION_L1(new OBISIdentifier(1, 22, 7, 0), CosemQuantity.KILO_WATT),
98 EMETER_INSTANT_POWER_PRODUCTION_L2(new OBISIdentifier(1, 42, 7, 0), CosemQuantity.KILO_WATT),
99 EMETER_INSTANT_POWER_PRODUCTION_L3(new OBISIdentifier(1, 62, 7, 0), CosemQuantity.KILO_WATT),
100 EMETER_INSTANT_VOLTAGE_L1(new OBISIdentifier(1, 32, 7, 0), CosemQuantity.VOLT),
101 EMETER_INSTANT_VOLTAGE_L2(new OBISIdentifier(1, 52, 7, 0), CosemQuantity.VOLT),
102 EMETER_INSTANT_VOLTAGE_L3(new OBISIdentifier(1, 72, 7, 0), CosemQuantity.VOLT),
105 GMETER_EQUIPMENT_IDENTIFIER_V2(new OBISIdentifier(7, 0, 0, 0), CosemHexString.INSTANCE),
106 GMETER_24H_DELIVERY_V2(new OBISIdentifier(7, 23, 1, 0), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE),
107 GMETER_24H_DELIVERY_COMPENSATED_V2(new OBISIdentifier(7, 23, 2, 0), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE),
108 GMETER_LAST_VALUE(new OBISIdentifier(0, 24, 2, 3), CosemDate.INSTANCE, CosemQuantity.CUBIC_METRE),
109 GMETER_VALUE_V3(new OBISIdentifier(0, 24, 3, 0, true), CosemDate.INSTANCE, // Time stamp off the reading
110 new CosemString("val1"), // Specification is not clear what this value is
111 new CosemDecimal("val2"), // Specification is not clear what this value is
112 new CosemDecimal("val3"), // Specification is not clear what this value is
113 new CosemString("obisId"), // String containing an OBIS Identifier
114 new CosemString("unit"), // String containing the type (m3)
115 CosemDecimal.INSTANCE),
116 GMETER_VALVE_POSITION_V2_1(new OBISIdentifier(7, 96, 3, 10), CosemDecimal.INSTANCE),
117 GMETER_VALVE_POSITION_V2_2(new OBISIdentifier(7, 24, 4, 0), CosemDecimal.INSTANCE),
120 HMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(5, 0, 0, 0), CosemHexString.INSTANCE),
121 HMETER_VALUE_V2(new OBISIdentifier(5, 1, 0, 0), CosemQuantity.GIGA_JOULE, CosemDate.INSTANCE),
124 CMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(6, 0, 0, 0), CosemHexString.INSTANCE),
125 CMETER_VALUE_V2(new OBISIdentifier(6, 1, 0, 0), CosemQuantity.GIGA_JOULE, CosemDate.INSTANCE),
128 WMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(8, 0, 0, 0), CosemHexString.INSTANCE),
129 WMETER_VALUE_V2(new OBISIdentifier(8, 1, 0, 0), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE),
130 WMETER_VALUE_V3(new OBISIdentifier(0, 24, 3, 0, true), CosemQuantity.CUBIC_METRE),
132 /* M3 Meter (Gas, Water) */
133 M3METER_VALUE(new OBISIdentifier(0, 24, 2, 1, true), CosemDate.INSTANCE, CosemQuantity.CUBIC_METRE),
135 /* GJ Meter (Heating, Cooling) */
136 GJMETER_VALUE_V3(new OBISIdentifier(0, 24, 3, 0, true), CosemQuantity.GIGA_JOULE),
137 GJMETER_VALUE_V4(new OBISIdentifier(0, 24, 2, 1, true), CosemDate.INSTANCE, CosemQuantity.GIGA_JOULE),
139 /* Generic Meter (DSMR v3 only) */
140 GENMETER_VALUE_V3(new OBISIdentifier(0, 24, 3, 0, true), CosemDecimal.INSTANCE),
142 /* Additional Luxembourgish Smarty Electricity */
143 EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_Q(new OBISIdentifier(1, 3, 8, 0), CosemQuantity.KILO_VAR_HOUR),
144 EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_R_RATE1(new OBISIdentifier(1, 3, 8, 1), CosemQuantity.KILO_VAR_HOUR),
145 EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_R_RATE2(new OBISIdentifier(1, 3, 8, 2), CosemQuantity.KILO_VAR_HOUR),
146 EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_Q(new OBISIdentifier(1, 4, 8, 0), CosemQuantity.KILO_VAR_HOUR),
147 EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_R_RATE1(new OBISIdentifier(1, 4, 8, 1), CosemQuantity.KILO_VAR_HOUR),
148 EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_R_RATE2(new OBISIdentifier(1, 4, 8, 2), CosemQuantity.KILO_VAR_HOUR),
149 // The actual reactive's and threshold have no unit in the data and therefore are not quantity types.
150 EMETER_ACTUAL_REACTIVE_DELIVERY(new OBISIdentifier(1, 3, 7, 0), CosemDecimal.INSTANCE_WITH_UNITS),
151 EMETER_ACTUAL_REACTIVE_PRODUCTION(new OBISIdentifier(1, 4, 7, 0), CosemDecimal.INSTANCE_WITH_UNITS),
152 EMETER_ACTIVE_THRESHOLD_SMAX(new OBISIdentifier(0, 17, 0, 0, true), CosemDecimal.INSTANCE_WITH_UNITS),
153 EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L1(new OBISIdentifier(1, 23, 7, 0), CosemQuantity.KILO_VAR),
154 EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L2(new OBISIdentifier(1, 43, 7, 0), CosemQuantity.KILO_VAR),
155 EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L3(new OBISIdentifier(1, 63, 7, 0), CosemQuantity.KILO_VAR),
156 EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L1(new OBISIdentifier(1, 24, 7, 0), CosemQuantity.KILO_VAR),
157 EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L2(new OBISIdentifier(1, 44, 7, 0), CosemQuantity.KILO_VAR),
158 EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L3(new OBISIdentifier(1, 64, 7, 0), CosemQuantity.KILO_VAR);
160 /** OBIS reduced identifier */
161 public final OBISIdentifier obisId;
163 /** COSEM value descriptors */
164 private final List<CosemValueDescriptor<?>> descriptors;
165 private final List<CosemValueDescriptor<?>> repeatingDescriptors;
168 * Constructs a new CosemObjectType
170 * @param obisId {@link OBISIdentifier} containing the obisIdentifier for CosemObjectType
171 * @param descriptors variable parameter list of {@link CosemValueDescriptor}
173 CosemObjectType(OBISIdentifier obisId, CosemValueDescriptor<?>... descriptors) {
174 this(obisId, 0, descriptors);
178 * Constructs a new CosemObjectType
180 * @param obisId {@link OBISIdentifier} containing the obisIdentifier for CosemObjectType
181 * @param nrOfRepeatingDescriptors nr of repeating descriptors (this are the last n descriptors in the variable list
183 * @param descriptors variable parameter list of {@link CosemValueDescriptor}
185 CosemObjectType(OBISIdentifier obisId, int nrOfRepeatingDescriptors, CosemValueDescriptor<?>... descriptors) {
186 this.obisId = obisId;
187 if (nrOfRepeatingDescriptors == 0) {
188 this.descriptors = Arrays.asList(descriptors);
189 this.repeatingDescriptors = List.of();
191 final List<CosemValueDescriptor<?>> allDescriptors = List.of(descriptors);
194 * The last nrOfRepeatingDescriptors CosemValueDescriptor will go into the repeatingDescriptor list.
195 * The other descriptors will got into the regular list
197 this.descriptors = allDescriptors.subList(0, allDescriptors.size() - nrOfRepeatingDescriptors);
198 this.repeatingDescriptors = allDescriptors.subList(this.descriptors.size(),
199 this.descriptors.size() + nrOfRepeatingDescriptors);
204 * Returns the {@link CosemValueDescriptor} for the specified index.
205 * If the list contains repeating descriptors the specified index will mapped onto the repeating list
207 * e.g. If the list contains 4 descriptors and the last 2 are repeating, idx=6 will return the 4th descriptor.
209 * The idx is < 0 or outside a non-repeating descriptorslist size null is returned
211 * @param idx the CosemValueDescriptor to return
212 * @return the CosemValueDescriptor or null if not found.
214 public @Nullable Entry<String, CosemValueDescriptor<?>> getDescriptor(int idx) {
215 if (idx >= descriptors.size() && !repeatingDescriptors.isEmpty()) {
216 /* We have a repeating list, find the correct repeating descriptor */
217 final int repeatingIdx = (idx - descriptors.size()) % repeatingDescriptors.size();
219 final CosemValueDescriptor<?> descriptor = repeatingDescriptors.get(repeatingIdx);
221 /* The repeating descriptor must have a specific channel */
222 final int repeatCount = (idx - descriptors.size()) / repeatingDescriptors.size();
224 return new SimpleEntry<>(descriptor.getChannelId() + repeatCount, descriptor);
225 } else if (idx < descriptors.size()) {
226 final CosemValueDescriptor<?> descriptor = descriptors.get(idx);
228 return new SimpleEntry<>(descriptor.getChannelId(), descriptor);
235 * Returns if this CosemObjectType supports the requested number of values.
237 * Note that for repeating list the number of values must match the repeating pattern.
239 * So if the list contains 4 values and the last 2 are repeating, nrOfValues = 6 will return true,
240 * however nrOfvalues = 7 will return false (only 4, 6, 8, etc... is allowed)
242 * @param nrOfValues number of values to check.
244 * @return true if this CosemObjectType support the requested number of values, false otherwise.
246 public boolean supportsNrOfValues(int nrOfValues) {
247 if (repeatingDescriptors.isEmpty()) {
248 return nrOfValues == descriptors.size();
250 /* There are repeating descriptors */
251 return ((nrOfValues - descriptors.size()) % repeatingDescriptors.size()) == 0;