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.nikohomecontrol.internal.protocol;
15 import java.io.IOException;
16 import java.net.DatagramPacket;
17 import java.net.DatagramSocket;
18 import java.net.InetAddress;
19 import java.net.SocketTimeoutException;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.HashMap;
23 import java.util.List;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.core.util.HexUtils;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
33 * Class {@link NikoHomeControlDiscover} is used to get the Niko Home Control IP-interfaces IP address for bridge
36 * The constructor broadcasts a UDP packet with content 0x44 on port 10000.
37 * The Niko Home Control IP-interfaces responds to this UDP packet.
38 * The IP-address from the Niko Home Control IP-interface is then extracted from the response packet.
39 * The data content of the response packet is used as a unique identifier for the bridge.
41 * @author Mark Herwege - Initial Contribution
44 * @author Mark Herwege - Initial Contribution
48 public final class NikoHomeControlDiscover {
50 private final Logger logger = LoggerFactory.getLogger(NikoHomeControlDiscover.class);
52 private List<String> nhcBridgeIds = new ArrayList<>();
53 private Map<String, InetAddress> inetAdresses = new HashMap<>();
54 private Map<String, Boolean> isNhcII = new HashMap<>();
57 * Discover the list of Niko Home Control IP interfaces by broadcasting UDP packet 0x44 to port 10000. The IP
58 * interface will reply. The address of the IP interface is than derived from that response.
60 * @param broadcast Broadcast address of the network
63 public NikoHomeControlDiscover(String broadcast) throws IOException {
64 final byte[] discoverBuffer = { (byte) 0x44 };
65 final InetAddress broadcastAddr = InetAddress.getByName(broadcast);
66 final int broadcastPort = 10000;
68 DatagramPacket discoveryPacket = new DatagramPacket(discoverBuffer, discoverBuffer.length, broadcastAddr,
70 byte[] buffer = new byte[1024];
71 DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
73 try (DatagramSocket datagramSocket = new DatagramSocket(null)) {
74 datagramSocket.setBroadcast(true);
75 datagramSocket.setSoTimeout(500);
76 datagramSocket.send(discoveryPacket);
79 datagramSocket.receive(packet);
80 logger.trace("bridge discovery response {}",
81 HexUtils.bytesToHex(Arrays.copyOf(packet.getData(), packet.getLength())));
82 if (isNhcController(packet)) {
83 String bridgeId = setNhcBridgeId(packet);
84 setIsNhcII(bridgeId, packet);
85 InetAddress addr = setAddr(bridgeId, packet);
86 logger.debug("IP address is {}, unique ID is {}", addr, bridgeId);
89 } catch (SocketTimeoutException e) {
90 // all received, continue
96 * @return the discovered nhcBridgeIds
98 public List<String> getNhcBridgeIds() {
103 * @param bridgeId discovered bridgeId
104 * @return the addr, null if not in the list of discovered bridgeId's
106 public @Nullable InetAddress getAddr(String bridgeId) {
107 return inetAdresses.get(bridgeId);
111 * Check if the UDP packet comes from a Niko Home Control controller. The response should start with 0x44.
114 * @return true if packet is from a Niko Home Control controller
116 private boolean isNhcController(DatagramPacket packet) {
117 byte[] packetData = packet.getData();
118 boolean isNhc = (packet.getLength() > 2) && (packetData[0] == 0x44);
119 // filter response from Gen1 touchscreens
120 boolean isController = isNhc && (packetData[1] == 0x3b) || (packetData[1] == 0x0c) || (packetData[1] == 0x0e);
122 logger.trace("not a NHC controller");
128 * Retrieves a unique ID from the returned datagram packet received after sending the UDP discovery message.
132 private String setNhcBridgeId(DatagramPacket packet) {
133 byte[] packetData = packet.getData();
134 int packetLength = packet.getLength();
135 packetLength = packetLength > 6 ? 6 : packetLength;
136 StringBuilder sb = new StringBuilder(packetLength);
137 for (int i = 0; i < packetLength; i++) {
138 sb.append(String.format("%02x", packetData[i]));
140 String bridgeId = sb.toString();
141 nhcBridgeIds.add(bridgeId);
146 * Checks if this is a NHC II Connected Controller
151 private void setIsNhcII(String bridgeId, DatagramPacket packet) {
152 byte[] packetData = packet.getData();
153 int packetLength = packet.getLength();
154 // The 16th byte in the packet is 2 for a NHC II Connected Controller
155 if ((packetLength >= 16) && (packetData[15] >= 2)) {
156 isNhcII.put(bridgeId, true);
158 isNhcII.put(bridgeId, false);
163 * Sets the IP address retrieved from the packet response and keep it with bridgeId
167 * @return address from packet response
169 private InetAddress setAddr(String bridgeId, DatagramPacket packet) {
170 InetAddress address = packet.getAddress();
171 inetAdresses.put(bridgeId, address);
176 * Test if the installation is a Niko Home Control II installation
179 * @return true if this is a Niko Home Control II installation
181 public boolean isNhcII(String bridgeId) {
182 return isNhcII.getOrDefault(bridgeId, false);