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[] { (byte) (subType % 4) },
304 new DecimalType(subType % 4));
310 // DPT 3.007 and DPT 3.008 consist of a control bit (1 bit) and stepsize (3 bit)
311 // if stepsize is 0, OH will ignore the command
312 byte controlBit = 1 << 3;
313 // loop all other step sizes and check only the control bit
314 for (byte i = 1; i < 8; i++) {
315 helper("3.007", new byte[] { i }, IncreaseDecreaseType.DECREASE, new byte[0], new byte[] { controlBit });
316 helper("3.007", new byte[] { (byte) (i + controlBit) }, IncreaseDecreaseType.INCREASE, new byte[0],
317 new byte[] { controlBit });
318 helper("3.008", new byte[] { i }, UpDownType.UP, new byte[0], new byte[] { controlBit });
319 helper("3.008", new byte[] { (byte) (i + controlBit) }, UpDownType.DOWN, new byte[0],
320 new byte[] { controlBit });
323 // check if OH ignores incoming frames with mask 0 (mapped to UndefType)
324 Assertions.assertFalse(ValueDecoder.decode("3.007", new byte[] { 0 },
325 IncreaseDecreaseType.class) instanceof IncreaseDecreaseType);
326 Assertions.assertFalse(ValueDecoder.decode("3.007", new byte[] { controlBit },
327 IncreaseDecreaseType.class) instanceof IncreaseDecreaseType);
328 Assertions.assertFalse(ValueDecoder.decode("3.008", new byte[] { 0 }, UpDownType.class) instanceof UpDownType);
329 Assertions.assertFalse(
330 ValueDecoder.decode("3.008", new byte[] { controlBit }, UpDownType.class) instanceof UpDownType);
335 helper("5.001", new byte[] { 0 }, new QuantityType<>("0 %"));
336 helper("5.001", new byte[] { (byte) 0xff }, new QuantityType<>("100 %"));
337 // fallback: PercentType
338 helper("5.001", new byte[] { 0 }, new PercentType(0));
339 helper("5.001", new byte[] { (byte) 0x80 }, new PercentType(50));
340 helper("5.001", new byte[] { (byte) 0xff }, new PercentType(100));
342 helper("5.003", new byte[] { 0 }, new QuantityType<>("0 °"));
343 helper("5.003", new byte[] { (byte) 0xff }, new QuantityType<>("360 °"));
344 helper("5.004", new byte[] { 0 }, new QuantityType<>("0 %"));
345 helper("5.004", new byte[] { (byte) 0x64 }, new QuantityType<>("100 %"));
346 helper("5.004", new byte[] { (byte) 0xff }, new QuantityType<>("255 %"));
347 // PercentType cannot encode values >100%, not supported for 5.004
348 helper("5.005", new byte[] { 42 }, new DecimalType(42));
349 helper("5.005", new byte[] { (byte) 0xff }, new DecimalType(255));
350 helper("5.006", new byte[] { 0 }, new DecimalType(0));
351 helper("5.006", new byte[] { 42 }, new DecimalType(42));
352 helper("5.006", new byte[] { (byte) 0xfe }, new DecimalType(254));
354 helper("5.010", new byte[] { 42 }, new DecimalType(42));
355 helper("5.010", new byte[] { (byte) 0xff }, new DecimalType(255));
360 helper("6.001", new byte[] { 0 }, new QuantityType<>("0 %"));
361 helper("6.001", new byte[] { (byte) 0x7f }, new QuantityType<>("127 %"));
362 helper("6.001", new byte[] { (byte) 0xff }, new QuantityType<>("-1 %"));
363 // PercentType cannot encode values >100% or <0%, not supported for 6.001
365 helper("6.010", new byte[] { 0 }, new DecimalType(0));
366 helper("6.010", new byte[] { (byte) 0x7f }, new DecimalType(127));
367 helper("6.010", new byte[] { (byte) 0xff }, new DecimalType(-1));
369 helper("6.020", new byte[] { 9 }, StringType.valueOf("0/0/0/0/1 0"));
374 helper("7.001", new byte[] { 0, 42 }, new DecimalType(42));
375 helper("7.001", new byte[] { (byte) 0xff, (byte) 0xff }, new DecimalType(65535));
376 helper("7.002", new byte[] { (byte) 0x00, (byte) 0xff }, new QuantityType<>("255 ms"));
377 helper("7.002", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 ms"));
378 helper("7.002", new byte[] { (byte) 0xff, (byte) 0xff }, new QuantityType<>("65535 ms"));
379 helper("7.003", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 ms"));
380 helper("7.003", new byte[] { (byte) 0x00, (byte) 0x64 }, new QuantityType<>("1000 ms"));
381 helper("7.003", new byte[] { (byte) 0x00, (byte) 0xff }, new QuantityType<>("2550 ms"));
382 helper("7.003", new byte[] { (byte) 0xff, (byte) 0xff }, new QuantityType<>("655350 ms"));
383 helper("7.004", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 ms"));
384 helper("7.004", new byte[] { (byte) 0x00, (byte) 0xff }, new QuantityType<>("25500 ms"));
385 helper("7.004", new byte[] { (byte) 0xff, (byte) 0xff }, new QuantityType<>("6553500 ms"));
386 helper("7.005", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 s"));
387 helper("7.005", new byte[] { (byte) 0x00, (byte) 0xff }, new QuantityType<>("255 s"));
388 helper("7.005", new byte[] { (byte) 0xff, (byte) 0xff }, new QuantityType<>("65535 s"));
389 helper("7.006", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 min"));
390 helper("7.006", new byte[] { (byte) 0x00, (byte) 0xff }, new QuantityType<>("255 min"));
391 helper("7.006", new byte[] { (byte) 0xff, (byte) 0xff }, new QuantityType<>("65535 min"));
392 helper("7.006", new byte[] { (byte) 0xff, (byte) 0xff }, new QuantityType<>("3932100 s"));
393 helper("7.007", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 h"));
394 helper("7.007", new byte[] { (byte) 0x00, (byte) 0xff }, new QuantityType<>("255 h"));
395 helper("7.007", new byte[] { (byte) 0x00, (byte) 0xff }, new QuantityType<>("918000 s"));
396 helper("7.007", new byte[] { (byte) 0xff, (byte) 0xff }, new QuantityType<>("65535 h"));
398 helper("7.010", new byte[] { 0, 42 }, new DecimalType(42));
399 helper("7.010", new byte[] { (byte) 0xff, (byte) 0xff }, new DecimalType(65535));
400 helper("7.011", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 mm"));
401 helper("7.011", new byte[] { (byte) 0x00, (byte) 0xff }, new QuantityType<>("255 mm"));
402 helper("7.011", new byte[] { (byte) 0xff, (byte) 0xff }, new QuantityType<>("65535 mm"));
403 helper("7.012", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 mA"));
404 helper("7.012", new byte[] { (byte) 0x00, (byte) 0xff }, new QuantityType<>("255 mA"));
405 helper("7.012", new byte[] { (byte) 0xff, (byte) 0xff }, new QuantityType<>("65535 mA"));
406 helper("7.013", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 lx"));
407 helper("7.013", new byte[] { (byte) 0x00, (byte) 0xff }, new QuantityType<>("255 lx"));
408 helper("7.013", new byte[] { (byte) 0xff, (byte) 0xff }, new QuantityType<>("65535 lx"));
410 helper("7.600", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 K"));
411 helper("7.600", new byte[] { (byte) 0x00, (byte) 0xff }, new QuantityType<>("255 K"));
412 helper("7.600", new byte[] { (byte) 0xff, (byte) 0xff }, new QuantityType<>("65535 K"));
417 helper("8.001", new byte[] { (byte) 0x7f, (byte) 0xff }, new DecimalType(32767));
418 helper("8.001", new byte[] { (byte) 0x80, (byte) 0x00 }, new DecimalType(-32768));
419 helper("8.002", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 ms"));
420 helper("8.002", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 ms"));
421 helper("8.002", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 ms"));
422 helper("8.003", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-327680 ms"));
423 helper("8.003", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("327670 ms"));
424 helper("8.003", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 ms"));
425 helper("8.004", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-3276800 ms"));
426 helper("8.004", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("3276700 ms"));
427 helper("8.004", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 ms"));
428 helper("8.005", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 s"));
429 helper("8.005", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 s"));
430 helper("8.005", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 s"));
431 helper("8.006", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 min"));
432 helper("8.006", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 min"));
433 helper("8.006", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 min"));
434 helper("8.007", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 h"));
435 helper("8.007", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 h"));
436 helper("8.007", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 h"));
438 helper("8.010", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-327.68 %"));
439 helper("8.011", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 °"));
440 helper("8.011", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 °"));
441 helper("8.011", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 °"));
442 helper("8.012", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 m"));
443 helper("8.012", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 m"));
444 helper("8.012", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 m"));
449 // special float with sign, 4-bit exponent, and mantissa in two's complement notation
450 // ref: KNX spec, 03_07_02-Datapoint-Types
451 // FIXME according to spec, value 0x7fff shall be regarded as "invalid data"
452 // TODO add tests for clipping at lower boundary (e.g. absolute zero)
453 helper("9.001", new byte[] { (byte) 0x00, (byte) 0x64 }, new QuantityType<>("1 °C"));
454 helper("9.001", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 °C"));
455 helper("9.001", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 °C"));
456 helper("9.001", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 °C"));
457 // lower values than absolute zero will be set to abs. zero (-273 °C)
458 // helper("9.001", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-273 °C"));
459 helper("9.002", new byte[] { (byte) 0x00, (byte) 0x64 }, new QuantityType<>("1 K"));
460 helper("9.002", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 K"));
461 helper("9.002", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 K"));
462 helper("9.002", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 K"));
463 helper("9.002", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 K"));
464 helper("9.003", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 K/h"));
465 helper("9.003", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 K/h"));
466 helper("9.003", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 K/h"));
467 helper("9.003", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 K/h"));
468 helper("9.004", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 lx"));
469 helper("9.004", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 lx"));
470 helper("9.004", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 lx"));
471 // no negative values allowed for DPTs 9.004-9.008
472 helper("9.005", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 m/s"));
473 helper("9.005", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 m/s"));
474 helper("9.005", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 m/s"));
475 helper("9.005", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 m/s"));
476 helper("9.005", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 m/s"));
477 helper("9.005", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 m/s"));
478 helper("9.006", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 Pa"));
479 helper("9.006", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 Pa"));
480 helper("9.006", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 Pa"));
481 helper("9.007", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 %"));
482 helper("9.007", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 %"));
483 helper("9.007", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 %"));
484 helper("9.008", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 ppm"));
485 helper("9.008", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 ppm"));
486 helper("9.008", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 ppm"));
487 helper("9.009", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 m³/h"));
488 helper("9.009", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 m³/h"));
489 helper("9.009", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 m³/h"));
490 helper("9.009", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 m³/h"));
491 helper("9.010", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 s"));
492 helper("9.010", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 s"));
493 helper("9.010", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 s"));
494 helper("9.010", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 s"));
495 helper("9.011", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 ms"));
496 helper("9.011", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 ms"));
497 helper("9.011", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 ms"));
498 helper("9.011", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 ms"));
500 helper("9.020", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 mV"));
501 helper("9.020", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 mV"));
502 helper("9.020", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 mV"));
503 helper("9.020", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 mV"));
504 helper("9.021", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 mA"));
505 helper("9.021", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 mA"));
506 helper("9.021", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 mA"));
507 helper("9.021", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 mA"));
508 helper("9.022", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 W/m²"));
509 helper("9.022", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 W/m²"));
510 helper("9.022", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 W/m²"));
511 helper("9.022", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 W/m²"));
512 helper("9.023", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 K/%"));
513 helper("9.023", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 K/%"));
514 helper("9.023", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 K/%"));
515 helper("9.023", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 K/%"));
516 helper("9.024", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 kW"));
517 helper("9.024", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 kW"));
518 helper("9.024", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 kW"));
519 helper("9.024", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 kW"));
520 helper("9.025", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 l/h"));
521 helper("9.025", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 l/h"));
522 helper("9.025", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 l/h"));
523 helper("9.025", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 l/h"));
524 helper("9.026", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 l/m²"));
525 helper("9.026", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 l/m²"));
526 helper("9.026", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 l/m²"));
527 helper("9.026", new byte[] { (byte) 0xf8, (byte) 0x00 }, new QuantityType<>("-671088.64 l/m²"));
528 helper("9.027", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 °F"));
529 helper("9.027", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 °F"));
530 helper("9.027", new byte[] { (byte) 0x87, (byte) 0x9c }, new QuantityType<>("-1 °F"));
531 // lower values than absolute zero will be set to abs. zero (-459.6 °F)
532 helper("9.028", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 km/h"));
533 helper("9.028", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 km/h"));
534 helper("9.028", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 km/h"));
535 // no negative values allowed for DPTs 9.028-9.030
536 helper("9.029", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 g/m³"));
537 helper("9.029", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 g/m³"));
538 helper("9.029", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 g/m³"));
539 helper("9.030", new byte[] { (byte) 0x07, (byte) 0xff }, new QuantityType<>("20.47 µg/m³"));
540 helper("9.030", new byte[] { (byte) 0x7f, (byte) 0xfe }, new QuantityType<>("670433.28 µg/m³"));
541 helper("9.030", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 µg/m³"));
546 // TODO check handling of DPT10: date is not set to current date, but 1970-01-01 + offset if day is given
547 // maybe we should change the semantics and use current date + offset if day is given
549 // note: local timezone is set when creating DateTimeType, for example "1970-01-01Thh:mm:ss.000+0100"
553 .toString(ValueDecoder.decode("10.001", new byte[] { (byte) 0x11, (byte) 0x1e, 0 }, DecimalType.class))
554 .startsWith("1970-01-01T17:30:00.000+"));
555 // Thursday, this is correct for 1970-01-01
557 .toString(ValueDecoder.decode("10.001", new byte[] { (byte) 0x91, (byte) 0x1e, 0 }, DecimalType.class))
558 .startsWith("1970-01-01T17:30:00.000+"));
559 // Monday -> 1970-01-05
561 .toString(ValueDecoder.decode("10.001", new byte[] { (byte) 0x31, (byte) 0x1e, 0 }, DecimalType.class))
562 .startsWith("1970-01-05T17:30:00.000+"));
564 // Thursday, otherwise first byte of encoded data will not match
565 helper("10.001", new byte[] { (byte) 0x91, (byte) 0x1e, (byte) 0x0 }, new DateTimeType("17:30:00"));
566 helper("10.001", new byte[] { (byte) 0x11, (byte) 0x1e, (byte) 0x0 }, new DateTimeType("17:30:00"), new byte[0],
567 new byte[] { (byte) 0x1f, (byte) 0xff, (byte) 0xff });
572 // note: local timezone and dst is set when creating DateTimeType, for example "2019-06-12T00:00:00.000+0200"
573 helper("11.001", new byte[] { (byte) 12, 6, 19 }, new DateTimeType("2019-06-12"));
578 helper("12.001", new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xfe },
579 new DecimalType("4294967294"));
580 helper("12.100", new byte[] { 0, 0, 0, 60 }, new QuantityType<>("60 s"));
581 helper("12.100", new byte[] { 0, 0, 0, 60 }, new QuantityType<>("1 min"));
582 helper("12.101", new byte[] { 0, 0, 0, 60 }, new QuantityType<>("60 min"));
583 helper("12.101", new byte[] { 0, 0, 0, 60 }, new QuantityType<>("1 h"));
584 helper("12.102", new byte[] { 0, 0, 0, 1 }, new QuantityType<>("1 h"));
585 helper("12.102", new byte[] { 0, 0, 0, 1 }, new QuantityType<>("60 min"));
587 helper("12.1200", new byte[] { 0, 0, 0, 1 }, new QuantityType<>("1 l"));
588 helper("12.1200", new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xfe },
589 new QuantityType<>("4294967294 l"));
590 helper("12.1201", new byte[] { 0, 0, 0, 1 }, new QuantityType<>("1 m³"));
591 helper("12.1201", new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xfe },
592 new QuantityType<>("4294967294 m³"));
597 helper("13.001", new byte[] { 0, 0, 0, 0 }, new DecimalType(0));
598 helper("13.001", new byte[] { 0, 0, 0, 42 }, new DecimalType(42));
599 helper("13.001", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
600 new DecimalType(2147483647));
601 // KNX representation typically uses two's complement
602 helper("13.001", new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }, new DecimalType(-1));
603 helper("13.001", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 }, new DecimalType(-2147483648));
604 helper("13.002", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 m³/h"));
605 helper("13.002", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
606 new QuantityType<>("-2147483648 m³/h"));
607 helper("13.002", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
608 new QuantityType<>("2147483647 m³/h"));
610 helper("13.010", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 Wh"));
611 helper("13.010", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
612 new QuantityType<>("-2147483648 Wh"));
613 helper("13.010", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
614 new QuantityType<>("2147483647 Wh"));
615 helper("13.011", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 VAh"));
616 helper("13.011", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
617 new QuantityType<>("-2147483648 VAh"));
618 helper("13.011", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
619 new QuantityType<>("2147483647 VAh"));
620 helper("13.012", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 varh"));
621 helper("13.012", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
622 new QuantityType<>("-2147483648 varh"));
623 helper("13.012", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
624 new QuantityType<>("2147483647 varh"));
625 helper("13.013", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 kWh"));
626 helper("13.013", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
627 new QuantityType<>("-2147483648 kWh"));
628 helper("13.013", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
629 new QuantityType<>("2147483647 kWh"));
630 helper("13.014", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 VAh"));
631 helper("13.014", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
632 new QuantityType<>("-2147483648000 VAh"));
633 helper("13.014", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
634 new QuantityType<>("2147483647000 VAh"));
635 helper("13.015", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 kvarh"));
636 helper("13.015", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
637 new QuantityType<>("-2147483648 kvarh"));
638 helper("13.015", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
639 new QuantityType<>("2147483647 kvarh"));
640 helper("13.016", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 MWh"));
641 helper("13.016", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
642 new QuantityType<>("-2147483648 MWh"));
643 helper("13.016", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
644 new QuantityType<>("2147483647 MWh"));
646 helper("13.100", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 s"));
647 helper("13.100", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
648 new QuantityType<>("-2147483648 s"));
649 helper("13.100", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
650 new QuantityType<>("2147483647 s"));
652 helper("13.1200", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 l"));
653 helper("13.1200", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
654 new QuantityType<>("-2147483648 l"));
655 helper("13.1200", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
656 new QuantityType<>("2147483647 l"));
657 helper("13.1201", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 m³"));
658 helper("13.1201", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 },
659 new QuantityType<>("-2147483648 m³"));
660 helper("13.1201", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff },
661 new QuantityType<>("2147483647 m³"));
666 helper("14.000", new byte[] { (byte) 0x3f, (byte) 0x80, 0, 0 }, new QuantityType<>("1 m/s²"));
667 helper("14.000", F32_MINUS_ONE, new QuantityType<>("-1 m/s²"));
668 helper("14.001", F32_MINUS_ONE, new QuantityType<>("-1 rad/s²"));
669 helper("14.002", F32_MINUS_ONE, new QuantityType<>("-1 J/mol"));
670 helper("14.003", F32_MINUS_ONE, new QuantityType<>("-1 /s"));
671 helper("14.004", F32_MINUS_ONE, new QuantityType<>("-1 mol"));
672 helper("14.005", F32_MINUS_ONE, new DecimalType("-1"));
673 helper("14.006", F32_MINUS_ONE, new QuantityType<>("-1 rad"));
674 helper("14.007", F32_MINUS_ONE, new QuantityType<>("-1 °"));
675 helper("14.008", F32_MINUS_ONE, new QuantityType<>("-1 J*s"));
676 helper("14.009", F32_MINUS_ONE, new QuantityType<>("-1 rad/s"));
677 helper("14.010", F32_MINUS_ONE, new QuantityType<>("-1 m²"));
678 helper("14.011", F32_MINUS_ONE, new QuantityType<>("-1 F"));
679 helper("14.012", F32_MINUS_ONE, new QuantityType<>("-1 C/m²"));
680 helper("14.013", F32_MINUS_ONE, new QuantityType<>("-1 C/m³"));
681 helper("14.014", F32_MINUS_ONE, new QuantityType<>("-1 m²/N"));
682 helper("14.015", F32_MINUS_ONE, new QuantityType<>("-1 S"));
683 helper("14.016", F32_MINUS_ONE, new QuantityType<>("-1 S/m"));
684 helper("14.017", F32_MINUS_ONE, new QuantityType<>("-1 kg/m³"));
685 helper("14.018", F32_MINUS_ONE, new QuantityType<>("-1 C"));
686 helper("14.019", F32_MINUS_ONE, new QuantityType<>("-1 A"));
687 helper("14.020", F32_MINUS_ONE, new QuantityType<>("-1 A/m²"));
688 helper("14.021", F32_MINUS_ONE, new QuantityType<>("-1 C*m"));
689 helper("14.022", F32_MINUS_ONE, new QuantityType<>("-1 C/m²"));
690 helper("14.023", F32_MINUS_ONE, new QuantityType<>("-1 V/m"));
691 helper("14.024", F32_MINUS_ONE, new QuantityType<>("-1 V*m")); // SI unit is Vm
692 helper("14.025", F32_MINUS_ONE, new QuantityType<>("-1 C/m²"));
693 helper("14.026", F32_MINUS_ONE, new QuantityType<>("-1 C/m²"));
694 helper("14.027", F32_MINUS_ONE, new QuantityType<>("-1 V"));
695 helper("14.028", F32_MINUS_ONE, new QuantityType<>("-1 V"));
696 helper("14.029", F32_MINUS_ONE, new QuantityType<>("-1 A*m²"));
697 helper("14.030", F32_MINUS_ONE, new QuantityType<>("-1 V"));
698 helper("14.031", F32_MINUS_ONE, new QuantityType<>("-1 J"));
699 helper("14.032", F32_MINUS_ONE, new QuantityType<>("-1 N"));
700 helper("14.033", F32_MINUS_ONE, new QuantityType<>("-1 /s"));
701 helper("14.034", F32_MINUS_ONE, new QuantityType<>("-1 rad/s"));
702 helper("14.035", F32_MINUS_ONE, new QuantityType<>("-1 J/K"));
703 helper("14.036", F32_MINUS_ONE, new QuantityType<>("-1 W"));
704 helper("14.037", F32_MINUS_ONE, new QuantityType<>("-1 J"));
705 helper("14.038", F32_MINUS_ONE, new QuantityType<>("-1 Ohm"));
706 helper("14.039", F32_MINUS_ONE, new QuantityType<>("-1 m"));
707 helper("14.040", F32_MINUS_ONE, new QuantityType<>("-1 J"));
708 helper("14.041", F32_MINUS_ONE, new QuantityType<>("-1 cd/m²"));
709 helper("14.042", F32_MINUS_ONE, new QuantityType<>("-1 lm"));
710 helper("14.043", F32_MINUS_ONE, new QuantityType<>("-1 cd"));
711 helper("14.044", F32_MINUS_ONE, new QuantityType<>("-1 A/m"));
712 helper("14.045", F32_MINUS_ONE, new QuantityType<>("-1 Wb"));
713 helper("14.046", F32_MINUS_ONE, new QuantityType<>("-1 T"));
714 helper("14.047", F32_MINUS_ONE, new QuantityType<>("-1 A*m²"));
715 helper("14.048", F32_MINUS_ONE, new QuantityType<>("-1 T"));
716 helper("14.049", F32_MINUS_ONE, new QuantityType<>("-1 A/m"));
717 helper("14.050", F32_MINUS_ONE, new QuantityType<>("-1 A"));
718 helper("14.051", F32_MINUS_ONE, new QuantityType<>("-1 kg"));
719 helper("14.052", F32_MINUS_ONE, new QuantityType<>("-1 kg/s"));
720 helper("14.053", F32_MINUS_ONE, new QuantityType<>("-1 N/s"));
721 helper("14.054", F32_MINUS_ONE, new QuantityType<>("-1 rad"));
722 helper("14.055", F32_MINUS_ONE, new QuantityType<>("-1 °"));
723 helper("14.056", F32_MINUS_ONE, new QuantityType<>("-1 W"));
724 helper("14.057", F32_MINUS_ONE, new DecimalType("-1"));
725 helper("14.058", F32_MINUS_ONE, new QuantityType<>("-1 Pa"));
726 helper("14.059", F32_MINUS_ONE, new QuantityType<>("-1 Ohm"));
727 helper("14.060", F32_MINUS_ONE, new QuantityType<>("-1 Ohm"));
728 helper("14.061", F32_MINUS_ONE, new QuantityType<>("-1 Ohm*m"));
729 helper("14.062", F32_MINUS_ONE, new QuantityType<>("-1 H"));
730 helper("14.063", F32_MINUS_ONE, new QuantityType<>("-1 sr"));
731 helper("14.064", F32_MINUS_ONE, new QuantityType<>("-1 W/m²"));
732 helper("14.065", F32_MINUS_ONE, new QuantityType<>("-1 m/s"));
733 helper("14.066", F32_MINUS_ONE, new QuantityType<>("-1 Pa"));
734 helper("14.067", F32_MINUS_ONE, new QuantityType<>("-1 N/m"));
735 helper("14.068", new byte[] { (byte) 0x3f, (byte) 0x80, 0, 0 }, new QuantityType<>("1 °C"));
736 helper("14.068", F32_MINUS_ONE, new QuantityType<>("-1 °C"));
737 helper("14.069", F32_MINUS_ONE, new QuantityType<>("-1 K"));
738 helper("14.070", F32_MINUS_ONE, new QuantityType<>("-1 K"));
739 helper("14.071", F32_MINUS_ONE, new QuantityType<>("-1 J/K"));
740 helper("14.072", F32_MINUS_ONE, new QuantityType<>("-1 W/m/K"));
741 helper("14.073", F32_MINUS_ONE, new QuantityType<>("-1 V/K"));
742 helper("14.074", F32_MINUS_ONE, new QuantityType<>("-1 s"));
743 helper("14.075", F32_MINUS_ONE, new QuantityType<>("-1 N*m"));
744 helper("14.076", F32_MINUS_ONE, new QuantityType<>("-1 m³"));
745 helper("14.077", F32_MINUS_ONE, new QuantityType<>("-1 m³/s"));
746 helper("14.078", F32_MINUS_ONE, new QuantityType<>("-1 N"));
747 helper("14.079", F32_MINUS_ONE, new QuantityType<>("-1 J"));
748 helper("14.080", F32_MINUS_ONE, new QuantityType<>("-1 VA"));
750 helper("14.1200", F32_MINUS_ONE, new QuantityType<>("-1 m³/h"));
751 helper("14.1201", F32_MINUS_ONE, new QuantityType<>("-1 l/s"));
756 helper("16.000", new byte[] { (byte) 0x4B, (byte) 0x4E, 0x58, 0x20, 0x69, 0x73, 0x20, (byte) 0x4F, (byte) 0x4B,
757 0x0, 0x0, 0x0, 0x0, 0x0 }, new StringType("KNX is OK"));
758 helper("16.001", new byte[] { (byte) 0x4B, (byte) 0x4E, 0x58, 0x20, 0x69, 0x73, 0x20, (byte) 0x4F, (byte) 0x4B,
759 0x0, 0x0, 0x0, 0x0, 0x0 }, new StringType("KNX is OK"));
764 helper("17.001", new byte[] { 0 }, new DecimalType(0));
765 helper("17.001", new byte[] { 42 }, new DecimalType(42));
766 helper("17.001", new byte[] { 63 }, new DecimalType(63));
771 // scene, activate 0..63
772 helper("18.001", new byte[] { 0 }, new DecimalType(0));
773 helper("18.001", new byte[] { 42 }, new DecimalType(42));
774 helper("18.001", new byte[] { 63 }, new DecimalType(63));
775 // scene, learn += 0x80
776 helper("18.001", new byte[] { (byte) (0x80 + 0) }, new DecimalType(0x80));
777 helper("18.001", new byte[] { (byte) (0x80 + 42) }, new DecimalType(0x80 + 42));
778 helper("18.001", new byte[] { (byte) (0x80 + 63) }, new DecimalType(0x80 + 63));
783 // 2019-01-15 17:30:00
784 helper("19.001", new byte[] { (byte) (2019 - 1900), 1, 15, 17, 30, 0, (byte) 0x25, (byte) 0x00 },
785 new DateTimeType("2019-01-15T17:30:00"));
786 helper("19.001", new byte[] { (byte) (2019 - 1900), 1, 15, 17, 30, 0, (byte) 0x24, (byte) 0x00 },
787 new DateTimeType("2019-01-15T17:30:00"));
788 // 2019-07-15 17:30:00
789 helper("19.001", new byte[] { (byte) (2019 - 1900), 7, 15, 17, 30, 0, (byte) 0x25, (byte) 0x00 },
790 new DateTimeType("2019-07-15T17:30:00"), new byte[0], new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 });
791 helper("19.001", new byte[] { (byte) (2019 - 1900), 7, 15, 17, 30, 0, (byte) 0x24, (byte) 0x00 },
792 new DateTimeType("2019-07-15T17:30:00"), new byte[0], new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 });
793 // TODO add tests for incompletly filled frames (e.g. containing only date or time)
798 // test default String representation of enum (incomplete)
799 helper("20.001", new byte[] { 0 }, new StringType("autonomous"));
800 helper("20.001", new byte[] { 1 }, new StringType("slave"));
801 helper("20.001", new byte[] { 2 }, new StringType("master"));
803 helper("20.002", new byte[] { 0 }, new StringType("building in use"));
804 helper("20.002", new byte[] { 1 }, new StringType("building not used"));
805 helper("20.002", new byte[] { 2 }, new StringType("building protection"));
807 // test DecimalType representation of enum
808 int[] subTypes = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 17, 20, 21, 22, 100, 101, 102, 103, 104,
809 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 120, 121, 122, 600, 601, 602, 603, 604, 605, 606,
810 607, 608, 609, 610, 611, 612, 613, 801, 802, 803, 804, 1000, 1001, 1002, 1003, 1004, 1005, 1200, 1202,
811 1203, 1204, 1205, 1206, 1207, 1208, 1209 };
812 for (int subType : subTypes) {
813 helper("20." + String.format("%03d", subType), new byte[] { 1 }, new DecimalType(1));
815 // once these DPTs are available in Calimero, add to check above
816 int[] unsupportedSubTypes = new int[] {};
817 for (int subType : unsupportedSubTypes) {
818 assertNull(ValueDecoder.decode("20." + String.format("%03d", subType), new byte[] { 0 }, StringType.class));
824 // test default String representation of bitfield (incomplete)
825 helper("21.001", new byte[] { 5 }, new StringType("overridden, out of service"));
827 // test DecimalType representation of bitfield
828 int[] subTypes = new int[] { 1, 2, 100, 101, 102, 103, 104, 105, 106, 601, 1000, 1001, 1002, 1010, 1200, 1201 };
829 for (int subType : subTypes) {
830 helper("21." + String.format("%03d", subType), new byte[] { 1 }, new DecimalType(1));
836 // test default String representation of bitfield (incomplete)
837 helper("22.101", new byte[] { 1, 0 }, new StringType("heating mode"));
838 helper("22.101", new byte[] { 1, 2 }, new StringType("heating mode, heating eco mode"));
840 // test DecimalType representation of bitfield
841 helper("22.100", new byte[] { 0, 2 }, new DecimalType(2));
842 helper("22.101", new byte[] { 0, 2 }, new DecimalType(2));
843 helper("22.1000", new byte[] { 0, 2 }, new DecimalType(2));
844 helper("22.1010", new byte[] { 0, 2 }, new DecimalType(2));
849 // null terminated strings, UTF8
850 helper("28.001", new byte[] { 0x31, 0x32, 0x33, 0x34, 0x0 }, new StringType("1234"));
851 helper("28.001", new byte[] { (byte) 0xce, (byte) 0xb5, 0x34, 0x0 }, new StringType("\u03b54"));
856 helper("29.010", new byte[] { 0, 0, 0, 0, 0, 0, 0, 42 }, new QuantityType<>("42 Wh"));
857 helper("29.010", new byte[] { (byte) 0x80, 0, 0, 0, 0, 0, 0, 0 },
858 new QuantityType<>("-9223372036854775808 Wh"));
859 helper("29.010", new byte[] { (byte) 0xff, 0, 0, 0, 0, 0, 0, 0 }, new QuantityType<>("-72057594037927936 Wh"));
860 helper("29.010", new byte[] { 0, 0, 0, 0, 0, 0, 0, 42 }, new QuantityType<>("42 Wh"));
861 helper("29.011", new byte[] { 0, 0, 0, 0, 0, 0, 0, 42 }, new QuantityType<>("42 VAh"));
862 helper("29.012", new byte[] { 0, 0, 0, 0, 0, 0, 0, 42 }, new QuantityType<>("42 varh"));
867 // special DPT for metering, allows several units and different scaling
868 // -> Calimero uses scaling, but always encodes as dimensionless value
869 final int dimensionlessCounter = 0b10111010;
870 helper("229.001", new byte[] { 0, 0, 0, 0, (byte) dimensionlessCounter, 0 }, new DecimalType(0));
874 void testColorDpts() {
876 helper("232.600", new byte[] { 123, 45, 67 }, ColorUtil.rgbToHsb(new int[] { 123, 45, 67 }));
878 helper("232.60000", new byte[] { 123, 45, 67 }, new HSBType("173.6, 17.6, 26.3"));
881 int x = (int) (14.65 * 65535.0 / 100.0);
882 int y = (int) (11.56 * 65535.0 / 100.0);
883 // encoding is always xy and brightness (C+B, 0x03), do not test other combinations
884 helper("242.600", new byte[] { (byte) ((x >> 8) & 0xff), (byte) (x & 0xff), (byte) ((y >> 8) & 0xff),
885 (byte) (y & 0xff), (byte) 0x28, 0x3 }, new HSBType("220,90,50"), new byte[] { 0, 8, 0, 8, 0, 0 },
887 // TODO check brightness
889 // RGBW, only RGB part
890 helper("251.600", new byte[] { 0x26, 0x2b, 0x31, 0x00, 0x00, 0x0e }, new HSBType("207, 23, 19"),
891 new byte[] { 1, 1, 1, 0, 0, 0 }, new byte[0]);
892 // RGBW, only RGB part
893 helper("251.600", new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x00, 0x00, 0x0e },
894 new HSBType("0, 0, 100"), new byte[] { 1, 1, 1, 0, 0, 0 }, new byte[0]);
896 helper("251.600", new byte[] { 0x0, 0x0, 0x0, 0x1A, 0x00, 0x01 }, new PercentType("10.2"));
898 helper("251.60600", new byte[] { (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0xff, 0x00, 0x0f },
899 new HSBType("0, 0, 100"), new byte[] { 1, 1, 1, 2, 0, 0 }, new byte[0]);
901 int[] rgbw = new int[] { 240, 0x0, 0x0, 0x0f };
902 HSBType hsb = ColorUtil.rgbToHsb(rgbw);
903 helper("251.60600", new byte[] { (byte) rgbw[0], (byte) rgbw[1], (byte) rgbw[2], (byte) rgbw[3], 0x00, 0x0f },
904 hsb, new byte[] { 2, 2, 2, 2, 0, 0 }, new byte[0]);
908 void testColorTransitionDpts() {
909 // DPT 243.600 DPT_Colour_Transition_xyY
910 // time(2) y(2) x(2), %brightness(1), flags(1)
911 helper("243.600", new byte[] { 0, 5, 0x7F, 0, (byte) 0xfe, 0, 42, 3 },
912 new StringType("(0.9922, 0.4961) 16.5 % 500 ms"));
913 helper("243.600", new byte[] { (byte) 0x02, (byte) 0x00, 0x7F, 0, (byte) 0xfe, 0, 42, 3 },
914 new StringType("(0.9922, 0.4961) 16.5 % 51200 ms"));
915 helper("243.600", new byte[] { (byte) 0x40, (byte) 0x00, 0x7F, 0, (byte) 0xfe, 0, 42, 3 },
916 new StringType("(0.9922, 0.4961) 16.5 % 1638400 ms"));
917 helper("243.600", new byte[] { (byte) 0xff, (byte) 0xff, 0x7F, 0, (byte) 0xfe, 0, 42, 3 },
918 new StringType("(0.9922, 0.4961) 16.5 % 6553500 ms"));
919 // DPT 249.600 DPT_Brightness_Colour_Temperature_Transition
920 // time(2) colortemp(2), brightness(1), flags(1)
921 helper("249.600", new byte[] { 0, 5, 0, 40, 127, 7 }, new StringType("49.8 % 40 K 0.5 s"));
922 helper("249.600", new byte[] { (byte) 0xff, (byte) 0xfa, 0, 40, 127, 7 }, new StringType("49.8 % 40 K 6553 s"));
923 helper("249.600", new byte[] { (byte) 0xff, (byte) 0xff, 0, 40, 127, 7 },
924 new StringType("49.8 % 40 K 6553.5 s"));
925 // DPT 250.600 DPT_Brightness_Colour_Temperature_Control
926 // cct(1) cb(1) flags(1)
927 helper("250.600", new byte[] { 0x0f, 0x0e, 3 }, new StringType("CT increase 7 steps BRT increase 6 steps"));
928 // DPT 252.600 DPT_Relative_Control_RGBW
929 // r(1) g(1) b(1) w(1) flags(1)
930 helper("252.600", new byte[] { 0x0f, 0x0e, 0x0d, 0x0c, 0x0f },
931 new StringType("R increase 7 steps G increase 6 steps B increase 5 steps W increase 4 steps"));
932 // DPT 253.600 DPT_Relative_Control_xyY
933 // cs(1) ct(1) cb(1) flags(1)
934 helper("253.600", new byte[] { 0x0f, 0x0e, 0x0d, 0x7 },
935 new StringType("x increase 7 steps y increase 6 steps Y increase 5 steps"));
936 // DPT 254.600 DPT_Relative_Control_RGB
938 helper("254.600", new byte[] { 0x0f, 0x0e, 0x0d },
939 new StringType("R increase 7 steps G increase 6 steps B increase 5 steps"));
944 static void checkForMissingMainTypes() {
945 // checks if we have itests for all main DPT types supported by Calimero library,
946 // data is collected within method helper()
947 var wrapper = new Object() {
948 boolean testsMissing = false;
950 TranslatorTypes.getAllMainTypes().forEach((i, t) -> {
951 if (!dptTested.contains(i)) {
952 LOGGER.warn("missing tests for main DPT type " + i);
953 wrapper.testsMissing = true;
956 assertEquals(false, wrapper.testsMissing, "add tests for new DPT main types");