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.max.internal.discovery;
15 import static org.openhab.binding.max.internal.MaxBindingConstants.*;
16 import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER;
18 import java.io.IOException;
19 import java.net.DatagramPacket;
20 import java.net.DatagramSocket;
21 import java.net.InetAddress;
22 import java.net.InterfaceAddress;
23 import java.net.NetworkInterface;
24 import java.net.SocketTimeoutException;
25 import java.nio.charset.StandardCharsets;
26 import java.util.Arrays;
27 import java.util.Enumeration;
28 import java.util.HashMap;
31 import java.util.concurrent.ScheduledFuture;
32 import java.util.concurrent.TimeUnit;
34 import org.openhab.binding.max.internal.Utils;
35 import org.openhab.core.config.discovery.AbstractDiscoveryService;
36 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
37 import org.openhab.core.config.discovery.DiscoveryService;
38 import org.openhab.core.thing.ThingTypeUID;
39 import org.openhab.core.thing.ThingUID;
40 import org.osgi.service.component.annotations.Component;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * The {@link MaxCubeBridgeDiscovery} is responsible for discovering new MAX!
46 * Cube LAN gateway devices on the network
48 * @author Marcel Verpaalen - Initial contribution
50 @Component(service = DiscoveryService.class, configurationPid = "discovery.max")
51 public class MaxCubeBridgeDiscovery extends AbstractDiscoveryService {
53 private static final String MAXCUBE_DISCOVER_STRING = "eQ3Max*\0**********I";
54 private static final int SEARCH_TIME = 15;
56 private final Logger logger = LoggerFactory.getLogger(MaxCubeBridgeDiscovery.class);
58 protected static boolean discoveryRunning;
60 /** The refresh interval for discovery of MAX! Cubes */
61 private static final long SEARCH_INTERVAL = 600;
62 private ScheduledFuture<?> cubeDiscoveryJob;
64 public MaxCubeBridgeDiscovery() {
69 public Set<ThingTypeUID> getSupportedThingTypes() {
70 return SUPPORTED_BRIDGE_THING_TYPES_UIDS;
74 public void startScan() {
75 logger.debug("Start MAX! Cube discovery");
80 protected void stopBackgroundDiscovery() {
81 logger.debug("Stop MAX! Cube background discovery");
82 if (cubeDiscoveryJob != null && !cubeDiscoveryJob.isCancelled()) {
83 cubeDiscoveryJob.cancel(true);
84 cubeDiscoveryJob = null;
89 protected void startBackgroundDiscovery() {
90 logger.debug("Start MAX! Cube background discovery");
91 if (cubeDiscoveryJob == null || cubeDiscoveryJob.isCancelled()) {
92 cubeDiscoveryJob = scheduler.scheduleWithFixedDelay(this::discoverCube, 0, SEARCH_INTERVAL,
97 private synchronized void discoverCube() {
98 logger.debug("Run MAX! Cube discovery");
99 sendDiscoveryMessage(MAXCUBE_DISCOVER_STRING);
100 logger.trace("Done sending broadcast discovery messages.");
101 receiveDiscoveryMessage();
102 logger.debug("Done receiving discovery messages.");
105 private void receiveDiscoveryMessage() {
106 try (final DatagramSocket bcReceipt = new DatagramSocket(23272)) {
107 discoveryRunning = true;
108 bcReceipt.setReuseAddress(true);
109 bcReceipt.setSoTimeout(5000);
111 while (discoveryRunning) {
112 // Wait for a response
113 final byte[] recvBuf = new byte[1500];
114 final DatagramPacket receivePacket = new DatagramPacket(recvBuf, recvBuf.length);
115 bcReceipt.receive(receivePacket);
117 // We have a response
118 final byte[] messageBuf = Arrays.copyOfRange(receivePacket.getData(), receivePacket.getOffset(),
119 receivePacket.getOffset() + receivePacket.getLength());
120 final String message = new String(messageBuf, StandardCharsets.UTF_8);
121 logger.trace("Broadcast response from {} : {} '{}'", receivePacket.getAddress(), message.length(),
124 // Check if the message is correct
125 if (message.startsWith("eQ3Max") && !message.equals(MAXCUBE_DISCOVER_STRING)) {
126 String maxCubeIP = receivePacket.getAddress().getHostAddress();
127 String maxCubeState = message.substring(0, 8);
128 String serialNumber = message.substring(8, 18);
129 String msgValidid = message.substring(18, 19);
130 String requestType = message.substring(19, 20);
131 String rfAddress = "";
132 logger.debug("MAX! Cube found on network");
133 logger.debug("Found at : {}", maxCubeIP);
134 logger.trace("Cube State: {}", maxCubeState);
135 logger.debug("Serial : {}", serialNumber);
136 logger.trace("Msg Valid : {}", msgValidid);
137 logger.trace("Msg Type : {}", requestType);
139 if (requestType.equals("I")) {
140 rfAddress = Utils.getHex(Arrays.copyOfRange(messageBuf, 21, 24)).replace(" ", "").toLowerCase();
141 String firmwareVersion = Utils.getHex(Arrays.copyOfRange(messageBuf, 24, 26)).replace(" ", ".");
142 logger.debug("RF Address: {}", rfAddress);
143 logger.debug("Firmware : {}", firmwareVersion);
145 discoveryResultSubmission(maxCubeIP, serialNumber, rfAddress);
148 } catch (SocketTimeoutException e) {
149 logger.trace("No further response");
150 discoveryRunning = false;
151 } catch (IOException e) {
152 logger.debug("IO error during MAX! Cube discovery: {}", e.getMessage());
153 discoveryRunning = false;
157 private void discoveryResultSubmission(String IpAddress, String cubeSerialNumber, String rfAddress) {
158 if (cubeSerialNumber != null) {
159 logger.trace("Adding new MAX! Cube Lan Gateway on {} with id '{}' to inbox", IpAddress, cubeSerialNumber);
160 Map<String, Object> properties = new HashMap<>(2);
161 properties.put(PROPERTY_IP_ADDRESS, IpAddress);
162 properties.put(PROPERTY_SERIAL_NUMBER, cubeSerialNumber);
163 properties.put(PROPERTY_RFADDRESS, rfAddress);
164 ThingUID uid = new ThingUID(CUBEBRIDGE_THING_TYPE, cubeSerialNumber);
165 thingDiscovered(DiscoveryResultBuilder.create(uid).withProperties(properties)
166 .withRepresentationProperty(PROPERTY_SERIAL_NUMBER).withThingType(CUBEBRIDGE_THING_TYPE)
167 .withLabel("MAX! Cube LAN Gateway").build());
172 * Send broadcast message over all active interfaces
174 * @param discoverString
175 * String to be used for the discovery
177 private void sendDiscoveryMessage(String discoverString) {
178 // Find the MaxCube using UDP broadcast
179 try (DatagramSocket bcSend = new DatagramSocket()) {
180 bcSend.setBroadcast(true);
182 byte[] sendData = discoverString.getBytes(StandardCharsets.UTF_8);
184 // Broadcast the message over all the network interfaces
185 Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
186 while (interfaces.hasMoreElements()) {
187 NetworkInterface networkInterface = interfaces.nextElement();
188 if (networkInterface.isLoopback() || !networkInterface.isUp()) {
191 for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
192 InetAddress[] broadcast = new InetAddress[3];
193 broadcast[0] = InetAddress.getByName("224.0.0.1");
194 broadcast[1] = InetAddress.getByName("255.255.255.255");
195 broadcast[2] = interfaceAddress.getBroadcast();
196 for (InetAddress bc : broadcast) {
197 // Send the broadcast package!
200 DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, bc, 23272);
201 bcSend.send(sendPacket);
202 } catch (IOException e) {
203 logger.debug("IO error during MAX! Cube discovery: {}", e.getMessage());
204 } catch (Exception e) {
205 logger.debug("{}", e.getMessage(), e);
207 logger.trace("Request packet sent to: {} Interface: {}", bc.getHostAddress(),
208 networkInterface.getDisplayName());
213 logger.trace("Done looping over all network interfaces. Now waiting for a reply!");
215 } catch (IOException e) {
216 logger.debug("IO error during MAX! Cube discovery: {}", e.getMessage());