]> git.basschouten.com Git - openhab-addons.git/blob
b37c02dcd5d55f9709e90ce39ebb11bbc583b813
[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.globalcache.internal.discovery;
14
15 import static org.openhab.binding.globalcache.internal.GlobalCacheBindingConstants.*;
16
17 import java.io.IOException;
18 import java.net.DatagramPacket;
19 import java.net.InetAddress;
20 import java.net.InetSocketAddress;
21 import java.net.InterfaceAddress;
22 import java.net.MulticastSocket;
23 import java.net.NetworkInterface;
24 import java.net.SocketException;
25 import java.net.SocketTimeoutException;
26 import java.net.UnknownHostException;
27 import java.util.Date;
28 import java.util.Enumeration;
29
30 import org.openhab.core.thing.ThingTypeUID;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 /**
35  * The {@link MulticastListener} class is responsible for listening for the GlobalCache device announcement
36  * beacons on the multicast address, and then extracting the data fields out of the received datagram.
37  *
38  * @author Mark Hilbush - Initial contribution
39  */
40 public class MulticastListener {
41     private final Logger logger = LoggerFactory.getLogger(MulticastListener.class);
42
43     private MulticastSocket socket;
44     private InetSocketAddress inetSocketAddress;
45
46     private String serialNumber = "";
47     private String vendor = "";
48     private String model = "";
49     private String softwareRevision = "";
50     private String hardwareRevision = "";
51
52     // GlobalCache-specific properties defined in this binding
53     private String uid = "";
54     private String ipAddress = "";
55     private String macAddress = "";
56
57     private Date lastUpdate;
58
59     // GC devices announce themselves on a multicast port
60     private static final String GC_MULTICAST_GROUP = "239.255.250.250";
61     private static final int GC_MULTICAST_PORT = 9131;
62
63     // How long to wait in milliseconds for a discovery beacon
64     public static final int DEFAULT_SOCKET_TIMEOUT = 3000;
65
66     /*
67      * Constructor joins the multicast group
68      */
69     public MulticastListener(String ipv4Address) throws IOException, SocketException, UnknownHostException {
70         InetAddress ifAddress = InetAddress.getByName(ipv4Address);
71         NetworkInterface networkInterface = getMulticastInterface(ipv4Address);
72         logger.debug("Discovery job using address {} on network interface {}", ifAddress.getHostAddress(),
73                 networkInterface.getName());
74         socket = new MulticastSocket(GC_MULTICAST_PORT);
75         socket.setNetworkInterface(networkInterface);
76         socket.setSoTimeout(DEFAULT_SOCKET_TIMEOUT);
77         inetSocketAddress = new InetSocketAddress(InetAddress.getByName(GC_MULTICAST_GROUP), GC_MULTICAST_PORT);
78         socket.joinGroup(inetSocketAddress, null);
79         logger.debug("Multicast listener joined multicast group {}:{}", GC_MULTICAST_GROUP, GC_MULTICAST_PORT);
80     }
81
82     public void shutdown() {
83         logger.debug("Multicast listener closing down multicast socket");
84         try {
85             socket.leaveGroup(inetSocketAddress, null);
86             socket.close();
87         } catch (IOException e) {
88             logger.debug("Exception shutting down multicast socket: {}", e.getMessage());
89         }
90     }
91
92     /*
93      * Wait on the multicast socket for an announcement beacon. Return false on socket timeout or error.
94      * Otherwise, parse the beacon for information about the device.
95      */
96     public boolean waitForBeacon() throws IOException {
97         byte[] bytes = new byte[600];
98         boolean beaconFound;
99
100         // Wait for a device to announce itself
101         logger.trace("Multicast listener waiting for datagram on multicast port");
102         DatagramPacket msgPacket = new DatagramPacket(bytes, bytes.length);
103         try {
104             socket.receive(msgPacket);
105             beaconFound = true;
106             logger.trace("Multicast listener got datagram of length {} from multicast port: {}", msgPacket.getLength(),
107                     msgPacket.toString());
108         } catch (SocketTimeoutException e) {
109             beaconFound = false;
110         }
111
112         if (beaconFound) {
113             // Get the device properties from the announcement beacon
114             parseAnnouncementBeacon(msgPacket);
115         }
116
117         return beaconFound;
118     }
119
120     /*
121      * Parse the announcement beacon into the elements needed to create the thing.
122      *
123      * Example iTach beacon:
124      *
125      * AMXB<-UUID=GlobalCache_000C1E021777><-SDKClass=Utility><-Make=GlobalCache><-Model=iTachWF2IR>
126      * <-Revision=710-1001-05><-Pkg_Level=GCPK002><-Config-URL=http://192.168.1.90><-PCB_PN=025-0026-06>
127      * <-Status=Ready>CR
128      *
129      * Example GC-100 beacon:
130      *
131      * AMXB<-UUID=GC100_000C1E00F0E9_GlobalCache><-SDKClass=Utility><-Make=GlobalCache><-Model=GC-100-06>
132      * <-Revision=1.0.0><Config-Name=GC-100><Config-URL=http://192.168.1.70>CR
133      *
134      */
135     private void parseAnnouncementBeacon(DatagramPacket packet) {
136         String beacon = (new String(packet.getData())).trim();
137
138         logger.trace("Multicast listener parsing announcement packet: {}", beacon);
139
140         clearProperties();
141
142         if (beacon.contains(GC_MODEL_ITACH)) {
143             parseItachAnnouncementBeacon(beacon);
144         } else if (beacon.contains(GC_MODEL_GC_100)) {
145             parseGC100AnnouncementBeacon(beacon);
146         } else if (beacon.contains(GC_MODEL_ZMOTE)) {
147             parseZmoteAnnouncementBeacon(beacon);
148         } else {
149             logger.debug("Multicast listener doesn't know how to parse beacon: {}", beacon);
150         }
151     }
152
153     private void parseItachAnnouncementBeacon(String beacon) {
154         String[] parameterList = beacon.split("<-");
155
156         for (String parameter : parameterList) {
157             String[] keyValue = parameter.split("=");
158
159             if (keyValue.length != 2) {
160                 continue;
161             }
162
163             if (keyValue[0].contains("UUID")) {
164                 uid = keyValue[1].substring(0, keyValue[1].length() - 1);
165                 macAddress = uid.substring(uid.indexOf("_") + 1);
166                 serialNumber = macAddress;
167             } else if (keyValue[0].contains("Make")) {
168                 vendor = keyValue[1].substring(0, keyValue[1].length() - 1);
169             } else if (keyValue[0].contains("Model")) {
170                 model = keyValue[1].substring(0, keyValue[1].length() - 1);
171             } else if (keyValue[0].contains("Revision")) {
172                 softwareRevision = keyValue[1].substring(0, keyValue[1].length() - 1);
173             } else if (keyValue[0].contains("Config-URL")) {
174                 ipAddress = keyValue[1].substring(keyValue[1].indexOf("://") + 3, keyValue[1].length() - 1);
175             } else if (keyValue[0].contains("PCB_PN")) {
176                 hardwareRevision = keyValue[1].substring(0, keyValue[1].length() - 1);
177             }
178         }
179         lastUpdate = new Date();
180     }
181
182     /*
183      * AMXB<-UUID=GC100_000C1E00F0E9_GlobalCache><-SDKClass=Utility><-Make=GlobalCache><-Model=GC-100-06>
184      * <-Revision=1.0.0><Config-Name=GC-100><Config-URL=http://192.168.1.70>CR
185      */
186     private void parseGC100AnnouncementBeacon(String beacon) {
187         String[] parameterList = beacon.split("<");
188
189         for (String parameter : parameterList) {
190             String[] keyValue = parameter.split("=");
191
192             if (keyValue.length != 2) {
193                 continue;
194             }
195
196             if (keyValue[0].contains("UUID")) {
197                 uid = keyValue[1].substring(0, keyValue[1].length() - 1);
198                 macAddress = uid.subSequence(6, 18).toString();
199                 serialNumber = macAddress;
200             } else if (keyValue[0].contains("Make")) {
201                 vendor = keyValue[1].substring(0, keyValue[1].length() - 1);
202             } else if (keyValue[0].contains("Model")) {
203                 model = keyValue[1].substring(0, keyValue[1].length() - 1);
204             } else if (keyValue[0].contains("Revision")) {
205                 softwareRevision = keyValue[1].substring(0, keyValue[1].length() - 1);
206             } else if (keyValue[0].contains("Config-URL")) {
207                 ipAddress = keyValue[1].substring(keyValue[1].indexOf("://") + 3, keyValue[1].length() - 1);
208             }
209         }
210         hardwareRevision = "N/A";
211         lastUpdate = new Date(System.currentTimeMillis());
212     }
213
214     /*
215      * AMXB<-UUID=CI00a1b2c3><-Type=ZMT2><-Make=zmote.io><-Model=ZV-2><-Revision=2.1.4><-Config-URL=http://192.168.1.12>
216      */
217     private void parseZmoteAnnouncementBeacon(String beacon) {
218         String[] parameterList = beacon.split("<-");
219
220         for (String parameter : parameterList) {
221             String[] keyValue = parameter.split("=");
222             if (keyValue.length != 2) {
223                 continue;
224             }
225
226             if (keyValue[0].contains("UUID")) {
227                 uid = keyValue[1].substring(0, keyValue[1].length() - 1);
228                 serialNumber = uid;
229             } else if (keyValue[0].contains("Make")) {
230                 vendor = keyValue[1].substring(0, keyValue[1].length() - 1);
231             } else if (keyValue[0].contains("Model")) {
232                 model = keyValue[1].substring(0, keyValue[1].length() - 1);
233             } else if (keyValue[0].contains("Revision")) {
234                 softwareRevision = keyValue[1].substring(0, keyValue[1].length() - 1);
235             } else if (keyValue[0].contains("Config-URL")) {
236                 ipAddress = keyValue[1].substring(keyValue[1].indexOf("://") + 3, keyValue[1].length() - 1);
237             }
238         }
239
240         hardwareRevision = "N/A";
241         lastUpdate = new Date(System.currentTimeMillis());
242     }
243
244     private void clearProperties() {
245         serialNumber = "";
246         vendor = "";
247         model = "";
248         softwareRevision = "";
249         hardwareRevision = "";
250         uid = "";
251         ipAddress = "";
252         macAddress = "";
253     }
254
255     private NetworkInterface getMulticastInterface(String interfaceIpAddress) throws SocketException {
256         Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
257         NetworkInterface networkInterface;
258         while (networkInterfaces.hasMoreElements()) {
259             networkInterface = networkInterfaces.nextElement();
260             if (networkInterface.isLoopback()) {
261                 continue;
262             }
263             for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
264                 if (logger.isTraceEnabled()) {
265                     logger.trace("Found interface address {} -> {}", interfaceAddress.toString(),
266                             interfaceAddress.getAddress().toString());
267                 }
268                 if (interfaceAddress.getAddress().toString().endsWith("/" + interfaceIpAddress)) {
269                     return networkInterface;
270                 }
271             }
272         }
273         throw new SocketException("Unable to get network interface for " + interfaceIpAddress);
274     }
275
276     public String getSerialNumber() {
277         return serialNumber;
278     }
279
280     public String getVendor() {
281         return vendor;
282     }
283
284     public String getModel() {
285         return model;
286     }
287
288     public String getSoftwareRevision() {
289         return softwareRevision;
290     }
291
292     public String getHardwareRevision() {
293         return hardwareRevision;
294     }
295
296     public String getUID() {
297         return uid;
298     }
299
300     public String getIPAddress() {
301         return ipAddress;
302     }
303
304     public String getMACAddress() {
305         return macAddress;
306     }
307
308     public Date getLastUpdate() {
309         return lastUpdate;
310     }
311
312     public boolean isITach() {
313         return model.contains(GC_MODEL_ITACH);
314     }
315
316     public boolean isGC100() {
317         return model.contains(GC_MODEL_GC_100);
318     }
319
320     public ThingTypeUID getThingTypeUID() {
321         logger.trace("Multicast listener looking up thing type for model {} at IP {}", model, ipAddress);
322
323         switch (model) {
324             case GC_MODEL_ITACHIP2IR:
325             case GC_MODEL_ITACHWF2IR:
326                 return THING_TYPE_ITACH_IR;
327
328             case GC_MODEL_ITACHIP2CC:
329             case GC_MODEL_ITACHWF2CC:
330                 return THING_TYPE_ITACH_CC;
331
332             case GC_MODEL_ITACHIP2SL:
333             case GC_MODEL_ITACHWF2SL:
334                 return THING_TYPE_ITACH_SL;
335
336             case GC_MODEL_ITACHFLEXETH:
337             case GC_MODEL_ITACHFLEXETHPOE:
338             case GC_MODEL_ITACHFLEXWIFI:
339                 return THING_TYPE_ITACH_FLEX;
340
341             case GC_MODEL_GC_100_06:
342                 return THING_TYPE_GC_100_06;
343
344             case GC_MODEL_GC_100_12:
345                 return THING_TYPE_GC_100_12;
346
347             case GC_MODEL_ZMOTE:
348                 return THING_TYPE_ZMOTE;
349
350             default:
351                 return null;
352         }
353     }
354 }