]> git.basschouten.com Git - openhab-addons.git/blob
4ec9cc14f91aa9ea9ce2ae1b3ebaa7b985c95a2a
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.lcn.internal.pchkdiscovery;
14
15 import java.io.IOException;
16 import java.net.DatagramPacket;
17 import java.net.InetAddress;
18 import java.net.InetSocketAddress;
19 import java.net.MulticastSocket;
20 import java.net.NetworkInterface;
21 import java.net.SocketException;
22 import java.net.UnknownHostException;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.LinkedList;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.openhab.binding.lcn.internal.LcnBindingConstants;
32 import org.openhab.binding.lcn.internal.common.LcnDefs;
33 import org.openhab.core.config.discovery.AbstractDiscoveryService;
34 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
35 import org.openhab.core.config.discovery.DiscoveryService;
36 import org.openhab.core.thing.ThingTypeUID;
37 import org.openhab.core.thing.ThingUID;
38 import org.osgi.service.component.annotations.Component;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import com.thoughtworks.xstream.XStream;
43 import com.thoughtworks.xstream.io.xml.StaxDriver;
44
45 /**
46  * Discovers LCN-PCK gateways, such as LCN-PCHK.
47  *
48  * Scan approach:
49  * 1. Determines all local network interfaces
50  * 2. Send a multicast message on each interface to the PCHK multicast address 234.5.6.7 (not configurable by user).
51  * 3. Evaluate multicast responses of PCK gateways in the network
52  *
53  * @author Fabian Wolter - Initial Contribution
54  */
55 @NonNullByDefault
56 @Component(service = DiscoveryService.class, configurationPid = "discovery.lcn")
57 public class LcnPchkDiscoveryService extends AbstractDiscoveryService {
58     private final Logger logger = LoggerFactory.getLogger(LcnPchkDiscoveryService.class);
59     private static final String HOSTNAME = "hostname";
60     private static final String PORT = "port";
61     private static final String MAC_ADDRESS = "macAddress";
62     private static final String PCHK_DISCOVERY_MULTICAST_ADDRESS = "234.5.6.7";
63     private static final int PCHK_DISCOVERY_PORT = 4220;
64     private static final int INTERFACE_TIMEOUT_SEC = 2;
65     private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set
66             .of(LcnBindingConstants.THING_TYPE_PCK_GATEWAY);
67     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>";
68
69     public LcnPchkDiscoveryService() throws IllegalArgumentException {
70         super(SUPPORTED_THING_TYPES_UIDS, 0, false);
71     }
72
73     private List<NetworkInterface> getLocalNetworkInterfaces() {
74         List<NetworkInterface> result = new LinkedList<>();
75         try {
76             for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) {
77                 try {
78                     if (networkInterface.isUp() && !networkInterface.isLoopback()
79                             && !networkInterface.isPointToPoint()) {
80                         result.add(networkInterface);
81                     }
82                 } catch (SocketException exception) {
83                     // ignore
84                 }
85             }
86         } catch (SocketException exception) {
87             return Collections.emptyList();
88         }
89         return result;
90     }
91
92     @Override
93     protected void startScan() {
94         try {
95             InetAddress multicastAddress = InetAddress.getByName(PCHK_DISCOVERY_MULTICAST_ADDRESS);
96
97             getLocalNetworkInterfaces().forEach(localNetworkInterface -> {
98                 logger.debug("Searching on {} ...", localNetworkInterface);
99                 try (MulticastSocket socket = new MulticastSocket(PCHK_DISCOVERY_PORT)) {
100                     socket.setReuseAddress(true);
101                     socket.setSoTimeout(INTERFACE_TIMEOUT_SEC * 1000);
102                     socket.joinGroup(new InetSocketAddress(multicastAddress, PCHK_DISCOVERY_PORT),
103                             localNetworkInterface);
104
105                     byte[] requestData = DISCOVER_REQUEST.getBytes(LcnDefs.LCN_ENCODING);
106                     DatagramPacket request = new DatagramPacket(requestData, requestData.length, multicastAddress,
107                             PCHK_DISCOVERY_PORT);
108                     socket.send(request);
109
110                     do {
111                         byte[] rxbuf = new byte[8192];
112                         DatagramPacket packet = new DatagramPacket(rxbuf, rxbuf.length);
113                         socket.receive(packet);
114
115                         InetAddress addr = packet.getAddress();
116                         String response = new String(packet.getData(), LcnDefs.LCN_ENCODING);
117
118                         if (response.contains("ServicesRequest")) {
119                             continue;
120                         }
121
122                         ServicesResponse deserialized = xmlToServiceResponse(response);
123
124                         String macAddress = deserialized.getServer().getMachineId().replace(":", "");
125                         ThingUID thingUid = new ThingUID(LcnBindingConstants.THING_TYPE_PCK_GATEWAY, macAddress);
126
127                         Map<String, Object> properties = new HashMap<>(3);
128                         properties.put(HOSTNAME, addr.getHostAddress());
129                         properties.put(PORT, deserialized.getExtServices().getExtService().getLocalPort());
130                         properties.put(MAC_ADDRESS, macAddress);
131
132                         DiscoveryResultBuilder discoveryResult = DiscoveryResultBuilder.create(thingUid)
133                                 .withProperties(properties).withRepresentationProperty(MAC_ADDRESS)
134                                 .withLabel(deserialized.getServer().getContent() + " ("
135                                         + deserialized.getServer().getMachineName() + ")");
136
137                         thingDiscovered(discoveryResult.build());
138                     } while (true); // left by SocketTimeoutException
139                 } catch (IOException e) {
140                     logger.debug("Discovery failed for {}: {}", localNetworkInterface, e.getMessage());
141                 }
142             });
143         } catch (UnknownHostException e) {
144             logger.warn("Discovery failed: {}", e.getMessage());
145         }
146     }
147
148     ServicesResponse xmlToServiceResponse(String response) {
149         XStream xstream = new XStream(new StaxDriver());
150         xstream.allowTypesByWildcard(new String[] { ServicesResponse.class.getPackageName() + ".**" });
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);
158
159         return (ServicesResponse) xstream.fromXML(response);
160     }
161 }