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