]> git.basschouten.com Git - openhab-addons.git/blob
fec78745c256384f67e31de3eaa5461fb648b872
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.max.internal.discovery;
14
15 import static org.openhab.binding.max.internal.MaxBindingConstants.*;
16 import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER;
17
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;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.concurrent.ScheduledFuture;
32 import java.util.concurrent.TimeUnit;
33
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;
43
44 /**
45  * The {@link MaxCubeBridgeDiscovery} is responsible for discovering new MAX!
46  * Cube LAN gateway devices on the network
47  *
48  * @author Marcel Verpaalen - Initial contribution
49  */
50 @Component(service = DiscoveryService.class, configurationPid = "discovery.max")
51 public class MaxCubeBridgeDiscovery extends AbstractDiscoveryService {
52
53     private static final String MAXCUBE_DISCOVER_STRING = "eQ3Max*\0**********I";
54     private static final int SEARCH_TIME = 15;
55
56     private final Logger logger = LoggerFactory.getLogger(MaxCubeBridgeDiscovery.class);
57
58     protected static boolean discoveryRunning;
59
60     /** The refresh interval for discovery of MAX! Cubes */
61     private static final long SEARCH_INTERVAL = 600;
62     private ScheduledFuture<?> cubeDiscoveryJob;
63
64     public MaxCubeBridgeDiscovery() {
65         super(SEARCH_TIME);
66     }
67
68     @Override
69     public Set<ThingTypeUID> getSupportedThingTypes() {
70         return SUPPORTED_BRIDGE_THING_TYPES_UIDS;
71     }
72
73     @Override
74     public void startScan() {
75         logger.debug("Start MAX! Cube discovery");
76         discoverCube();
77     }
78
79     @Override
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;
85         }
86     }
87
88     @Override
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,
93                     TimeUnit.SECONDS);
94         }
95     }
96
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.");
103     }
104
105     private void receiveDiscoveryMessage() {
106         try (final DatagramSocket bcReceipt = new DatagramSocket(23272)) {
107             discoveryRunning = true;
108             bcReceipt.setReuseAddress(true);
109             bcReceipt.setSoTimeout(5000);
110
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);
116
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(),
122                         message);
123
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);
138
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);
144                     }
145                     discoveryResultSubmission(maxCubeIP, serialNumber, rfAddress);
146                 }
147             }
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;
154         }
155     }
156
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());
168         }
169     }
170
171     /**
172      * Send broadcast message over all active interfaces
173      *
174      * @param discoverString
175      *            String to be used for the discovery
176      */
177     private void sendDiscoveryMessage(String discoverString) {
178         // Find the MaxCube using UDP broadcast
179         try (DatagramSocket bcSend = new DatagramSocket()) {
180             bcSend.setBroadcast(true);
181
182             byte[] sendData = discoverString.getBytes(StandardCharsets.UTF_8);
183
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()) {
189                     continue;
190                 }
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!
198                         if (bc != null) {
199                             try {
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);
206                             }
207                             logger.trace("Request packet sent to: {} Interface: {}", bc.getHostAddress(),
208                                     networkInterface.getDisplayName());
209                         }
210                     }
211                 }
212             }
213             logger.trace("Done looping over all network interfaces. Now waiting for a reply!");
214
215         } catch (IOException e) {
216             logger.debug("IO error during MAX! Cube discovery: {}", e.getMessage());
217         }
218     }
219 }