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.benqprojector.internal.discovery;
15 import static org.openhab.binding.benqprojector.internal.BenqProjectorBindingConstants.*;
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.nio.charset.StandardCharsets;
25 import java.util.HashMap;
26 import java.util.Locale;
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.openhab.core.thing.Thing;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
36 * The {@link MulticastListener} class is responsible for listening for the BenQ projector device announcement
37 * beacons on the multicast address, and then extracting the data fields out of the received datagram.
39 * @author Mark Hilbush - Initial contribution
40 * @author Michael Lobstein - Adapted for the BenQ Projector binding
43 public class MulticastListener {
44 private final Logger logger = LoggerFactory.getLogger(MulticastListener.class);
46 private MulticastSocket socket;
48 // BenQ projector devices announce themselves on the AMX DDD multicast port
49 private static final String AMX_MULTICAST_GROUP = "239.255.250.250";
50 private static final int AMX_MULTICAST_PORT = 9131;
52 // How long to wait in milliseconds for a discovery beacon
53 public static final int DEFAULT_SOCKET_TIMEOUT_SEC = 3000;
56 * Constructor joins the multicast group, throws IOException on failure.
58 public MulticastListener(String ipv4Address) throws IOException, SocketException {
59 InetAddress ifAddress = InetAddress.getByName(ipv4Address);
60 NetworkInterface netIF = NetworkInterface.getByInetAddress(ifAddress);
61 logger.debug("Discovery job using address {} on network interface {}", ifAddress.getHostAddress(),
62 netIF != null ? netIF.getName() : "UNKNOWN");
63 socket = new MulticastSocket(AMX_MULTICAST_PORT);
64 socket.setInterface(ifAddress);
65 socket.setSoTimeout(DEFAULT_SOCKET_TIMEOUT_SEC);
66 InetAddress mcastAddress = InetAddress.getByName(AMX_MULTICAST_GROUP);
67 socket.joinGroup(mcastAddress);
68 logger.debug("Multicast listener joined multicast group {}:{}", AMX_MULTICAST_GROUP, AMX_MULTICAST_PORT);
71 public void shutdown() {
72 logger.debug("Multicast listener closing down multicast socket");
77 * Wait on the multicast socket for an announcement beacon. Return null on socket timeout or error.
78 * Otherwise, parse the beacon for information about the device and return the device properties.
80 public @Nullable Map<String, Object> waitForBeacon() throws IOException {
81 byte[] bytes = new byte[600];
84 // Wait for a device to announce itself
85 logger.trace("Multicast listener waiting for datagram on multicast port");
86 DatagramPacket msgPacket = new DatagramPacket(bytes, bytes.length);
88 socket.receive(msgPacket);
90 logger.trace("Multicast listener got datagram of length {} from multicast port: {}", msgPacket.getLength(),
91 msgPacket.toString());
92 } catch (SocketTimeoutException e) {
97 // Return the device properties from the announcement beacon
98 return parseAnnouncementBeacon(msgPacket);
105 * Parse the announcement beacon into the elements needed to create the thing.
108 * AMXB<-UUID=000048746B33><-SDKClass=VideoProjector><-GUID=EPSON_EMP001><-Revision=1.0.0>
110 private @Nullable Map<String, Object> parseAnnouncementBeacon(DatagramPacket packet) {
111 String beacon = (new String(packet.getData(), StandardCharsets.UTF_8)).trim();
112 logger.trace("Multicast listener parsing announcement packet: {}", beacon);
114 if (beacon.toUpperCase(Locale.ENGLISH).contains("BENQ") && beacon.contains("VideoProjector")) {
115 String[] parameterList = beacon.replace(">", "").split("<-");
117 for (String parameter : parameterList) {
118 String[] keyValue = parameter.split("=");
120 if (keyValue.length == 2 && keyValue[0].contains("UUID") && !keyValue[1].isEmpty()) {
121 Map<String, Object> properties = new HashMap<>();
122 properties.put(Thing.PROPERTY_MAC_ADDRESS, keyValue[1]);
123 properties.put(THING_PROPERTY_HOST, packet.getAddress().getHostAddress());
124 properties.put(THING_PROPERTY_PORT, DEFAULT_PORT);
128 logger.debug("Multicast listener doesn't know how to parse beacon: {}", beacon);