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.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;
30 import org.openhab.core.thing.ThingTypeUID;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
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.
38 * @author Mark Hilbush - Initial contribution
40 public class MulticastListener {
41 private final Logger logger = LoggerFactory.getLogger(MulticastListener.class);
43 private MulticastSocket socket;
44 private InetSocketAddress inetSocketAddress;
46 private String serialNumber = "";
47 private String vendor = "";
48 private String model = "";
49 private String softwareRevision = "";
50 private String hardwareRevision = "";
52 // GlobalCache-specific properties defined in this binding
53 private String uid = "";
54 private String ipAddress = "";
55 private String macAddress = "";
57 private Date lastUpdate;
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;
63 // How long to wait in milliseconds for a discovery beacon
64 public static final int DEFAULT_SOCKET_TIMEOUT = 3000;
67 * Constructor joins the multicast group
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);
82 public void shutdown() {
83 logger.debug("Multicast listener closing down multicast socket");
85 socket.leaveGroup(inetSocketAddress, null);
87 } catch (IOException e) {
88 logger.debug("Exception shutting down multicast socket: {}", e.getMessage());
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.
96 public boolean waitForBeacon() throws IOException {
97 byte[] bytes = new byte[600];
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);
104 socket.receive(msgPacket);
106 logger.trace("Multicast listener got datagram of length {} from multicast port: {}", msgPacket.getLength(),
107 msgPacket.toString());
109 } catch (SocketTimeoutException e) {
114 // Get the device properties from the announcement beacon
115 parseAnnouncementBeacon(msgPacket);
122 * Parse the announcement beacon into the elements needed to create the thing.
124 * Example iTach beacon:
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>
130 * Example GC-100 beacon:
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
136 private void parseAnnouncementBeacon(DatagramPacket packet) {
137 String beacon = (new String(packet.getData())).trim();
139 logger.trace("Multicast listener parsing announcement packet: {}", beacon);
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);
150 logger.debug("Multicast listener doesn't know how to parse beacon: {}", beacon);
154 private void parseItachAnnouncementBeacon(String beacon) {
155 String[] parameterList = beacon.split("<-");
157 for (String parameter : parameterList) {
158 String[] keyValue = parameter.split("=");
160 if (keyValue.length != 2) {
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);
180 lastUpdate = new Date();
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
187 private void parseGC100AnnouncementBeacon(String beacon) {
188 String[] parameterList = beacon.split("<");
190 for (String parameter : parameterList) {
191 String[] keyValue = parameter.split("=");
193 if (keyValue.length != 2) {
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);
211 hardwareRevision = "N/A";
212 lastUpdate = new Date(System.currentTimeMillis());
216 * AMXB<-UUID=CI00a1b2c3><-Type=ZMT2><-Make=zmote.io><-Model=ZV-2><-Revision=2.1.4><-Config-URL=http://192.168.1.12>
218 private void parseZmoteAnnouncementBeacon(String beacon) {
219 String[] parameterList = beacon.split("<-");
221 for (String parameter : parameterList) {
222 String[] keyValue = parameter.split("=");
223 if (keyValue.length != 2) {
227 if (keyValue[0].contains("UUID")) {
228 uid = keyValue[1].substring(0, keyValue[1].length() - 1);
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);
241 hardwareRevision = "N/A";
242 lastUpdate = new Date(System.currentTimeMillis());
245 private void clearProperties() {
249 softwareRevision = "";
250 hardwareRevision = "";
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()) {
264 for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
265 if (logger.isTraceEnabled()) {
266 logger.trace("Found interface address {} -> {}", interfaceAddress.toString(),
267 interfaceAddress.getAddress().toString());
269 if (interfaceAddress.getAddress().toString().endsWith("/" + interfaceIpAddress)) {
270 return networkInterface;
274 throw new SocketException("Unable to get network interface for " + interfaceIpAddress);
277 public String getSerialNumber() {
281 public String getVendor() {
285 public String getModel() {
289 public String getSoftwareRevision() {
290 return softwareRevision;
293 public String getHardwareRevision() {
294 return hardwareRevision;
297 public String getUID() {
301 public String getIPAddress() {
305 public String getMACAddress() {
309 public Date getLastUpdate() {
313 public boolean isITach() {
314 return model.contains(GC_MODEL_ITACH);
317 public boolean isGC100() {
318 return model.contains(GC_MODEL_GC_100);
321 public ThingTypeUID getThingTypeUID() {
322 logger.trace("Multicast listener looking up thing type for model {} at IP {}", model, ipAddress);
325 case GC_MODEL_ITACHIP2IR:
326 case GC_MODEL_ITACHWF2IR:
327 return THING_TYPE_ITACH_IR;
329 case GC_MODEL_ITACHIP2CC:
330 case GC_MODEL_ITACHWF2CC:
331 return THING_TYPE_ITACH_CC;
333 case GC_MODEL_ITACHIP2SL:
334 case GC_MODEL_ITACHWF2SL:
335 return THING_TYPE_ITACH_SL;
337 case GC_MODEL_ITACHFLEXETH:
338 case GC_MODEL_ITACHFLEXETHPOE:
339 case GC_MODEL_ITACHFLEXWIFI:
340 return THING_TYPE_ITACH_FLEX;
342 case GC_MODEL_GC_100_06:
343 return THING_TYPE_GC_100_06;
345 case GC_MODEL_GC_100_12:
346 return THING_TYPE_GC_100_12;
349 return THING_TYPE_ZMOTE;