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.danfossairunit.internal.discovery;
15 import static org.openhab.binding.danfossairunit.internal.DanfossAirUnitBindingConstants.*;
17 import java.io.IOException;
18 import java.net.DatagramPacket;
19 import java.net.DatagramSocket;
20 import java.net.InetAddress;
21 import java.net.InterfaceAddress;
22 import java.net.NetworkInterface;
23 import java.net.SocketTimeoutException;
24 import java.util.Arrays;
25 import java.util.Enumeration;
26 import java.util.HashMap;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.core.config.discovery.AbstractDiscoveryService;
33 import org.openhab.core.config.discovery.DiscoveryResult;
34 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
35 import org.openhab.core.config.discovery.DiscoveryService;
36 import org.openhab.core.i18n.LocaleProvider;
37 import org.openhab.core.i18n.TranslationProvider;
38 import org.openhab.core.thing.ThingTypeUID;
39 import org.openhab.core.thing.ThingUID;
40 import org.osgi.service.component.annotations.Activate;
41 import org.osgi.service.component.annotations.Component;
42 import org.osgi.service.component.annotations.Reference;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
47 * The Discovery service implementation to scan for available air units in the network via broadcast.
49 * @author Ralf Duckstein - Initial contribution
50 * @author Robert Bach - heavy refactorings
52 @Component(service = DiscoveryService.class)
54 public class DanfossAirUnitDiscoveryService extends AbstractDiscoveryService {
56 private static final int BROADCAST_PORT = 30045;
57 private static final byte[] DISCOVER_SEND = { 0x0c, 0x00, 0x30, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13 };
58 private static final byte[] DISCOVER_RECEIVE = { 0x0d, 0x00, 0x07, 0x00, 0x02, 0x02, 0x00 };
59 private static final int TIMEOUT_IN_SECONDS = 15;
61 private final Logger logger = LoggerFactory.getLogger(DanfossAirUnitDiscoveryService.class);
64 public DanfossAirUnitDiscoveryService(@Reference TranslationProvider i18nProvider,
65 @Reference LocaleProvider localeProvider) {
66 super(SUPPORTED_THING_TYPES_UIDS, TIMEOUT_IN_SECONDS, true);
67 this.i18nProvider = i18nProvider;
68 this.localeProvider = localeProvider;
72 public Set<ThingTypeUID> getSupportedThingTypes() {
73 return SUPPORTED_THING_TYPES_UIDS;
77 protected void startBackgroundDiscovery() {
78 logger.debug("Start Danfoss Air CCM background discovery");
79 scheduler.execute(this::discover);
83 public void startScan() {
84 logger.debug("Start Danfoss Air CCM scan");
88 private synchronized void discover() {
89 logger.debug("Try to discover all Danfoss Air CCM devices");
91 try (DatagramSocket socket = new DatagramSocket()) {
92 Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
93 while (interfaces.hasMoreElements()) {
95 NetworkInterface networkInterface = interfaces.nextElement();
96 if (networkInterface.isLoopback() || !networkInterface.isUp()) {
99 for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
100 if (interfaceAddress.getBroadcast() == null) {
103 logger.debug("Sending broadcast on interface {} to discover Danfoss Air CCM device...",
104 interfaceAddress.getAddress());
105 sendBroadcastToDiscoverThing(socket, interfaceAddress.getBroadcast());
108 } catch (IOException e) {
109 logger.debug("No Danfoss Air CCM device found. Diagnostic: {}", e.getMessage());
113 private void sendBroadcastToDiscoverThing(DatagramSocket socket, InetAddress broadcastAddress) throws IOException {
114 socket.setBroadcast(true);
115 socket.setSoTimeout(500);
117 byte[] sendBuffer = DISCOVER_SEND;
118 DatagramPacket sendPacket = new DatagramPacket(sendBuffer, sendBuffer.length, broadcastAddress, BROADCAST_PORT);
119 socket.send(sendPacket);
120 logger.debug("Discover message sent");
122 // wait for responses
124 byte[] receiveBuffer = new byte[7];
125 DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
127 socket.receive(receivePacket);
128 } catch (SocketTimeoutException e) {
129 break; // leave the endless loop
132 byte[] data = receivePacket.getData();
133 if (Arrays.equals(data, DISCOVER_RECEIVE)) {
134 logger.debug("Discover received correct response");
136 String host = receivePacket.getAddress().getHostName();
137 Map<String, Object> properties = new HashMap<>();
138 properties.put("host", host);
140 logger.debug("Adding a new Danfoss Air Unit CCM '{}' to inbox", host);
142 ThingUID uid = new ThingUID(THING_TYPE_AIRUNIT, String.valueOf(receivePacket.getAddress().hashCode()));
144 DiscoveryResult result = DiscoveryResultBuilder.create(uid).withRepresentationProperty("host")
145 .withProperties(properties).withLabel("@text/discovery.danfossairunit.label").build();
146 thingDiscovered(result);
148 logger.debug("Thing discovered '{}'", result);