2 * Copyright (c) 2010-2020 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.lcn.internal.pchkdiscovery;
15 import java.io.IOException;
16 import java.net.DatagramPacket;
17 import java.net.InetAddress;
18 import java.net.MulticastSocket;
19 import java.net.NetworkInterface;
20 import java.net.SocketException;
21 import java.net.UnknownHostException;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.LinkedList;
25 import java.util.List;
28 import java.util.stream.Collectors;
29 import java.util.stream.Stream;
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.openhab.binding.lcn.internal.LcnBindingConstants;
33 import org.openhab.binding.lcn.internal.common.LcnDefs;
34 import org.openhab.core.config.discovery.AbstractDiscoveryService;
35 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
36 import org.openhab.core.config.discovery.DiscoveryService;
37 import org.openhab.core.thing.ThingTypeUID;
38 import org.openhab.core.thing.ThingUID;
39 import org.osgi.service.component.annotations.Component;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
43 import com.thoughtworks.xstream.XStream;
44 import com.thoughtworks.xstream.io.xml.StaxDriver;
47 * Discovers LCN-PCK gateways, such as LCN-PCHK.
50 * 1. Determines all local network interfaces
51 * 2. Send a multicast message on each interface to the PCHK multicast address 234.5.6.7 (not configurable by user).
52 * 3. Evaluate multicast responses of PCK gateways in the network
54 * @author Fabian Wolter - Initial Contribution
57 @Component(service = DiscoveryService.class, configurationPid = "discovery.lcn")
58 public class LcnPchkDiscoveryService extends AbstractDiscoveryService {
59 private final Logger logger = LoggerFactory.getLogger(LcnPchkDiscoveryService.class);
60 private static final String HOSTNAME = "hostname";
61 private static final String PORT = "port";
62 private static final String MAC_ADDRESS = "macAddress";
63 private static final String PCHK_DISCOVERY_MULTICAST_ADDRESS = "234.5.6.7";
64 private static final int PCHK_DISCOVERY_PORT = 4220;
65 private static final int INTERFACE_TIMEOUT_SEC = 2;
66 private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
67 .unmodifiableSet(Stream.of(LcnBindingConstants.THING_TYPE_PCK_GATEWAY).collect(Collectors.toSet()));
68 private static final String DISCOVER_REQUEST = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><ServicesRequest xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"servicesrequest.xsd\"><Version major=\"1\" minor=\"0\" /><Requester requestId=\"1\" type=\"openHAB\" major=\"1\" minor=\"0\">openHAB</Requester><Requests><Request xsi:type=\"EnumServices\" major=\"1\" minor=\"0\" name=\"LcnPchkBus\" /></Requests></ServicesRequest>";
70 public LcnPchkDiscoveryService() throws IllegalArgumentException {
71 super(SUPPORTED_THING_TYPES_UIDS, 0, false);
74 private List<InetAddress> getLocalAddresses() {
75 List<InetAddress> result = new LinkedList<>();
77 for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) {
79 if (networkInterface.isUp() && !networkInterface.isLoopback()
80 && !networkInterface.isPointToPoint()) {
81 result.addAll(Collections.list(networkInterface.getInetAddresses()));
83 } catch (SocketException exception) {
87 } catch (SocketException exception) {
88 return Collections.emptyList();
94 protected void startScan() {
96 InetAddress multicastAddress = InetAddress.getByName(PCHK_DISCOVERY_MULTICAST_ADDRESS);
98 getLocalAddresses().forEach(localInterfaceAddress -> {
99 logger.debug("Searching on {} ...", localInterfaceAddress.getHostAddress());
100 try (MulticastSocket socket = new MulticastSocket(PCHK_DISCOVERY_PORT)) {
101 socket.setInterface(localInterfaceAddress);
102 socket.setReuseAddress(true);
103 socket.setSoTimeout(INTERFACE_TIMEOUT_SEC * 1000);
104 socket.joinGroup(multicastAddress);
106 byte[] requestData = DISCOVER_REQUEST.getBytes(LcnDefs.LCN_ENCODING);
107 DatagramPacket request = new DatagramPacket(requestData, requestData.length, multicastAddress,
108 PCHK_DISCOVERY_PORT);
109 socket.send(request);
112 byte[] rxbuf = new byte[8192];
113 DatagramPacket packet = new DatagramPacket(rxbuf, rxbuf.length);
114 socket.receive(packet);
116 InetAddress addr = packet.getAddress();
117 String response = new String(packet.getData(), LcnDefs.LCN_ENCODING);
119 if (response.contains("ServicesRequest")) {
123 ServicesResponse deserialized = xmlToServiceResponse(response);
125 String macAddress = deserialized.getServer().getMachineId().replace(":", "");
126 ThingUID thingUid = new ThingUID(LcnBindingConstants.THING_TYPE_PCK_GATEWAY, macAddress);
128 Map<String, Object> properties = new HashMap<>(3);
129 properties.put(HOSTNAME, addr.getHostAddress());
130 properties.put(PORT, deserialized.getExtServices().getExtService().getLocalPort());
131 properties.put(MAC_ADDRESS, macAddress);
133 DiscoveryResultBuilder discoveryResult = DiscoveryResultBuilder.create(thingUid)
134 .withProperties(properties).withRepresentationProperty(MAC_ADDRESS)
135 .withLabel(deserialized.getServer().getContent() + " ("
136 + deserialized.getServer().getMachineName() + ")");
138 thingDiscovered(discoveryResult.build());
139 } while (true); // left by SocketTimeoutException
140 } catch (IOException e) {
141 logger.debug("Discovery failed for {}: {}", localInterfaceAddress, e.getMessage());
144 } catch (UnknownHostException e) {
145 logger.warn("Discovery failed: {}", e.getMessage());
149 ServicesResponse xmlToServiceResponse(String response) {
150 XStream xstream = new XStream(new StaxDriver());
151 xstream.setClassLoader(getClass().getClassLoader());
152 xstream.autodetectAnnotations(true);
153 xstream.alias("ServicesResponse", ServicesResponse.class);
154 xstream.alias("Server", Server.class);
155 xstream.alias("Version", Server.class);
156 xstream.alias("ExtServices", ExtServices.class);
157 xstream.alias("ExtService", ExtService.class);
159 return (ServicesResponse) xstream.fromXML(response);