2 * Copyright (c) 2010-2024 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.knx.internal.itests;
15 import static org.junit.jupiter.api.Assertions.assertEquals;
16 import static org.junit.jupiter.api.Assertions.assertNotNull;
17 import static org.junit.jupiter.api.Assertions.assertNull;
18 import static org.junit.jupiter.api.Assertions.assertTrue;
20 import java.util.Arrays;
21 import java.util.HashSet;
22 import java.util.Objects;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.junit.jupiter.api.AfterAll;
27 import org.junit.jupiter.api.Assertions;
28 import org.junit.jupiter.api.Test;
29 import org.openhab.binding.knx.internal.client.DummyKNXNetworkLink;
30 import org.openhab.binding.knx.internal.client.DummyProcessListener;
31 import org.openhab.binding.knx.internal.dpt.DPTUtil;
32 import org.openhab.binding.knx.internal.dpt.ValueDecoder;
33 import org.openhab.binding.knx.internal.dpt.ValueEncoder;
34 import org.openhab.core.library.types.DateTimeType;
35 import org.openhab.core.library.types.DecimalType;
36 import org.openhab.core.library.types.HSBType;
37 import org.openhab.core.library.types.IncreaseDecreaseType;
38 import org.openhab.core.library.types.OnOffType;
39 import org.openhab.core.library.types.OpenClosedType;
40 import org.openhab.core.library.types.PercentType;
41 import org.openhab.core.library.types.QuantityType;
42 import org.openhab.core.library.types.StopMoveType;
43 import org.openhab.core.library.types.StringType;
44 import org.openhab.core.library.types.UpDownType;
45 import org.openhab.core.types.Type;
46 import org.openhab.core.util.ColorUtil;
47 import org.openhab.core.util.HexUtils;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
51 import tuwien.auto.calimero.DataUnitBuilder;
52 import tuwien.auto.calimero.GroupAddress;
53 import tuwien.auto.calimero.KNXException;
54 import tuwien.auto.calimero.datapoint.CommandDP;
55 import tuwien.auto.calimero.datapoint.Datapoint;
56 import tuwien.auto.calimero.dptxlator.TranslatorTypes;
57 import tuwien.auto.calimero.process.ProcessCommunicator;
58 import tuwien.auto.calimero.process.ProcessCommunicatorImpl;
61 * Integration test to check conversion from raw KNX frame data to OH data types and back.
65 * <li>if OH can properly decode raw data payload from KNX frames using {@link ValueDecoder#decode()},
66 * <li>if OH can properly encode the data for handover to Calimero using {@link ValueEncoder#encode()},
67 * <li>if Calimero supports and correctly handles the data conversion to raw bytes for sending.
70 * In addition, it checks if newly integrated releases of Calimero introduce new DPT types not yet
71 * handled by this test. However, new subtypes are not detected.
73 * @see DummyKNXNetworkLink
75 * @author Holger Friedrich - Initial contribution
79 public class Back2BackTest {
80 public static final Logger LOGGER = LoggerFactory.getLogger(Back2BackTest.class);
81 static Set<Integer> dptTested = new HashSet<>();
82 static final byte[] F32_MINUS_ONE = new byte[] { (byte) 0xbf, (byte) 0x80, 0, 0 };
83 boolean testsMissing = false;
86 * helper method for integration tests
88 * @param dpt DPT type, e.g. "251.600", see 03_07_02-Datapoint-Types-v02.02.01-AS.pdf
89 * @param rawData byte array containing raw data, known content
90 * @param ohReferenceData OpenHAB data type, initialized to known good value
91 * @param maxDistance byte array containing maximal deviations when comparing byte arrays (rawData against created
92 * KNX frame), may be empty if no deviation is considered
93 * @param bitmask to mask certain bits in the raw to raw comparison, required for multi-valued KNX frames
95 void helper(String dpt, byte[] rawData, Type ohReferenceData, byte[] maxDistance, byte[] bitmask) {
97 DummyKNXNetworkLink link = new DummyKNXNetworkLink();
98 ProcessCommunicator pc = new ProcessCommunicatorImpl(link);
99 DummyProcessListener processListener = new DummyProcessListener();
100 pc.addProcessListener(processListener);
102 GroupAddress groupAddress = new GroupAddress(2, 4, 6);
103 Datapoint datapoint = new CommandDP(groupAddress, "dummy GA", 0,
104 DPTUtil.NORMALIZED_DPT.getOrDefault(dpt, dpt));
106 // 0) check usage of helper()
107 assertEquals(true, rawData.length > 0);
108 if (maxDistance.length == 0) {
109 maxDistance = new byte[rawData.length];
111 assertEquals(rawData.length, maxDistance.length, "incorrect length of maxDistance array");
112 if (bitmask.length == 0) {
113 bitmask = new byte[rawData.length];
114 Arrays.fill(bitmask, (byte) 0xff);
116 assertEquals(rawData.length, bitmask.length, "incorrect length of bitmask array");
117 int mainType = Integer.parseUnsignedInt(dpt.substring(0, dpt.indexOf('.')));
118 dptTested.add(Integer.valueOf(mainType));
119 // check if OH would be able to send out a frame, given the type
120 Set<Integer> knownWorking = Set.of(1, 3, 5);
121 if (!knownWorking.contains(mainType)) {
122 Set<Class<? extends Type>> allowedTypes = DPTUtil.getAllowedTypes("" + mainType);
123 if (!allowedTypes.contains(ohReferenceData.getClass())) {
125 "test for DPT {} uses type {} which is not contained in DPT_TYPE_MAP, sending may not be allowed",
126 dpt, ohReferenceData.getClass());
130 // 1) check if the decoder works (rawData to known good type ohReferenceData)
132 // This test is based on known raw data. The mapping to openHAB type is known and confirmed.
133 // In this test, only ValueDecoder.decode() is involved.
135 // raw data of the DPT on application layer, without all headers from the layers below
136 // see 03_07_02-Datapoint-Types-v02.02.01-AS.pdf
137 Type ohData = (Type) ValueDecoder.decode(dpt, rawData, ohReferenceData.getClass());
138 assertNotNull(ohData, "could not decode frame data for DPT " + dpt);
139 if ((ohReferenceData instanceof HSBType hsbReferenceData) && (ohData instanceof HSBType hsbData)) {
140 assertTrue(hsbReferenceData.closeTo(hsbData, 0.001),
141 "comparing reference to decoded value for DPT " + dpt);
143 assertEquals(ohReferenceData, ohData, "comparing reference to decoded value: failed for DPT " + dpt
144 + ", check ValueEncoder.decode()");
147 // 2) check the encoding (ohData to raw data)
149 // Test approach is to a) encode the value into String format using ValueEncoder.encode(),
150 // b) pass it to Calimero for conversion into a raw representation, and
151 // c) finally grab raw data bytes from a custom KNXNetworkLink implementation
152 String enc = ValueEncoder.encode(ohData, dpt);
153 pc.write(datapoint, enc);
155 byte[] frame = link.getLastFrame();
156 assertNotNull(frame);
157 // remove header; for compact frames extract data byte from header
158 frame = DataUnitBuilder.extractASDU(frame);
159 assertEquals(rawData.length, frame.length,
160 "unexpected length of KNX frame: " + HexUtils.bytesToHex(frame, " "));
161 for (int i = 0; i < rawData.length; i++) {
162 assertEquals(rawData[i] & bitmask[i] & 0xff, frame[i] & bitmask[i] & 0xff, maxDistance[i],
163 "unexpected content in encoded data, " + i);
166 // 3) Check date provided by Calimero library as input via loopback, it should match the initial data
168 // Deviations in some bytes of the frame may be possible due to data conversion, e.g. for HSBType.
169 // This is why maxDistance is used.
170 byte[] input = processListener.getLastFrame();
171 LOGGER.info("loopback {}", HexUtils.bytesToHex(input, " "));
172 assertNotNull(input);
173 assertEquals(rawData.length, input.length, "unexpected length of loopback frame");
174 for (int i = 0; i < rawData.length; i++) {
175 assertEquals(rawData[i] & bitmask[i] & 0xff, input[i] & bitmask[i] & 0xff, maxDistance[i],
176 "unexpected content in loopback data, " + i);
180 } catch (KNXException e) {
181 LOGGER.warn("exception occurred", e.toString());
182 assertEquals("", e.toString());
186 void helper(String dpt, byte[] rawData, Type ohReferenceData) {
187 helper(dpt, rawData, ohReferenceData, new byte[0], new byte[0]);
192 helper("1.001", new byte[] { 0 }, OnOffType.OFF);
193 helper("1.001", new byte[] { 1 }, OnOffType.ON);
194 helper("1.001", new byte[] { 0 }, OpenClosedType.CLOSED);
195 helper("1.001", new byte[] { 1 }, OpenClosedType.OPEN);
196 helper("1.002", new byte[] { 0 }, OnOffType.OFF);
197 helper("1.002", new byte[] { 1 }, OnOffType.ON);
198 helper("1.002", new byte[] { 0 }, OpenClosedType.CLOSED);
199 helper("1.002", new byte[] { 1 }, OpenClosedType.OPEN);
200 helper("1.003", new byte[] { 0 }, OnOffType.OFF);
201 helper("1.003", new byte[] { 1 }, OnOffType.ON);
202 helper("1.003", new byte[] { 0 }, OpenClosedType.CLOSED);
203 helper("1.003", new byte[] { 1 }, OpenClosedType.OPEN);
204 helper("1.004", new byte[] { 0 }, OnOffType.OFF);
205 helper("1.004", new byte[] { 1 }, OnOffType.ON);
206 helper("1.004", new byte[] { 0 }, OpenClosedType.CLOSED);
207 helper("1.004", new byte[] { 1 }, OpenClosedType.OPEN);
208 helper("1.005", new byte[] { 0 }, OnOffType.OFF);
209 helper("1.005", new byte[] { 1 }, OnOffType.ON);
210 helper("1.005", new byte[] { 0 }, OpenClosedType.CLOSED);
211 helper("1.005", new byte[] { 1 }, OpenClosedType.OPEN);
212 helper("1.006", new byte[] { 0 }, OnOffType.OFF);
213 helper("1.006", new byte[] { 1 }, OnOffType.ON);
214 helper("1.006", new byte[] { 0 }, OpenClosedType.CLOSED);
215 helper("1.006", new byte[] { 1 }, OpenClosedType.OPEN);
216 helper("1.007", new byte[] { 0 }, OnOffType.OFF);
217 helper("1.007", new byte[] { 1 }, OnOffType.ON);
218 helper("1.007", new byte[] { 0 }, OpenClosedType.CLOSED);
219 helper("1.007", new byte[] { 1 }, OpenClosedType.OPEN);
220 helper("1.008", new byte[] { 0 }, UpDownType.UP);
221 helper("1.008", new byte[] { 1 }, UpDownType.DOWN);
222 // NOTE: This is how DPT 1.009 is defined: 0: open, 1: closed
223 // For historical reasons it is defined the other way on OH
224 helper("1.009", new byte[] { 0 }, OnOffType.OFF);
225 helper("1.009", new byte[] { 1 }, OnOffType.ON);
226 helper("1.009", new byte[] { 0 }, OpenClosedType.CLOSED);
227 helper("1.009", new byte[] { 1 }, OpenClosedType.OPEN);
228 helper("1.010", new byte[] { 0 }, StopMoveType.STOP);
229 helper("1.010", new byte[] { 1 }, StopMoveType.MOVE);
230 helper("1.011", new byte[] { 0 }, OnOffType.OFF);
231 helper("1.011", new byte[] { 1 }, OnOffType.ON);
232 helper("1.011", new byte[] { 0 }, OpenClosedType.CLOSED);
233 helper("1.011", new byte[] { 1 }, OpenClosedType.OPEN);
234 helper("1.012", new byte[] { 0 }, OnOffType.OFF);
235 helper("1.012", new byte[] { 1 }, OnOffType.ON);
236 helper("1.012", new byte[] { 0 }, OpenClosedType.CLOSED);
237 helper("1.012", new byte[] { 1 }, OpenClosedType.OPEN);
238 helper("1.013", new byte[] { 0 }, OnOffType.OFF);
239 helper("1.013", new byte[] { 1 }, OnOffType.ON);
240 helper("1.013", new byte[] { 0 }, OpenClosedType.CLOSED);
241 helper("1.013", new byte[] { 1 }, OpenClosedType.OPEN);
242 helper("1.014", new byte[] { 0 }, OnOffType.OFF);
243 helper("1.014", new byte[] { 1 }, OnOffType.ON);
244 helper("1.014", new byte[] { 0 }, OpenClosedType.CLOSED);
245 helper("1.014", new byte[] { 1 }, OpenClosedType.OPEN);
246 helper("1.015", new byte[] { 0 }, OnOffType.OFF);
247 helper("1.015", new byte[] { 1 }, OnOffType.ON);
248 helper("1.015", new byte[] { 0 }, OpenClosedType.CLOSED);
249 helper("1.015", new byte[] { 1 }, OpenClosedType.OPEN);
250 helper("1.016", new byte[] { 0 }, OnOffType.OFF);
251 helper("1.016", new byte[] { 1 }, OnOffType.ON);
252 helper("1.016", new byte[] { 0 }, OpenClosedType.CLOSED);
253 helper("1.016", new byte[] { 1 }, OpenClosedType.OPEN);
254 // DPT 1.017 is a special case, "trigger" has no "value", both 0 and 1 shall trigger
255 helper("1.017", new byte[] { 0 }, OnOffType.OFF);
256 helper("1.017", new byte[] { 0 }, OpenClosedType.CLOSED);
257 // Calimero maps it always to 0
258 // helper("1.017", new byte[] { 1 }, OnOffType.ON);
259 helper("1.018", new byte[] { 0 }, OnOffType.OFF);
260 helper("1.018", new byte[] { 1 }, OnOffType.ON);
261 helper("1.018", new byte[] { 0 }, OpenClosedType.CLOSED);
262 helper("1.018", new byte[] { 1 }, OpenClosedType.OPEN);
263 helper("1.019", new byte[] { 0 }, OnOffType.OFF);
264 helper("1.019", new byte[] { 1 }, OnOffType.ON);
265 helper("1.019", new byte[] { 0 }, OpenClosedType.CLOSED);
266 helper("1.019", new byte[] { 1 }, OpenClosedType.OPEN);
268 helper("1.021", new byte[] { 0 }, OnOffType.OFF);
269 helper("1.021", new byte[] { 1 }, OnOffType.ON);
270 helper("1.021", new byte[] { 0 }, OpenClosedType.CLOSED);
271 helper("1.021", new byte[] { 1 }, OpenClosedType.OPEN);
272 // DPT 1.022 is mapped to decimal, Calimero does not follow the recommendation
273 // from KNX spec to add offset 1
274 helper("1.022", new byte[] { 0 }, DecimalType.valueOf("0"));
275 helper("1.022", new byte[] { 1 }, DecimalType.valueOf("1"));
276 helper("1.023", new byte[] { 0 }, OnOffType.OFF);
277 helper("1.023", new byte[] { 1 }, OnOffType.ON);
278 helper("1.023", new byte[] { 0 }, OpenClosedType.CLOSED);
279 helper("1.023", new byte[] { 1 }, OpenClosedType.OPEN);
280 helper("1.024", new byte[] { 0 }, OnOffType.OFF);
281 helper("1.024", new byte[] { 1 }, OnOffType.ON);
282 helper("1.024", new byte[] { 0 }, OpenClosedType.CLOSED);
283 helper("1.024", new byte[] { 1 }, OpenClosedType.OPEN);
285 helper("1.100", new byte[] { 0 }, OnOffType.OFF);
286 helper("1.100", new byte[] { 1 }, OnOffType.ON);
287 helper("1.100", new byte[] { 0 }, OpenClosedType.CLOSED);
288 helper("1.100", new byte[] { 1 }, OpenClosedType.OPEN);
290 helper("1.1200", new byte[] { 0 }, OnOffType.OFF);
291 helper("1.1200", new byte[] { 1 }, OnOffType.ON);
292 helper("1.1200", new byte[] { 0 }, OpenClosedType.CLOSED);
293 helper("1.1200", new byte[] { 1 }, OpenClosedType.OPEN);
294 helper("1.1201", new byte[] { 0 }, OnOffType.OFF);
295 helper("1.1201", new byte[] { 1 }, OnOffType.ON);
296 helper("1.1201", new byte[] { 0 }, OpenClosedType.CLOSED);
297 helper("1.1201", new byte[] { 1 }, OpenClosedType.OPEN);
302 for (int subType = 1; subType <= 12; subType++) {
303 helper("2." + String.format("%03d", subType), new byte[] { 3 }, new DecimalType(3));
309 // DPT 3.007 and DPT 3.008 consist of a control bit (1 bit) and stepsize (3 bit)
310 // if stepsize is 0, OH will ignore the command
311 byte controlBit = 1 << 3;
312 // loop all other step sizes and check only the control bit
313 for (byte i = 1; i < 8; i++) {
314 helper("3.007", new byte[] { i }, IncreaseDecreaseType.DECREASE, new byte[0], new byte[] { controlBit });
315 helper("3.007", new byte[] { (byte) (i + controlBit) }, IncreaseDecreaseType.INCREASE, new byte[0],
316 new byte[] { controlBit });
317 helper("3.008", new byte[] { i }, UpDownType.UP, new byte[0], new byte[] { controlBit });
318 helper("3.008", new byte[] { (byte) (i + controlBit) }, UpDownType.DOWN, new byte[0],
319 new byte[] { controlBit });
322 // check if OH ignores incoming frames with mask 0 (mapped to UndefType)
323 Assertions.assertFalse(ValueDecoder.decode("3.007", new byte[] { 0 },
324 IncreaseDecreaseType.class) instanceof IncreaseDecreaseType);
325 Assertions.assertFalse(ValueDecoder.decode("3.007", new byte[] { controlBit },
326 IncreaseDecreaseType.class) instanceof IncreaseDecreaseType);
327 Assertions.assertFalse(ValueDecoder.decode("3.008", new byte[] { 0 }, UpDownType.class) instanceof UpDownType);
328 Assertions.assertFalse(
329 ValueDecoder.decode("3.008", new byte[] { controlBit }, UpDownType.class) instanceof UpDownType);
334 helper("5.001", new byte[] { 0 }, new QuantityType<>("0 %"));
335 helper("5.001", new byte[] { (byte) 0xff }, new QuantityType<>("100 %"));
336 // fallback: PercentType
337 helper("5.001", new byte[] { 0 }, new PercentType(0));
338 helper("5.001", new byte[] { (byte) 0x80 }, new PercentType(50));
339 helper("5.001", new byte[] { (byte) 0xff }, new PercentType(100));
341 helper("5.003", new byte[] { 0 }, new QuantityType<>("0 °"));
342 helper("5.003", new byte[] { (byte) 0xff }, new QuantityType<>("360 °"));
343 helper("5.004", new byte[] { 0 }, new QuantityType<>("0 %"));
344 helper("5.004", new byte[] { (byte) 0x64 }, new QuantityType<>("100 %"));
345 helper("5.004", new byte[] { (byte) 0xff }, new QuantityType<>("255 %"));
346 // PercentType cannot encode values >100%, not supported for 5.004
347 helper("5.005", new byte[] { 42 }, new DecimalType(42));
348 helper("5.005", new byte[] { (byte) 0xff }, new DecimalType(255));
349 helper("5.006", new byte[] { 0 }, new DecimalType(0));
350 helper("5.006", new byte[] { 42 }, new DecimalType(42));
351 helper("5.006", new byte[] { (byte) 0xfe }, new DecimalType(254));
353 helper("5.010", new byte[] { 42 }, new DecimalType(42));
354 helper("5.010", new byte[] { (byte) 0xff }, new DecimalType(255));
359 helper("6.001", new byte[] { 0 }, new QuantityType<>("0 %"));
360 helper("6.001", new byte[] { (byte) 0x7f }, new QuantityType<>("127 %"));
361 helper("6.001", new byte[] { (byte) 0xff }, new QuantityType<>("-1 %"));
362 // PercentType cannot encode values >100% or <0%, not supported for 6.001
364 helper("6.010", new byte[] { 0 }, new DecimalType(0));
365 helper("6.010", new byte[] { (byte) 0x7f }, new DecimalType(127));
366 helper("6.010", new byte[] { (byte) 0xff }, new DecimalType(-1));
371 // TODO add tests for more subtypes
372 helper("7.001", new byte[] { 0, 42 }, new DecimalType(42));
373 helper("7.001", new byte[] { (byte) 0xff, (byte) 0xff }, new DecimalType(65535));
378 helper("8.001", new byte[] { (byte) 0x7f, (byte) 0xff }, new DecimalType(32767));
379 helper("8.001", new byte[] { (byte) 0x80, (byte) 0x00 }, new DecimalType(-32768));
380 helper("8.002", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 ms"));
381 helper("8.002", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 ms"));
382 helper("8.002", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 ms"));
383 helper("8.003", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-327680 ms"));
384 helper("8.003", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("327670 ms"));
385 helper("8.003", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 ms"));
386 helper("8.004", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-3276800 ms"));
387 helper("8.004", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("3276700 ms"));
388 helper("8.004", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 ms"));
389 helper("8.005", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 s"));
390 helper("8.005", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 s"));
391 helper("8.005", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 s"));
392 helper("8.006", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 min"));
393 helper("8.006", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 min"));
394 helper("8.006", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 min"));
395 helper("8.007", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 h"));
396 helper("8.007", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 h"));
397 helper("8.007", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 h"));
399 helper("8.011", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 °"));
400 helper("8.011", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 °"));
401 helper("8.011", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 °"));
402 helper("8.012", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 m"));
403 helper("8.012", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 m"));
404 helper("8.012", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 m"));
409 // special float with sign, 4-bit exponent, and mantissa in two's complement notation
410 // ref: KNX spec, 03_07_02-Datapoint-Types
411 // FIXME according to spec, value 0x7fff shall be regarded as "invalid data"
412 // FIXME lower boundary not fully covered by Calimero library
413 // TODO add tests for clipping at lower boundary (e.g. absolute zero)
414 helper("9.001", new byte[] { (byte) 0x00, (byte) 0x64 }, new QuantityType<>("1 °C"));
415 helper("9.001", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 °C"));
416 helper("9.001", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 °C"));
417 helper("9.001", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 °C"));
418 // lower values than absolute zero will be set to abs. zero (-273 °C)
419 // helper("9.001", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-273 °C"));
420 helper("9.002", new byte[] { (byte) 0x00, (byte) 0x64 }, new QuantityType<>("1 K"));
421 helper("9.002", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 K"));
422 helper("9.002", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 K"));
423 helper("9.002", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 K"));
424 // broken, Calimero does not allow full range
425 // helper("9.002", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 K"));
426 helper("9.003", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 K/h"));
427 helper("9.003", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 K/h"));
428 helper("9.003", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 K/h"));
429 // broken, Calimero does not allow full range
430 // helper("9.003", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 K/h"));
431 helper("9.004", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 lx"));
432 helper("9.004", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 lx"));
433 helper("9.004", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 lx"));
434 helper("9.005", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 m/s"));
435 helper("9.005", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 m/s"));
436 helper("9.005", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 m/s"));
437 // no negative values allowed for DPTs 9.005-9.008
438 helper("9.005", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 m/s"));
439 helper("9.005", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 m/s"));
440 helper("9.005", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 m/s"));
441 helper("9.006", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 Pa"));
442 helper("9.006", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 Pa"));
443 helper("9.006", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 Pa"));
444 helper("9.007", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 %"));
445 helper("9.007", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 %"));
446 helper("9.007", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 %"));
447 helper("9.008", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 ppm"));
448 helper("9.008", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 ppm"));
449 helper("9.008", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 ppm"));
450 helper("9.009", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 m³/h"));
451 helper("9.009", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 m³/h"));
452 helper("9.009", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 m³/h"));
453 // broken, Calimero does not allow full range
454 // helper("9.009", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 m³/h"));
455 helper("9.010", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 s"));
456 helper("9.010", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 s"));
457 helper("9.010", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 s"));
458 // broken, Calimero does not allow full range
459 // helper("9.010", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 s"));
460 helper("9.011", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 ms"));
461 helper("9.011", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 ms"));
462 helper("9.011", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 ms"));
463 // broken, Calimero does not allow full range
464 // helper("9.011", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 K/h"));
466 helper("9.020", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 mV"));
467 helper("9.020", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 mV"));
468 helper("9.020", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 mV"));
469 // broken, Calimero does not allow full range
470 // helper("9.020", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 mV"));
471 helper("9.021", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 mA"));
472 helper("9.021", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 mA"));
473 helper("9.021", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 mA"));
474 // broken, Calimero does not allow full range
475 // helper("9.021", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 mA"));
476 helper("9.022", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 W/m²"));
477 helper("9.022", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 W/m²"));
478 helper("9.022", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 W/m²"));
479 // broken, Calimero does not allow full range
480 // helper("9.022", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 W/m²"));
481 helper("9.023", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 K/%"));
482 helper("9.023", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 K/%"));
483 helper("9.023", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 K/%"));
484 // broken, Calimero does not allow full range
485 // helper("9.023", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 K/%"));
486 helper("9.024", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 kW"));
487 helper("9.024", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 kW"));
488 helper("9.024", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 kW"));
489 // broken, Calimero does not allow full range
490 // helper("9.024", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 kW"));
491 helper("9.025", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 l/h"));
492 helper("9.025", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 l/h"));
493 helper("9.025", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 l/h"));
494 // broken, Calimero does not allow full range
495 // helper("9.025", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 l/h"));
496 helper("9.026", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 l/m²"));
497 helper("9.026", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 l/m²"));
498 helper("9.026", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 l/m²"));
499 // broken, Calimero does not allow full range
500 // helper("9.026", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 l/m²"));
501 helper("9.027", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 °F"));
502 helper("9.027", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 °F"));
503 helper("9.027", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 °F"));
504 // lower values than absolute zero will be set to abs. zero (-459.6 °F)
505 helper("9.028", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 km/h"));
506 helper("9.028", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 km/h"));
507 helper("9.028", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 km/h"));
508 // no negative values allowed for DPTs 9.028-9.030
509 helper("9.029", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 g/m³"));
510 helper("9.029", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 g/m³"));
511 helper("9.029", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 g/m³"));
512 helper("9.030", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 µg/m³"));
513 helper("9.030", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 µg/m³"));
514 helper("9.030", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 µg/m³"));
519 // TODO check handling of DPT10: date is not set to current date, but 1970-01-01 + offset if day is given
520 // maybe we should change the semantics and use current date + offset if day is given
522 // note: local timezone is set when creating DateTimeType, for example "1970-01-01Thh:mm:ss.000+0100"
526 .toString(ValueDecoder.decode("10.001", new byte[] { (byte) 0x11, (byte) 0x1e, 0 }, DecimalType.class))
527 .startsWith("1970-01-01T17:30:00.000+"));
528 // Thursday, this is correct for 1970-01-01
530 .toString(ValueDecoder.decode("10.001", new byte[] { (byte) 0x91, (byte) 0x1e, 0 }, DecimalType.class))
531 .startsWith("1970-01-01T17:30:00.000+"));
532 // Monday -> 1970-01-05
534 .toString(ValueDecoder.decode("10.001", new byte[] { (byte) 0x31, (byte) 0x1e, 0 }, DecimalType.class))
535 .startsWith("1970-01-05T17:30:00.000+"));
537 // Thursday, otherwise first byte of encoded data will not match
538 helper("10.001", new byte[] { (byte) 0x91, (byte) 0x1e, (byte) 0x0 }, new DateTimeType("17:30:00"));
539 helper("10.001", new byte[] { (byte) 0x11, (byte) 0x1e, (byte) 0x0 }, new DateTimeType("17:30:00"), new byte[0],
540 new byte[] { (byte) 0x1f, (byte) 0xff, (byte) 0xff });
545 // note: local timezone and dst is set when creating DateTimeType, for example "2019-06-12T00:00:00.000+0200"
546 helper("11.001", new byte[] { (byte) 12, 6, 19 }, new DateTimeType("2019-06-12"));
551 helper("12.001", new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xfe },
552 new DecimalType("4294967294"));
553 helper("12.100", new byte[] { 0, 0, 0, 60 }, new QuantityType<>("60 s"));
554 helper("12.100", new byte[] { 0, 0, 0, 60 }, new QuantityType<>("1 min"));
555 helper("12.101", new byte[] { 0, 0, 0, 60 }, new QuantityType<>("60 min"));
556 helper("12.101", new byte[] { 0, 0, 0, 60 }, new QuantityType<>("1 h"));
557 helper("12.102", new byte[] { 0, 0, 0, 1 }, new QuantityType<>("1 h"));
558 helper("12.102", new byte[] { 0, 0, 0, 1 }, new QuantityType<>("60 min"));
560 helper("12.1200", new byte[] { 0, 0, 0, 1 }, new QuantityType<>("1 l"));
561 helper("12.1200", new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xfe },
562 new QuantityType<>("4294967294 l"));
563 helper("12.1201", new byte[] { 0, 0, 0, 1 }, new QuantityType<>("1 m³"));
564 helper("12.1201", new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xfe },
565 new QuantityType<>("4294967294 m³"));
570 helper("13.001", new byte[] { 0, 0, 0, 0 }, new DecimalType(0));
571 helper("13.001", new byte[] { 0, 0, 0, 42 }, new DecimalType(42));
572 helper("13.001", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
573 new DecimalType(2147483647));
574 // KNX representation typically uses two's complement
575 helper("13.001", new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }, new DecimalType(-1));
576 helper("13.001", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 }, new DecimalType(-2147483648));
577 helper("13.002", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 m³/h"));
578 helper("13.002", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
579 new QuantityType<>("-2147483648 m³/h"));
580 helper("13.002", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
581 new QuantityType<>("2147483647 m³/h"));
583 helper("13.010", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 Wh"));
584 helper("13.010", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
585 new QuantityType<>("-2147483648 Wh"));
586 helper("13.010", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
587 new QuantityType<>("2147483647 Wh"));
588 helper("13.011", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 VAh"));
589 helper("13.011", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
590 new QuantityType<>("-2147483648 VAh"));
591 helper("13.011", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
592 new QuantityType<>("2147483647 VAh"));
593 helper("13.012", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 varh"));
594 helper("13.012", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
595 new QuantityType<>("-2147483648 varh"));
596 helper("13.012", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
597 new QuantityType<>("2147483647 varh"));
598 helper("13.013", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 kWh"));
599 helper("13.013", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
600 new QuantityType<>("-2147483648 kWh"));
601 helper("13.013", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
602 new QuantityType<>("2147483647 kWh"));
603 helper("13.014", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 VAh"));
604 helper("13.014", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
605 new QuantityType<>("-2147483648000 VAh"));
606 helper("13.014", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
607 new QuantityType<>("2147483647000 VAh"));
608 helper("13.015", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 kvarh"));
609 helper("13.015", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
610 new QuantityType<>("-2147483648 kvarh"));
611 helper("13.015", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
612 new QuantityType<>("2147483647 kvarh"));
613 helper("13.016", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 MWh"));
614 helper("13.016", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
615 new QuantityType<>("-2147483648 MWh"));
616 helper("13.016", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
617 new QuantityType<>("2147483647 MWh"));
619 helper("13.100", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 s"));
620 helper("13.100", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
621 new QuantityType<>("-2147483648 s"));
622 helper("13.100", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
623 new QuantityType<>("2147483647 s"));
625 helper("13.1200", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 l"));
626 helper("13.1200", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
627 new QuantityType<>("-2147483648 l"));
628 helper("13.1200", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
629 new QuantityType<>("2147483647 l"));
630 helper("13.1201", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 m³"));
631 helper("13.1201", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
632 new QuantityType<>("-2147483648 m³"));
633 helper("13.1201", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
634 new QuantityType<>("2147483647 m³"));
639 helper("14.000", new byte[] { (byte) 0x3f, (byte) 0x80, 0, 0 }, new QuantityType<>("1 m/s²"));
640 helper("14.000", F32_MINUS_ONE, new QuantityType<>("-1 m/s²"));
641 helper("14.001", F32_MINUS_ONE, new QuantityType<>("-1 rad/s²"));
642 helper("14.002", F32_MINUS_ONE, new QuantityType<>("-1 J/mol"));
643 helper("14.003", F32_MINUS_ONE, new QuantityType<>("-1 /s"));
644 helper("14.004", F32_MINUS_ONE, new QuantityType<>("-1 mol"));
645 helper("14.005", F32_MINUS_ONE, new DecimalType("-1"));
646 helper("14.006", F32_MINUS_ONE, new QuantityType<>("-1 rad"));
647 helper("14.007", F32_MINUS_ONE, new QuantityType<>("-1 °"));
648 helper("14.008", F32_MINUS_ONE, new QuantityType<>("-1 J*s"));
649 helper("14.009", F32_MINUS_ONE, new QuantityType<>("-1 rad/s"));
650 helper("14.010", F32_MINUS_ONE, new QuantityType<>("-1 m²"));
651 helper("14.011", F32_MINUS_ONE, new QuantityType<>("-1 F"));
652 helper("14.012", F32_MINUS_ONE, new QuantityType<>("-1 C/m²"));
653 helper("14.013", F32_MINUS_ONE, new QuantityType<>("-1 C/m³"));
654 helper("14.014", F32_MINUS_ONE, new QuantityType<>("-1 m²/N"));
655 helper("14.015", F32_MINUS_ONE, new QuantityType<>("-1 S"));
656 helper("14.016", F32_MINUS_ONE, new QuantityType<>("-1 S/m"));
657 helper("14.017", F32_MINUS_ONE, new QuantityType<>("-1 kg/m³"));
658 helper("14.018", F32_MINUS_ONE, new QuantityType<>("-1 C"));
659 helper("14.019", F32_MINUS_ONE, new QuantityType<>("-1 A"));
660 helper("14.020", F32_MINUS_ONE, new QuantityType<>("-1 A/m²"));
661 helper("14.021", F32_MINUS_ONE, new QuantityType<>("-1 C*m"));
662 helper("14.022", F32_MINUS_ONE, new QuantityType<>("-1 C/m²"));
663 helper("14.023", F32_MINUS_ONE, new QuantityType<>("-1 V/m"));
664 helper("14.024", F32_MINUS_ONE, new QuantityType<>("-1 V*m")); // SI unit is Vm
665 helper("14.025", F32_MINUS_ONE, new QuantityType<>("-1 C/m²"));
666 helper("14.026", F32_MINUS_ONE, new QuantityType<>("-1 C/m²"));
667 helper("14.027", F32_MINUS_ONE, new QuantityType<>("-1 V"));
668 helper("14.028", F32_MINUS_ONE, new QuantityType<>("-1 V"));
669 helper("14.029", F32_MINUS_ONE, new QuantityType<>("-1 A*m²"));
670 helper("14.030", F32_MINUS_ONE, new QuantityType<>("-1 V"));
671 helper("14.031", F32_MINUS_ONE, new QuantityType<>("-1 J"));
672 helper("14.032", F32_MINUS_ONE, new QuantityType<>("-1 N"));
673 helper("14.033", F32_MINUS_ONE, new QuantityType<>("-1 /s"));
674 helper("14.034", F32_MINUS_ONE, new QuantityType<>("-1 rad/s"));
675 helper("14.035", F32_MINUS_ONE, new QuantityType<>("-1 J/K"));
676 helper("14.036", F32_MINUS_ONE, new QuantityType<>("-1 W"));
677 helper("14.037", F32_MINUS_ONE, new QuantityType<>("-1 J"));
678 helper("14.038", F32_MINUS_ONE, new QuantityType<>("-1 Ohm"));
679 helper("14.039", F32_MINUS_ONE, new QuantityType<>("-1 m"));
680 helper("14.040", F32_MINUS_ONE, new QuantityType<>("-1 J"));
681 helper("14.041", F32_MINUS_ONE, new QuantityType<>("-1 cd/m²"));
682 helper("14.042", F32_MINUS_ONE, new QuantityType<>("-1 lm"));
683 helper("14.043", F32_MINUS_ONE, new QuantityType<>("-1 cd"));
684 helper("14.044", F32_MINUS_ONE, new QuantityType<>("-1 A/m"));
685 helper("14.045", F32_MINUS_ONE, new QuantityType<>("-1 Wb"));
686 helper("14.046", F32_MINUS_ONE, new QuantityType<>("-1 T"));
687 helper("14.047", F32_MINUS_ONE, new QuantityType<>("-1 A*m²"));
688 helper("14.048", F32_MINUS_ONE, new QuantityType<>("-1 T"));
689 helper("14.049", F32_MINUS_ONE, new QuantityType<>("-1 A/m"));
690 helper("14.050", F32_MINUS_ONE, new QuantityType<>("-1 A"));
691 helper("14.051", F32_MINUS_ONE, new QuantityType<>("-1 kg"));
692 helper("14.052", F32_MINUS_ONE, new QuantityType<>("-1 kg/s"));
693 helper("14.053", F32_MINUS_ONE, new QuantityType<>("-1 N/s"));
694 helper("14.054", F32_MINUS_ONE, new QuantityType<>("-1 rad"));
695 helper("14.055", F32_MINUS_ONE, new QuantityType<>("-1 °"));
696 helper("14.056", F32_MINUS_ONE, new QuantityType<>("-1 W"));
697 helper("14.057", F32_MINUS_ONE, new DecimalType("-1"));
698 helper("14.058", F32_MINUS_ONE, new QuantityType<>("-1 Pa"));
699 helper("14.059", F32_MINUS_ONE, new QuantityType<>("-1 Ohm"));
700 helper("14.060", F32_MINUS_ONE, new QuantityType<>("-1 Ohm"));
701 helper("14.061", F32_MINUS_ONE, new QuantityType<>("-1 Ohm*m"));
702 helper("14.062", F32_MINUS_ONE, new QuantityType<>("-1 H"));
703 helper("14.063", F32_MINUS_ONE, new QuantityType<>("-1 sr"));
704 helper("14.064", F32_MINUS_ONE, new QuantityType<>("-1 W/m²"));
705 helper("14.065", F32_MINUS_ONE, new QuantityType<>("-1 m/s"));
706 helper("14.066", F32_MINUS_ONE, new QuantityType<>("-1 Pa"));
707 helper("14.067", F32_MINUS_ONE, new QuantityType<>("-1 N/m"));
708 helper("14.068", new byte[] { (byte) 0x3f, (byte) 0x80, 0, 0 }, new QuantityType<>("1 °C"));
709 helper("14.068", F32_MINUS_ONE, new QuantityType<>("-1 °C"));
710 helper("14.069", F32_MINUS_ONE, new QuantityType<>("-1 K"));
711 helper("14.070", F32_MINUS_ONE, new QuantityType<>("-1 K"));
712 helper("14.071", F32_MINUS_ONE, new QuantityType<>("-1 J/K"));
713 helper("14.072", F32_MINUS_ONE, new QuantityType<>("-1 W/m/K"));
714 helper("14.073", F32_MINUS_ONE, new QuantityType<>("-1 V/K"));
715 helper("14.074", F32_MINUS_ONE, new QuantityType<>("-1 s"));
716 helper("14.075", F32_MINUS_ONE, new QuantityType<>("-1 N*m"));
717 helper("14.076", F32_MINUS_ONE, new QuantityType<>("-1 m³"));
718 helper("14.077", F32_MINUS_ONE, new QuantityType<>("-1 m³/s"));
719 helper("14.078", F32_MINUS_ONE, new QuantityType<>("-1 N"));
720 helper("14.079", F32_MINUS_ONE, new QuantityType<>("-1 J"));
721 helper("14.080", F32_MINUS_ONE, new QuantityType<>("-1 VA"));
723 helper("14.1200", F32_MINUS_ONE, new QuantityType<>("-1 m³/h"));
724 helper("14.1201", F32_MINUS_ONE, new QuantityType<>("-1 l/s"));
729 helper("16.000", new byte[] { (byte) 0x4B, (byte) 0x4E, 0x58, 0x20, 0x69, 0x73, 0x20, (byte) 0x4F, (byte) 0x4B,
730 0x0, 0x0, 0x0, 0x0, 0x0 }, new StringType("KNX is OK"));
731 helper("16.001", new byte[] { (byte) 0x4B, (byte) 0x4E, 0x58, 0x20, 0x69, 0x73, 0x20, (byte) 0x4F, (byte) 0x4B,
732 0x0, 0x0, 0x0, 0x0, 0x0 }, new StringType("KNX is OK"));
737 helper("17.001", new byte[] { 0 }, new DecimalType(0));
738 helper("17.001", new byte[] { 42 }, new DecimalType(42));
739 helper("17.001", new byte[] { 63 }, new DecimalType(63));
744 // scene, activate 0..63
745 helper("18.001", new byte[] { 0 }, new DecimalType(0));
746 helper("18.001", new byte[] { 42 }, new DecimalType(42));
747 helper("18.001", new byte[] { 63 }, new DecimalType(63));
748 // scene, learn += 0x80
749 helper("18.001", new byte[] { (byte) (0x80 + 0) }, new DecimalType(0x80));
750 helper("18.001", new byte[] { (byte) (0x80 + 42) }, new DecimalType(0x80 + 42));
751 helper("18.001", new byte[] { (byte) (0x80 + 63) }, new DecimalType(0x80 + 63));
756 // 2019-01-15 17:30:00
757 helper("19.001", new byte[] { (byte) (2019 - 1900), 1, 15, 17, 30, 0, (byte) 0x25, (byte) 0x00 },
758 new DateTimeType("2019-01-15T17:30:00"));
759 helper("19.001", new byte[] { (byte) (2019 - 1900), 1, 15, 17, 30, 0, (byte) 0x24, (byte) 0x00 },
760 new DateTimeType("2019-01-15T17:30:00"));
761 // 2019-07-15 17:30:00
762 helper("19.001", new byte[] { (byte) (2019 - 1900), 7, 15, 17, 30, 0, (byte) 0x25, (byte) 0x00 },
763 new DateTimeType("2019-07-15T17:30:00"), new byte[0], new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 });
764 helper("19.001", new byte[] { (byte) (2019 - 1900), 7, 15, 17, 30, 0, (byte) 0x24, (byte) 0x00 },
765 new DateTimeType("2019-07-15T17:30:00"), new byte[0], new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 });
770 // test default String representation of enum (incomplete)
771 helper("20.001", new byte[] { 0 }, new StringType("autonomous"));
772 helper("20.001", new byte[] { 1 }, new StringType("slave"));
773 helper("20.001", new byte[] { 2 }, new StringType("master"));
775 helper("20.002", new byte[] { 0 }, new StringType("building in use"));
776 helper("20.002", new byte[] { 1 }, new StringType("building not used"));
777 helper("20.002", new byte[] { 2 }, new StringType("building protection"));
779 // test DecimalType representation of enum
780 int[] subTypes = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 17, 20, 21, 100, 101, 102, 103, 104, 105,
781 106, 107, 108, 109, 110, 111, 112, 113, 114, 120, 121, 122, 600, 601, 602, 603, 604, 605, 606, 607, 608,
782 609, 610, 801, 802, 803, 804, 1000, 1001, 1002, 1003, 1004, 1005, 1200, 1202 };
783 for (int subType : subTypes) {
784 helper("20." + String.format("%03d", subType), new byte[] { 1 }, new DecimalType(1));
786 // once these DPTs are available in Calimero, add to check above
787 int[] unsupportedSubTypes = new int[] { 22, 115, 611, 612, 613, 1203, 1204, 1205, 1206, 1207, 1208, 1209 };
788 for (int subType : unsupportedSubTypes) {
789 assertNull(ValueDecoder.decode("20." + String.format("%03d", subType), new byte[] { 0 }, StringType.class));
795 // test default String representation of bitfield (incomplete)
796 helper("21.001", new byte[] { 5 }, new StringType("overridden, out of service"));
798 // test DecimalType representation of bitfield
799 int[] subTypes = new int[] { 1, 2, 100, 101, 102, 103, 104, 105, 106, 601, 1000, 1001, 1002, 1010 };
800 for (int subType : subTypes) {
801 helper("21." + String.format("%03d", subType), new byte[] { 1 }, new DecimalType(1));
803 // once these DPTs are available in Calimero, add to check above
804 assertNull(ValueDecoder.decode("21.1200", new byte[] { 0 }, StringType.class));
805 assertNull(ValueDecoder.decode("21.1201", new byte[] { 0 }, StringType.class));
810 // test default String representation of bitfield (incomplete)
811 helper("22.101", new byte[] { 1, 0 }, new StringType("heating mode"));
812 helper("22.101", new byte[] { 1, 2 }, new StringType("heating mode, heating eco mode"));
814 // test DecimalType representation of bitfield
815 helper("22.101", new byte[] { 0, 2 }, new DecimalType(2));
816 helper("22.1000", new byte[] { 0, 2 }, new DecimalType(2));
817 // once these DPTs are available in Calimero, add to check above
818 assertNull(ValueDecoder.decode("22.100", new byte[] { 0, 2 }, StringType.class));
819 assertNull(ValueDecoder.decode("22.1010", new byte[] { 0, 2 }, StringType.class));
824 // null terminated strings, UTF8
825 helper("28.001", new byte[] { 0x31, 0x32, 0x33, 0x34, 0x0 }, new StringType("1234"));
826 helper("28.001", new byte[] { (byte) 0xce, (byte) 0xb5, 0x34, 0x0 }, new StringType("\u03b54"));
831 helper("29.010", new byte[] { 0, 0, 0, 0, 0, 0, 0, 42 }, new QuantityType<>("42 Wh"));
832 helper("29.010", new byte[] { (byte) 0x80, 0, 0, 0, 0, 0, 0, 0 },
833 new QuantityType<>("-9223372036854775808 Wh"));
834 helper("29.010", new byte[] { (byte) 0xff, 0, 0, 0, 0, 0, 0, 0 }, new QuantityType<>("-72057594037927936 Wh"));
835 helper("29.010", new byte[] { 0, 0, 0, 0, 0, 0, 0, 42 }, new QuantityType<>("42 Wh"));
836 helper("29.011", new byte[] { 0, 0, 0, 0, 0, 0, 0, 42 }, new QuantityType<>("42 VAh"));
837 helper("29.012", new byte[] { 0, 0, 0, 0, 0, 0, 0, 42 }, new QuantityType<>("42 varh"));
842 // special DPT for metering, allows several units and different scaling
843 // -> Calimero uses scaling, but always encodes as dimensionless value
844 final int dimensionlessCounter = 0b10111010;
845 helper("229.001", new byte[] { 0, 0, 0, 0, (byte) dimensionlessCounter, 0 }, new DecimalType(0));
849 void testColorDpts() {
851 helper("232.600", new byte[] { 123, 45, 67 }, ColorUtil.rgbToHsb(new int[] { 123, 45, 67 }));
853 helper("232.60000", new byte[] { 123, 45, 67 }, new HSBType("173.6, 17.6, 26.3"));
856 int x = (int) (14.65 * 65535.0 / 100.0);
857 int y = (int) (11.56 * 65535.0 / 100.0);
858 // encoding is always xy and brightness (C+B, 0x03), do not test other combinations
859 helper("242.600", new byte[] { (byte) ((x >> 8) & 0xff), (byte) (x & 0xff), (byte) ((y >> 8) & 0xff),
860 (byte) (y & 0xff), (byte) 0x28, 0x3 }, new HSBType("220,90,50"), new byte[] { 0, 8, 0, 8, 0, 0 },
862 // TODO check brightness
864 // RGBW, only RGB part
865 helper("251.600", new byte[] { 0x26, 0x2b, 0x31, 0x00, 0x00, 0x0e }, new HSBType("207, 23, 19"),
866 new byte[] { 1, 1, 1, 0, 0, 0 }, new byte[0]);
867 // RGBW, only RGB part
868 helper("251.600", new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x00, 0x00, 0x0e },
869 new HSBType("0, 0, 100"), new byte[] { 1, 1, 1, 0, 0, 0 }, new byte[0]);
871 helper("251.600", new byte[] { 0x0, 0x0, 0x0, 0x1A, 0x00, 0x01 }, new PercentType("10.2"));
873 helper("251.60600", new byte[] { (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0xff, 0x00, 0x0f },
874 new HSBType("0, 0, 100"), new byte[] { 1, 1, 1, 2, 0, 0 }, new byte[0]);
876 int[] rgbw = new int[] { 240, 0x0, 0x0, 0x0f };
877 HSBType hsb = ColorUtil.rgbToHsb(rgbw);
878 helper("251.60600", new byte[] { (byte) rgbw[0], (byte) rgbw[1], (byte) rgbw[2], (byte) rgbw[3], 0x00, 0x0f },
879 hsb, new byte[] { 2, 2, 2, 2, 0, 0 }, new byte[0]);
883 void testColorTransitionDpts() {
884 // DPT 243.600 DPT_Colour_Transition_xyY
885 // time(2) y(2) x(2), %brightness(1), flags(1)
886 helper("243.600", new byte[] { 0, 5, 0x7F, 0, (byte) 0xfe, 0, 42, 3 },
887 new StringType("(0.9922, 0.4961) 16.5 % 0.5 s"));
888 // DPT 249.600 DPT_Brightness_Colour_Temperature_Transition
889 // time(2) colortemp(2), brightness(1), flags(1)
890 helper("249.600", new byte[] { 0, 5, 0, 40, 127, 7 }, new StringType("49.8 % 40 K 0.5 s"));
891 // DPT 250.600 DPT_Brightness_Colour_Temperature_Control
892 // cct(1) cb(1) flags(1)
893 helper("250.600", new byte[] { 0x0f, 0x0e, 3 }, new StringType("CT increase 7 steps BRT increase 6 steps"));
894 // DPT 252.600 DPT_Relative_Control_RGBW
895 // r(1) g(1) b(1) w(1) flags(1)
896 helper("252.600", new byte[] { 0x0f, 0x0e, 0x0d, 0x0c, 0x0f },
897 new StringType("R increase 7 steps G increase 6 steps B increase 5 steps W increase 4 steps"));
898 // DPT 253.600 DPT_Relative_Control_xyY
899 // cs(1) ct(1) cb(1) flags(1)
900 helper("253.600", new byte[] { 0x0f, 0x0e, 0x0d, 0x7 },
901 new StringType("x increase 7 steps y increase 6 steps Y increase 5 steps"));
902 // DPT 254.600 DPT_Relative_Control_RGB
904 helper("254.600", new byte[] { 0x0f, 0x0e, 0x0d },
905 new StringType("R increase 7 steps G increase 6 steps B increase 5 steps"));
910 static void checkForMissingMainTypes() {
911 // checks if we have itests for all main DPT types supported by Calimero library,
912 // data is collected within method helper()
913 var wrapper = new Object() {
914 boolean testsMissing = false;
916 TranslatorTypes.getAllMainTypes().forEach((i, t) -> {
917 if (!dptTested.contains(i)) {
918 LOGGER.warn("missing tests for main DPT type " + i);
919 wrapper.testsMissing = true;
922 assertEquals(false, wrapper.testsMissing, "add tests for new DPT main types");