]> git.basschouten.com Git - openhab-addons.git/blob
afd69f9f94cfb375871bc5f1416bc7410fa6b44c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.epsonprojector.internal.discovery;
14
15 import static org.openhab.binding.epsonprojector.internal.EpsonProjectorBindingConstants.DEFAULT_PORT;
16 import static org.openhab.binding.epsonprojector.internal.EpsonProjectorBindingConstants.THING_PROPERTY_HOST;
17 import static org.openhab.binding.epsonprojector.internal.EpsonProjectorBindingConstants.THING_PROPERTY_PORT;
18
19 import java.io.IOException;
20 import java.net.DatagramPacket;
21 import java.net.InetAddress;
22 import java.net.MulticastSocket;
23 import java.net.NetworkInterface;
24 import java.net.SocketException;
25 import java.net.SocketTimeoutException;
26 import java.nio.charset.StandardCharsets;
27 import java.util.HashMap;
28 import java.util.Locale;
29 import java.util.Map;
30
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.openhab.core.thing.Thing;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 /**
38  * The {@link MulticastListener} class is responsible for listening for the Epson projector device announcement
39  * beacons on the multicast address, and then extracting the data fields out of the received datagram.
40  *
41  * @author Mark Hilbush - Initial contribution
42  * @author Michael Lobstein - Adapted for the Epson Projector binding
43  */
44 @NonNullByDefault
45 public class MulticastListener {
46     private final Logger logger = LoggerFactory.getLogger(MulticastListener.class);
47
48     private MulticastSocket socket;
49
50     // Epson projector devices announce themselves on the AMX DDD multicast port
51     private static final String AMX_MULTICAST_GROUP = "239.255.250.250";
52     private static final int AMX_MULTICAST_PORT = 9131;
53
54     // How long to wait in milliseconds for a discovery beacon
55     public static final int DEFAULT_SOCKET_TIMEOUT_SEC = 3000;
56
57     /*
58      * Constructor joins the multicast group, throws IOException on failure.
59      */
60     public MulticastListener(String ipv4Address) throws IOException, SocketException {
61         InetAddress ifAddress = InetAddress.getByName(ipv4Address);
62         logger.debug("Discovery job using address {} on network interface {}", ifAddress.getHostAddress(),
63                 NetworkInterface.getByInetAddress(ifAddress).getName());
64         socket = new MulticastSocket(AMX_MULTICAST_PORT);
65         socket.setInterface(ifAddress);
66         socket.setSoTimeout(DEFAULT_SOCKET_TIMEOUT_SEC);
67         InetAddress mcastAddress = InetAddress.getByName(AMX_MULTICAST_GROUP);
68         socket.joinGroup(mcastAddress);
69         logger.debug("Multicast listener joined multicast group {}:{}", AMX_MULTICAST_GROUP, AMX_MULTICAST_PORT);
70     }
71
72     public void shutdown() {
73         logger.debug("Multicast listener closing down multicast socket");
74         socket.close();
75     }
76
77     /*
78      * Wait on the multicast socket for an announcement beacon. Return null on socket timeout or error.
79      * Otherwise, parse the beacon for information about the device and return the device properties.
80      */
81     public @Nullable Map<String, Object> waitForBeacon() throws IOException {
82         byte[] bytes = new byte[600];
83         boolean beaconFound;
84
85         // Wait for a device to announce itself
86         logger.trace("Multicast listener waiting for datagram on multicast port");
87         DatagramPacket msgPacket = new DatagramPacket(bytes, bytes.length);
88         try {
89             socket.receive(msgPacket);
90             beaconFound = true;
91             logger.trace("Multicast listener got datagram of length {} from multicast port: {}", msgPacket.getLength(),
92                     msgPacket.toString());
93         } catch (SocketTimeoutException e) {
94             beaconFound = false;
95         }
96
97         if (beaconFound) {
98             // Return the device properties from the announcement beacon
99             return parseAnnouncementBeacon(msgPacket);
100         }
101
102         return null;
103     }
104
105     /*
106      * Parse the announcement beacon into the elements needed to create the thing.
107      *
108      * Example Epson beacon:
109      * AMXB<-UUID=000048746B33><-SDKClass=VideoProjector><-GUID=EPSON_EMP001><-Revision=1.0.0>
110      */
111     private @Nullable Map<String, Object> parseAnnouncementBeacon(DatagramPacket packet) {
112         String beacon = (new String(packet.getData(), StandardCharsets.UTF_8)).trim();
113         logger.trace("Multicast listener parsing announcement packet: {}", beacon);
114
115         if (beacon.toUpperCase(Locale.ENGLISH).contains("EPSON") && beacon.contains("VideoProjector")) {
116             String[] parameterList = beacon.replace(">", "").split("<-");
117
118             for (String parameter : parameterList) {
119                 String[] keyValue = parameter.split("=");
120
121                 if (keyValue.length == 2 && keyValue[0].contains("UUID") && !keyValue[1].isEmpty()) {
122                     Map<String, Object> properties = new HashMap<>();
123                     properties.put(Thing.PROPERTY_MAC_ADDRESS, keyValue[1]);
124                     properties.put(THING_PROPERTY_HOST, packet.getAddress().getHostAddress());
125                     properties.put(THING_PROPERTY_PORT, DEFAULT_PORT);
126                     return properties;
127                 }
128             }
129             logger.debug("Multicast listener doesn't know how to parse beacon: {}", beacon);
130         }
131         return null;
132     }
133 }