2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.knx.internal.client;
15 import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
16 import static org.openhab.binding.knx.internal.handler.DeviceConstants.*;
18 import java.util.Collections;
19 import java.util.HashMap;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.knx.internal.handler.Firmware;
26 import org.openhab.binding.knx.internal.handler.Manufacturer;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
30 import tuwien.auto.calimero.DataUnitBuilder;
31 import tuwien.auto.calimero.DeviceDescriptor;
32 import tuwien.auto.calimero.DeviceDescriptor.DD0;
33 import tuwien.auto.calimero.GroupAddress;
34 import tuwien.auto.calimero.IndividualAddress;
35 import tuwien.auto.calimero.mgmt.PropertyAccess.PID;
38 * Client dedicated to read device specific information using the {@link DeviceInfoClient}.
40 * @author Simon Kaufmann - initial contribution and API.
44 public class DeviceInspector {
46 private static final long OPERATION_TIMEOUT = 5000;
47 private static final long OPERATION_INTERVAL = 2000;
49 private final Logger logger = LoggerFactory.getLogger(DeviceInspector.class);
50 private final DeviceInfoClient client;
51 private final IndividualAddress address;
53 public static class Result {
54 private final Map<String, String> properties;
55 private final Set<GroupAddress> groupAddresses;
57 public Result(Map<String, String> properties, Set<GroupAddress> groupAddresses) {
59 this.properties = properties;
60 this.groupAddresses = groupAddresses;
63 public Map<String, String> getProperties() {
67 public Set<GroupAddress> getGroupAddresses() {
68 return groupAddresses;
72 public DeviceInspector(DeviceInfoClient client, IndividualAddress address) {
74 this.address = address;
77 private DeviceInfoClient getClient() {
82 public Result readDeviceInfo() {
83 if (!getClient().isConnected()) {
87 logger.debug("Fetching device information for address {}", address);
88 Map<String, String> properties = new HashMap<>();
89 properties.putAll(readDeviceDescription(address));
90 properties.putAll(readDeviceProperties(address));
91 return new Result(properties, Collections.emptySet());
94 private Map<String, String> readDeviceProperties(IndividualAddress address) {
95 Map<String, String> ret = new HashMap<>();
97 Thread.sleep(OPERATION_INTERVAL);
98 // check if there is a Device Object in the KNX device
99 byte[] elements = getClient().readDeviceProperties(address, DEVICE_OBJECT, PID.OBJECT_TYPE, 0, 1, false,
101 if ((elements == null ? 0 : toUnsigned(elements)) == 1) {
102 Thread.sleep(OPERATION_INTERVAL);
103 String manufacturerID = Manufacturer.getName(toUnsigned(getClient().readDeviceProperties(address,
104 DEVICE_OBJECT, PID.MANUFACTURER_ID, 1, 1, false, OPERATION_TIMEOUT)));
105 Thread.sleep(OPERATION_INTERVAL);
106 String serialNo = toHex(getClient().readDeviceProperties(address, DEVICE_OBJECT, PID.SERIAL_NUMBER, 1,
107 1, false, OPERATION_TIMEOUT), "");
108 Thread.sleep(OPERATION_INTERVAL);
109 String hardwareType = toHex(getClient().readDeviceProperties(address, DEVICE_OBJECT, HARDWARE_TYPE, 1,
110 1, false, OPERATION_TIMEOUT), " ");
111 Thread.sleep(OPERATION_INTERVAL);
112 String firmwareRevision = Integer.toString(toUnsigned(getClient().readDeviceProperties(address,
113 DEVICE_OBJECT, PID.FIRMWARE_REVISION, 1, 1, false, OPERATION_TIMEOUT)));
115 ret.put(MANUFACTURER_NAME, manufacturerID);
116 if (serialNo != null) {
117 ret.put(MANUFACTURER_SERIAL_NO, serialNo);
119 if (hardwareType != null) {
120 ret.put(MANUFACTURER_HARDWARE_TYPE, hardwareType);
122 ret.put(MANUFACTURER_FIRMWARE_REVISION, firmwareRevision);
123 logger.debug("Identified device {} as a {}, type {}, revision {}, serial number {}", address,
124 manufacturerID, hardwareType, firmwareRevision, serialNo);
126 logger.debug("The KNX device with address {} does not expose a Device Object", address);
128 } catch (InterruptedException e) {
129 logger.debug("Interrupted while fetching the device description for a device '{}' : {}", address,
135 private @Nullable String toHex(byte @Nullable [] input, String separator) {
136 return input == null ? null : DataUnitBuilder.toHex(input, separator);
139 private Map<String, String> readDeviceDescription(IndividualAddress address) {
140 Map<String, String> ret = new HashMap<>();
141 byte[] data = getClient().readDeviceDescription(address, 0, false, OPERATION_TIMEOUT);
143 final DD0 dd = DeviceDescriptor.DD0.from(data);
145 ret.put(FIRMWARE_TYPE, Firmware.getName(dd.firmwareType()));
146 ret.put(FIRMWARE_VERSION, Firmware.getName(dd.firmwareVersion()));
147 ret.put(FIRMWARE_SUBVERSION, Firmware.getName(dd.firmwareSubcode()));
148 logger.debug("The device with address {} is of type {}, version {}, subversion {}", address,
149 Firmware.getName(dd.firmwareType()), Firmware.getName(dd.firmwareVersion()),
150 Firmware.getName(dd.firmwareSubcode()));
152 logger.debug("The KNX device with address {} does not expose a Device Descriptor", address);
157 private int toUnsigned(final byte @Nullable [] data) {
161 int value = data[0] & 0xff;
162 if (data.length == 1) {
165 value = value << 8 | data[1] & 0xff;
166 if (data.length == 2) {
169 value = value << 16 | data[2] & 0xff << 8 | data[3] & 0xff;