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