2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.globalcache.internal.discovery;
15 import static org.openhab.binding.globalcache.internal.GlobalCacheBindingConstants.*;
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;
26 import org.openhab.core.thing.ThingTypeUID;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
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.
34 * @author Mark Hilbush - Initial contribution
36 public class MulticastListener {
37 private final Logger logger = LoggerFactory.getLogger(MulticastListener.class);
39 private MulticastSocket socket;
41 private String serialNumber = "";
42 private String vendor = "";
43 private String model = "";
44 private String softwareRevision = "";
45 private String hardwareRevision = "";
47 // GlobalCache-specific properties defined in this binding
48 private String uid = "";
49 private String ipAddress = "";
50 private String macAddress = "";
52 private Date lastUpdate;
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;
58 // How long to wait in milliseconds for a discovery beacon
59 public static final int DEFAULT_SOCKET_TIMEOUT = 3000;
62 * Constructor joins the multicast group, throws IOException on failure.
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);
77 public void shutdown() {
78 logger.debug("Multicast listener closing down multicast socket");
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.
86 public boolean waitForBeacon() throws IOException {
87 byte[] bytes = new byte[600];
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);
94 socket.receive(msgPacket);
96 logger.trace("Multicast listener got datagram of length {} from multicast port: {}", msgPacket.getLength(),
97 msgPacket.toString());
99 } catch (SocketTimeoutException e) {
104 // Get the device properties from the announcement beacon
105 parseAnnouncementBeacon(msgPacket);
112 * Parse the announcement beacon into the elements needed to create the thing.
114 * Example iTach beacon:
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>
120 * Example GC-100 beacon:
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
126 private void parseAnnouncementBeacon(DatagramPacket packet) {
127 String beacon = (new String(packet.getData())).trim();
129 logger.trace("Multicast listener parsing announcement packet: {}", beacon);
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);
140 logger.debug("Multicast listener doesn't know how to parse beacon: {}", beacon);
144 private void parseItachAnnouncementBeacon(String beacon) {
145 String[] parameterList = beacon.split("<-");
147 for (String parameter : parameterList) {
148 String[] keyValue = parameter.split("=");
150 if (keyValue.length != 2) {
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);
170 lastUpdate = new Date();
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
177 private void parseGC100AnnouncementBeacon(String beacon) {
178 String[] parameterList = beacon.split("<");
180 for (String parameter : parameterList) {
181 String[] keyValue = parameter.split("=");
183 if (keyValue.length != 2) {
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);
201 hardwareRevision = "N/A";
202 lastUpdate = new Date(System.currentTimeMillis());
206 * AMXB<-UUID=CI00a1b2c3><-Type=ZMT2><-Make=zmote.io><-Model=ZV-2><-Revision=2.1.4><-Config-URL=http://192.168.1.12>
208 private void parseZmoteAnnouncementBeacon(String beacon) {
209 String[] parameterList = beacon.split("<-");
211 for (String parameter : parameterList) {
212 String[] keyValue = parameter.split("=");
213 if (keyValue.length != 2) {
217 if (keyValue[0].contains("UUID")) {
218 uid = keyValue[1].substring(0, keyValue[1].length() - 1);
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);
231 hardwareRevision = "N/A";
232 lastUpdate = new Date(System.currentTimeMillis());
235 private void clearProperties() {
239 softwareRevision = "";
240 hardwareRevision = "";
246 public String getSerialNumber() {
250 public String getVendor() {
254 public String getModel() {
258 public String getSoftwareRevision() {
259 return softwareRevision;
262 public String getHardwareRevision() {
263 return hardwareRevision;
266 public String getUID() {
270 public String getIPAddress() {
274 public String getMACAddress() {
278 public Date getLastUpdate() {
282 public boolean isITach() {
283 return model.contains(GC_MODEL_ITACH);
286 public boolean isGC100() {
287 return model.contains(GC_MODEL_GC_100);
290 public ThingTypeUID getThingTypeUID() {
291 logger.trace("Multicast listener looking up thing type for model {} at IP {}", model, ipAddress);
294 case GC_MODEL_ITACHIP2IR:
295 case GC_MODEL_ITACHWF2IR:
296 return THING_TYPE_ITACH_IR;
298 case GC_MODEL_ITACHIP2CC:
299 case GC_MODEL_ITACHWF2CC:
300 return THING_TYPE_ITACH_CC;
302 case GC_MODEL_ITACHIP2SL:
303 case GC_MODEL_ITACHWF2SL:
304 return THING_TYPE_ITACH_SL;
306 case GC_MODEL_ITACHFLEXETH:
307 case GC_MODEL_ITACHFLEXETHPOE:
308 case GC_MODEL_ITACHFLEXWIFI:
309 return THING_TYPE_ITACH_FLEX;
311 case GC_MODEL_GC_100_06:
312 return THING_TYPE_GC_100_06;
314 case GC_MODEL_GC_100_12:
315 return THING_TYPE_GC_100_12;
318 return THING_TYPE_ZMOTE;