]> git.basschouten.com Git - openhab-addons.git/blob
940556d9b826a55f92eafda319c0a8dc20da905b
[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
109         } catch (SocketTimeoutException e) {
110             beaconFound = false;
111         }
112
113         if (beaconFound) {
114             // Get the device properties from the announcement beacon
115             parseAnnouncementBeacon(msgPacket);
116         }
117
118         return beaconFound;
119     }
120
121     /*
122      * Parse the announcement beacon into the elements needed to create the thing.
123      *
124      * Example iTach beacon:
125      *
126      * AMXB<-UUID=GlobalCache_000C1E021777><-SDKClass=Utility><-Make=GlobalCache><-Model=iTachWF2IR>
127      * <-Revision=710-1001-05><-Pkg_Level=GCPK002><-Config-URL=http://192.168.1.90><-PCB_PN=025-0026-06>
128      * <-Status=Ready>CR
129      *
130      * Example GC-100 beacon:
131      *
132      * AMXB<-UUID=GC100_000C1E00F0E9_GlobalCache><-SDKClass=Utility><-Make=GlobalCache><-Model=GC-100-06>
133      * <-Revision=1.0.0><Config-Name=GC-100><Config-URL=http://192.168.1.70>CR
134      *
135      */
136     private void parseAnnouncementBeacon(DatagramPacket packet) {
137         String beacon = (new String(packet.getData())).trim();
138
139         logger.trace("Multicast listener parsing announcement packet: {}", beacon);
140
141         clearProperties();
142
143         if (beacon.contains(GC_MODEL_ITACH)) {
144             parseItachAnnouncementBeacon(beacon);
145         } else if (beacon.contains(GC_MODEL_GC_100)) {
146             parseGC100AnnouncementBeacon(beacon);
147         } else if (beacon.contains(GC_MODEL_ZMOTE)) {
148             parseZmoteAnnouncementBeacon(beacon);
149         } else {
150             logger.debug("Multicast listener doesn't know how to parse beacon: {}", beacon);
151         }
152     }
153
154     private void parseItachAnnouncementBeacon(String beacon) {
155         String[] parameterList = beacon.split("<-");
156
157         for (String parameter : parameterList) {
158             String[] keyValue = parameter.split("=");
159
160             if (keyValue.length != 2) {
161                 continue;
162             }
163
164             if (keyValue[0].contains("UUID")) {
165                 uid = keyValue[1].substring(0, keyValue[1].length() - 1);
166                 macAddress = uid.substring(uid.indexOf("_") + 1);
167                 serialNumber = macAddress;
168             } else if (keyValue[0].contains("Make")) {
169                 vendor = keyValue[1].substring(0, keyValue[1].length() - 1);
170             } else if (keyValue[0].contains("Model")) {
171                 model = keyValue[1].substring(0, keyValue[1].length() - 1);
172             } else if (keyValue[0].contains("Revision")) {
173                 softwareRevision = keyValue[1].substring(0, keyValue[1].length() - 1);
174             } else if (keyValue[0].contains("Config-URL")) {
175                 ipAddress = keyValue[1].substring(keyValue[1].indexOf("://") + 3, keyValue[1].length() - 1);
176             } else if (keyValue[0].contains("PCB_PN")) {
177                 hardwareRevision = keyValue[1].substring(0, keyValue[1].length() - 1);
178             }
179         }
180         lastUpdate = new Date();
181     }
182
183     /*
184      * AMXB<-UUID=GC100_000C1E00F0E9_GlobalCache><-SDKClass=Utility><-Make=GlobalCache><-Model=GC-100-06>
185      * <-Revision=1.0.0><Config-Name=GC-100><Config-URL=http://192.168.1.70>CR
186      */
187     private void parseGC100AnnouncementBeacon(String beacon) {
188         String[] parameterList = beacon.split("<");
189
190         for (String parameter : parameterList) {
191             String[] keyValue = parameter.split("=");
192
193             if (keyValue.length != 2) {
194                 continue;
195             }
196
197             if (keyValue[0].contains("UUID")) {
198                 uid = keyValue[1].substring(0, keyValue[1].length() - 1);
199                 macAddress = uid.subSequence(6, 18).toString();
200                 serialNumber = macAddress;
201             } else if (keyValue[0].contains("Make")) {
202                 vendor = keyValue[1].substring(0, keyValue[1].length() - 1);
203             } else if (keyValue[0].contains("Model")) {
204                 model = keyValue[1].substring(0, keyValue[1].length() - 1);
205             } else if (keyValue[0].contains("Revision")) {
206                 softwareRevision = keyValue[1].substring(0, keyValue[1].length() - 1);
207             } else if (keyValue[0].contains("Config-URL")) {
208                 ipAddress = keyValue[1].substring(keyValue[1].indexOf("://") + 3, keyValue[1].length() - 1);
209             }
210         }
211         hardwareRevision = "N/A";
212         lastUpdate = new Date(System.currentTimeMillis());
213     }
214
215     /*
216      * AMXB<-UUID=CI00a1b2c3><-Type=ZMT2><-Make=zmote.io><-Model=ZV-2><-Revision=2.1.4><-Config-URL=http://192.168.1.12>
217      */
218     private void parseZmoteAnnouncementBeacon(String beacon) {
219         String[] parameterList = beacon.split("<-");
220
221         for (String parameter : parameterList) {
222             String[] keyValue = parameter.split("=");
223             if (keyValue.length != 2) {
224                 continue;
225             }
226
227             if (keyValue[0].contains("UUID")) {
228                 uid = keyValue[1].substring(0, keyValue[1].length() - 1);
229                 serialNumber = uid;
230             } else if (keyValue[0].contains("Make")) {
231                 vendor = keyValue[1].substring(0, keyValue[1].length() - 1);
232             } else if (keyValue[0].contains("Model")) {
233                 model = keyValue[1].substring(0, keyValue[1].length() - 1);
234             } else if (keyValue[0].contains("Revision")) {
235                 softwareRevision = keyValue[1].substring(0, keyValue[1].length() - 1);
236             } else if (keyValue[0].contains("Config-URL")) {
237                 ipAddress = keyValue[1].substring(keyValue[1].indexOf("://") + 3, keyValue[1].length() - 1);
238             }
239         }
240
241         hardwareRevision = "N/A";
242         lastUpdate = new Date(System.currentTimeMillis());
243     }
244
245     private void clearProperties() {
246         serialNumber = "";
247         vendor = "";
248         model = "";
249         softwareRevision = "";
250         hardwareRevision = "";
251         uid = "";
252         ipAddress = "";
253         macAddress = "";
254     }
255
256     private NetworkInterface getMulticastInterface(String interfaceIpAddress) throws SocketException {
257         Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
258         NetworkInterface networkInterface;
259         while (networkInterfaces.hasMoreElements()) {
260             networkInterface = networkInterfaces.nextElement();
261             if (networkInterface.isLoopback()) {
262                 continue;
263             }
264             for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
265                 if (logger.isTraceEnabled()) {
266                     logger.trace("Found interface address {} -> {}", interfaceAddress.toString(),
267                             interfaceAddress.getAddress().toString());
268                 }
269                 if (interfaceAddress.getAddress().toString().endsWith("/" + interfaceIpAddress)) {
270                     return networkInterface;
271                 }
272             }
273         }
274         throw new SocketException("Unable to get network interface for " + interfaceIpAddress);
275     }
276
277     public String getSerialNumber() {
278         return serialNumber;
279     }
280
281     public String getVendor() {
282         return vendor;
283     }
284
285     public String getModel() {
286         return model;
287     }
288
289     public String getSoftwareRevision() {
290         return softwareRevision;
291     }
292
293     public String getHardwareRevision() {
294         return hardwareRevision;
295     }
296
297     public String getUID() {
298         return uid;
299     }
300
301     public String getIPAddress() {
302         return ipAddress;
303     }
304
305     public String getMACAddress() {
306         return macAddress;
307     }
308
309     public Date getLastUpdate() {
310         return lastUpdate;
311     }
312
313     public boolean isITach() {
314         return model.contains(GC_MODEL_ITACH);
315     }
316
317     public boolean isGC100() {
318         return model.contains(GC_MODEL_GC_100);
319     }
320
321     public ThingTypeUID getThingTypeUID() {
322         logger.trace("Multicast listener looking up thing type for model {} at IP {}", model, ipAddress);
323
324         switch (model) {
325             case GC_MODEL_ITACHIP2IR:
326             case GC_MODEL_ITACHWF2IR:
327                 return THING_TYPE_ITACH_IR;
328
329             case GC_MODEL_ITACHIP2CC:
330             case GC_MODEL_ITACHWF2CC:
331                 return THING_TYPE_ITACH_CC;
332
333             case GC_MODEL_ITACHIP2SL:
334             case GC_MODEL_ITACHWF2SL:
335                 return THING_TYPE_ITACH_SL;
336
337             case GC_MODEL_ITACHFLEXETH:
338             case GC_MODEL_ITACHFLEXETHPOE:
339             case GC_MODEL_ITACHFLEXWIFI:
340                 return THING_TYPE_ITACH_FLEX;
341
342             case GC_MODEL_GC_100_06:
343                 return THING_TYPE_GC_100_06;
344
345             case GC_MODEL_GC_100_12:
346                 return THING_TYPE_GC_100_12;
347
348             case GC_MODEL_ZMOTE:
349                 return THING_TYPE_ZMOTE;
350
351             default:
352                 return null;
353         }
354     }
355 }