]> git.basschouten.com Git - openhab-addons.git/blob
7fb9f5348698cdcd49d1368789590402f5508431
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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 Smarthome inbox", IpAddress,
160                     cubeSerialNumber);
161             Map<String, Object> properties = new HashMap<>(2);
162             properties.put(PROPERTY_IP_ADDRESS, IpAddress);
163             properties.put(PROPERTY_SERIAL_NUMBER, cubeSerialNumber);
164             properties.put(PROPERTY_RFADDRESS, rfAddress);
165             ThingUID uid = new ThingUID(CUBEBRIDGE_THING_TYPE, cubeSerialNumber);
166             thingDiscovered(DiscoveryResultBuilder.create(uid).withProperties(properties)
167                     .withRepresentationProperty(PROPERTY_SERIAL_NUMBER).withThingType(CUBEBRIDGE_THING_TYPE)
168                     .withLabel("MAX! Cube LAN Gateway").build());
169         }
170     }
171
172     /**
173      * Send broadcast message over all active interfaces
174      *
175      * @param discoverString
176      *            String to be used for the discovery
177      */
178     private void sendDiscoveryMessage(String discoverString) {
179         // Find the MaxCube using UDP broadcast
180         try (DatagramSocket bcSend = new DatagramSocket()) {
181             bcSend.setBroadcast(true);
182
183             byte[] sendData = discoverString.getBytes(StandardCharsets.UTF_8);
184
185             // Broadcast the message over all the network interfaces
186             Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
187             while (interfaces.hasMoreElements()) {
188                 NetworkInterface networkInterface = interfaces.nextElement();
189                 if (networkInterface.isLoopback() || !networkInterface.isUp()) {
190                     continue;
191                 }
192                 for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
193                     InetAddress[] broadcast = new InetAddress[3];
194                     broadcast[0] = InetAddress.getByName("224.0.0.1");
195                     broadcast[1] = InetAddress.getByName("255.255.255.255");
196                     broadcast[2] = interfaceAddress.getBroadcast();
197                     for (InetAddress bc : broadcast) {
198                         // Send the broadcast package!
199                         if (bc != null) {
200                             try {
201                                 DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, bc, 23272);
202                                 bcSend.send(sendPacket);
203                             } catch (IOException e) {
204                                 logger.debug("IO error during MAX! Cube discovery: {}", e.getMessage());
205                             } catch (Exception e) {
206                                 logger.debug("{}", e.getMessage(), e);
207                             }
208                             logger.trace("Request packet sent to: {} Interface: {}", bc.getHostAddress(),
209                                     networkInterface.getDisplayName());
210                         }
211                     }
212                 }
213             }
214             logger.trace("Done looping over all network interfaces. Now waiting for a reply!");
215
216         } catch (IOException e) {
217             logger.debug("IO error during MAX! Cube discovery: {}", e.getMessage());
218         }
219     }
220 }