2 * Copyright (c) 2010-2024 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.freeboxos.internal.discovery;
15 import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
17 import java.util.ArrayList;
18 import java.util.List;
19 import java.util.Optional;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23 import java.util.stream.Collectors;
24 import java.util.stream.Stream;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.openhab.binding.freeboxos.internal.api.FreeboxException;
28 import org.openhab.binding.freeboxos.internal.api.rest.APManager;
29 import org.openhab.binding.freeboxos.internal.api.rest.APManager.Station;
30 import org.openhab.binding.freeboxos.internal.api.rest.FreeplugManager;
31 import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
32 import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager;
33 import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.LanHost;
34 import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager;
35 import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager.Status;
36 import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager;
37 import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Player;
38 import org.openhab.binding.freeboxos.internal.api.rest.RepeaterManager;
39 import org.openhab.binding.freeboxos.internal.api.rest.RepeaterManager.Repeater;
40 import org.openhab.binding.freeboxos.internal.api.rest.SystemManager;
41 import org.openhab.binding.freeboxos.internal.api.rest.SystemManager.Config;
42 import org.openhab.binding.freeboxos.internal.api.rest.VmManager;
43 import org.openhab.binding.freeboxos.internal.config.ClientConfiguration;
44 import org.openhab.binding.freeboxos.internal.config.FreeplugConfigurationBuilder;
45 import org.openhab.binding.freeboxos.internal.config.NodeConfigurationBuilder;
46 import org.openhab.binding.freeboxos.internal.config.PhoneConfigurationBuilder;
47 import org.openhab.binding.freeboxos.internal.handler.FreeboxOsHandler;
48 import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService;
49 import org.openhab.core.config.discovery.DiscoveryResult;
50 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
51 import org.openhab.core.thing.Thing;
52 import org.openhab.core.thing.ThingStatus;
53 import org.openhab.core.thing.ThingTypeUID;
54 import org.openhab.core.thing.ThingUID;
55 import org.osgi.service.component.annotations.Component;
56 import org.osgi.service.component.annotations.ServiceScope;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
60 import inet.ipaddr.mac.MACAddress;
63 * The {@link FreeboxOsDiscoveryService} is responsible for discovering all things
64 * except the Freebox API thing itself
66 * @author Gaƫl L'hopital - Initial contribution
68 @Component(scope = ServiceScope.PROTOTYPE, service = FreeboxOsDiscoveryService.class)
70 public class FreeboxOsDiscoveryService extends AbstractThingHandlerDiscoveryService<FreeboxOsHandler> {
71 private static final int DISCOVERY_TIME_SECONDS = 10;
73 private final Logger logger = LoggerFactory.getLogger(FreeboxOsDiscoveryService.class);
75 private boolean hasVm = true;
76 private boolean hasHomeAutomation = true;
78 private Optional<ScheduledFuture<?>> backgroundFuture = Optional.empty();
80 public FreeboxOsDiscoveryService() {
81 super(FreeboxOsHandler.class,
82 Stream.of(THINGS_TYPES_UIDS, HOME_TYPES_UIDS).flatMap(Set::stream).collect(Collectors.toSet()),
83 DISCOVERY_TIME_SECONDS);
87 protected void startBackgroundDiscovery() {
88 stopBackgroundDiscovery();
89 int interval = thingHandler.getConfiguration().discoveryInterval;
91 backgroundFuture = Optional
92 .of(scheduler.scheduleWithFixedDelay(this::startScan, 1, interval, TimeUnit.MINUTES));
97 protected void stopBackgroundDiscovery() {
98 backgroundFuture.ifPresent(future -> future.cancel(true));
99 backgroundFuture = Optional.empty();
103 protected void startScan() {
104 logger.debug("Starting Freebox discovery scan");
105 if (thingHandler.getThing().getStatus() == ThingStatus.ONLINE) {
107 ThingUID bridgeUID = thingHandler.getThing().getUID();
109 List<LanHost> lanHosts = new ArrayList<>(thingHandler.getManager(LanBrowserManager.class).getHosts()
110 .stream().filter(LanHost::reachable).toList());
112 discoverServer(bridgeUID);
113 discoverPhone(bridgeUID);
114 discoverPlugs(bridgeUID);
115 discoverRepeater(bridgeUID, lanHosts);
116 discoverPlayer(bridgeUID, lanHosts);
118 discoverVM(bridgeUID, lanHosts);
120 if (hasHomeAutomation) {
121 discoverHome(bridgeUID);
123 if (thingHandler.getConfiguration().discoverNetDevice) {
124 discoverHosts(bridgeUID, lanHosts);
126 } catch (FreeboxException e) {
127 logger.debug("Error while requesting data for things discovery: {}", e.getMessage());
132 private void discoverHome(ThingUID bridgeUID) {
133 NodeConfigurationBuilder builder = NodeConfigurationBuilder.getInstance();
135 thingHandler.getManager(HomeManager.class).getHomeNodes().forEach(
136 node -> builder.configure(bridgeUID, node).ifPresent(result -> thingDiscovered(result.build())));
137 } catch (FreeboxException e) {
138 logger.debug("Error discovering Home: {}", e.getMessage());
142 private void discoverPlugs(ThingUID bridgeUID) {
143 FreeplugConfigurationBuilder builder = FreeplugConfigurationBuilder.getInstance();
145 thingHandler.getManager(FreeplugManager.class).getPlugs()
146 .forEach(plug -> thingDiscovered(builder.configure(bridgeUID, plug).build()));
147 } catch (FreeboxException e) {
148 logger.debug("Error discovering freeplugs: {}", e.getMessage());
152 private void discoverPhone(ThingUID bridgeUID) {
153 PhoneConfigurationBuilder builder = PhoneConfigurationBuilder.getInstance();
154 List<Status> statuses = List.of();
156 statuses = thingHandler.getManager(PhoneManager.class).getPhoneStatuses();
157 statuses.forEach(phone -> thingDiscovered(builder.configure(bridgeUID, phone).build()));
158 } catch (FreeboxException e) {
159 logger.debug("Error discovering phones: {}", e.getMessage());
161 if (!statuses.isEmpty()) {
162 ThingUID thingUID = new ThingUID(THING_TYPE_CALL, bridgeUID, "calls");
163 logger.debug("Adding new Call thing {} to inbox", thingUID);
164 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
165 .withLabel("Phone Calls").build();
166 thingDiscovered(discoveryResult);
170 private void discoverHosts(ThingUID bridgeUID, List<LanHost> lanHosts) {
172 List<MACAddress> wifiMacs = new ArrayList<>();
173 wifiMacs.addAll(thingHandler.getManager(APManager.class).getStations().stream().map(Station::mac).toList());
175 thingHandler.getManager(RepeaterManager.class).getHosts().stream().map(LanHost::getMac).toList());
177 lanHosts.forEach(lanHost -> {
178 MACAddress mac = lanHost.getMac();
179 String macString = mac.toColonDelimitedString();
180 ThingUID thingUID = new ThingUID(wifiMacs.contains(mac) ? THING_TYPE_WIFI_HOST : THING_TYPE_HOST,
181 bridgeUID, mac.toHexString(false));
182 logger.debug("Adding new Freebox Network Host {} to inbox", thingUID);
183 DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
184 .withLabel(lanHost.getPrimaryName().orElse("Network Device %s".formatted(macString)))
185 .withTTL(300).withProperty(Thing.PROPERTY_MAC_ADDRESS, macString)
186 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS);
187 thingDiscovered(builder.build());
189 } catch (FreeboxException e) {
190 logger.debug("Error discovering Hosts: {}", e.getMessage());
194 private void discoverVM(ThingUID bridgeUID, List<LanHost> lanHosts) {
196 thingHandler.getManager(VmManager.class).getDevices().forEach(vm -> {
197 MACAddress mac = vm.mac();
198 lanHosts.removeIf(host -> host.getMac().equals(mac));
200 ThingUID thingUID = new ThingUID(THING_TYPE_VM, bridgeUID, mac.toHexString(false));
201 logger.debug("Adding new VM Device {} to inbox", thingUID);
202 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
203 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS)
204 .withLabel("%s (VM)".formatted(vm.name())).withProperty(ClientConfiguration.ID, vm.id())
205 .withProperty(Thing.PROPERTY_MAC_ADDRESS, mac.toColonDelimitedString()).build();
206 thingDiscovered(discoveryResult);
208 } catch (FreeboxException e) {
209 logger.debug("Error discovering VM: {}", e.getMessage());
213 private void discoverRepeater(ThingUID bridgeUID, List<LanHost> lanHosts) {
215 List<Repeater> repeaters = thingHandler.getManager(RepeaterManager.class).getDevices();
216 repeaters.forEach(repeater -> {
217 MACAddress mac = repeater.mainMac();
218 lanHosts.removeIf(host -> host.getMac().equals(mac));
220 ThingUID thingUID = new ThingUID(THING_TYPE_REPEATER, bridgeUID, Integer.toString(repeater.id()));
221 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
222 .withLabel("Repeater %s".formatted(repeater.name()))
223 .withProperty(Thing.PROPERTY_MAC_ADDRESS, mac.toColonDelimitedString())
224 .withProperty(ClientConfiguration.ID, repeater.id())
225 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
226 thingDiscovered(discoveryResult);
228 } catch (FreeboxException e) {
229 logger.debug("Error discovering Repeater: {}", e.getMessage());
233 private void discoverServer(ThingUID bridgeUID) {
235 Config config = thingHandler.getManager(SystemManager.class).getConfig();
237 hasVm = config.modelInfo().hasVm();
238 hasHomeAutomation = config.modelInfo().hasHomeAutomation();
240 ThingTypeUID targetType = config.boardName().startsWith("fbxgw7") ? THING_TYPE_DELTA
241 : THING_TYPE_REVOLUTION;
242 ThingUID thingUID = new ThingUID(targetType, bridgeUID, config.serial());
243 logger.debug("Adding new Freebox Server {} to inbox", thingUID);
245 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
246 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).withLabel(config.modelInfo().prettyName())
247 .withProperty(Thing.PROPERTY_MAC_ADDRESS, config.mac().toColonDelimitedString()).build();
248 thingDiscovered(discoveryResult);
249 } catch (FreeboxException e) {
250 logger.debug("Error discovering Server: {}", e.getMessage());
254 private void discoverPlayer(ThingUID bridgeUID, List<LanHost> lanHosts) {
256 for (Player player : thingHandler.getManager(PlayerManager.class).getDevices()) {
257 lanHosts.removeIf(host -> host.getMac().equals(player.mac()));
258 ThingUID thingUID = new ThingUID(player.apiAvailable() ? THING_TYPE_ACTIVE_PLAYER : THING_TYPE_PLAYER,
259 bridgeUID, Integer.toString(player.id()));
260 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
261 .withProperty(Thing.PROPERTY_MAC_ADDRESS, player.mac().toColonDelimitedString())
262 .withProperty(ClientConfiguration.ID, player.id()).withLabel(player.deviceName())
263 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
264 thingDiscovered(discoveryResult);
266 } catch (FreeboxException e) {
267 logger.debug("Error discovering Player: {}", e.getMessage());