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.PermissionException;
29 import org.openhab.binding.freeboxos.internal.api.rest.APManager;
30 import org.openhab.binding.freeboxos.internal.api.rest.APManager.Station;
31 import org.openhab.binding.freeboxos.internal.api.rest.FreeplugManager;
32 import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
33 import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager;
34 import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.LanHost;
35 import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager;
36 import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager.Status;
37 import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager;
38 import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Player;
39 import org.openhab.binding.freeboxos.internal.api.rest.RepeaterManager;
40 import org.openhab.binding.freeboxos.internal.api.rest.RepeaterManager.Repeater;
41 import org.openhab.binding.freeboxos.internal.api.rest.SystemManager;
42 import org.openhab.binding.freeboxos.internal.api.rest.SystemManager.Config;
43 import org.openhab.binding.freeboxos.internal.api.rest.VmManager;
44 import org.openhab.binding.freeboxos.internal.config.ClientConfiguration;
45 import org.openhab.binding.freeboxos.internal.config.FreeplugConfigurationBuilder;
46 import org.openhab.binding.freeboxos.internal.config.NodeConfigurationBuilder;
47 import org.openhab.binding.freeboxos.internal.config.PhoneConfigurationBuilder;
48 import org.openhab.binding.freeboxos.internal.handler.FreeboxOsHandler;
49 import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService;
50 import org.openhab.core.config.discovery.DiscoveryResult;
51 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
52 import org.openhab.core.thing.Thing;
53 import org.openhab.core.thing.ThingStatus;
54 import org.openhab.core.thing.ThingTypeUID;
55 import org.openhab.core.thing.ThingUID;
56 import org.osgi.service.component.annotations.Component;
57 import org.osgi.service.component.annotations.ServiceScope;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
61 import inet.ipaddr.mac.MACAddress;
64 * The {@link FreeboxOsDiscoveryService} is responsible for discovering all things
65 * except the Freebox API thing itself
67 * @author Gaƫl L'hopital - Initial contribution
69 @Component(scope = ServiceScope.PROTOTYPE, service = FreeboxOsDiscoveryService.class)
71 public class FreeboxOsDiscoveryService extends AbstractThingHandlerDiscoveryService<FreeboxOsHandler> {
72 private static final int DISCOVERY_TIME_SECONDS = 10;
74 private final Logger logger = LoggerFactory.getLogger(FreeboxOsDiscoveryService.class);
76 private Optional<ScheduledFuture<?>> backgroundFuture = Optional.empty();
78 public FreeboxOsDiscoveryService() {
79 super(FreeboxOsHandler.class,
80 Stream.of(THINGS_TYPES_UIDS, HOME_TYPES_UIDS).flatMap(Set::stream).collect(Collectors.toSet()),
81 DISCOVERY_TIME_SECONDS);
85 protected void startBackgroundDiscovery() {
86 stopBackgroundDiscovery();
87 int interval = thingHandler.getConfiguration().discoveryInterval;
89 backgroundFuture = Optional
90 .of(scheduler.scheduleWithFixedDelay(this::startScan, 1, interval, TimeUnit.MINUTES));
95 protected void stopBackgroundDiscovery() {
96 backgroundFuture.ifPresent(future -> future.cancel(true));
97 backgroundFuture = Optional.empty();
101 protected void startScan() {
102 logger.debug("Starting Freebox discovery scan");
103 if (thingHandler.getThing().getStatus() == ThingStatus.ONLINE) {
105 ThingUID bridgeUID = thingHandler.getThing().getUID();
107 List<LanHost> lanHosts = new ArrayList<>(thingHandler.getManager(LanBrowserManager.class).getHosts()
108 .stream().filter(LanHost::reachable).toList());
110 discoverServer(thingHandler.getManager(SystemManager.class), bridgeUID);
111 discoverPhone(thingHandler.getManager(PhoneManager.class), bridgeUID);
112 discoverPlugs(thingHandler.getManager(FreeplugManager.class), bridgeUID);
113 discoverRepeater(thingHandler.getManager(RepeaterManager.class), bridgeUID, lanHosts);
114 discoverPlayer(thingHandler.getManager(PlayerManager.class), bridgeUID, lanHosts);
115 discoverVM(thingHandler.getManager(VmManager.class), bridgeUID, lanHosts);
116 discoverHome(thingHandler.getManager(HomeManager.class), bridgeUID);
117 if (thingHandler.getConfiguration().discoverNetDevice) {
118 discoverHosts(thingHandler, bridgeUID, lanHosts);
120 } catch (FreeboxException e) {
121 logger.warn("Error while requesting data for things discovery: {}", e.getMessage());
126 private void discoverHome(HomeManager homeManager, ThingUID bridgeUID) throws FreeboxException {
127 NodeConfigurationBuilder builder = NodeConfigurationBuilder.getInstance();
129 homeManager.getHomeNodes().forEach(
130 node -> builder.configure(bridgeUID, node).ifPresent(result -> thingDiscovered(result.build())));
131 } catch (PermissionException e) {
132 logger.warn("Missing permission to discover Home {}", e.getPermission());
136 private void discoverPlugs(FreeplugManager freeplugManager, ThingUID bridgeUID) {
137 FreeplugConfigurationBuilder builder = FreeplugConfigurationBuilder.getInstance();
139 freeplugManager.getPlugs().forEach(plug -> thingDiscovered(builder.configure(bridgeUID, plug).build()));
140 } catch (FreeboxException e) {
141 logger.warn("Error discovering freeplugs {}", e.getMessage());
145 private void discoverPhone(PhoneManager phoneManager, ThingUID bridgeUID) throws FreeboxException {
146 PhoneConfigurationBuilder builder = PhoneConfigurationBuilder.getInstance();
147 List<Status> statuses = List.of();
149 statuses = phoneManager.getPhoneStatuses();
150 statuses.forEach(phone -> thingDiscovered(builder.configure(bridgeUID, phone).build()));
151 } catch (FreeboxException e) {
152 logger.warn("Error discovering phones {}", e.getMessage());
154 if (!statuses.isEmpty()) {
155 ThingUID thingUID = new ThingUID(THING_TYPE_CALL, bridgeUID, "landline");
156 logger.debug("Adding new Call thing {} to inbox", thingUID);
157 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
158 .withLabel("Freebox Calls").build();
159 thingDiscovered(discoveryResult);
163 private void discoverHosts(FreeboxOsHandler localHandler, ThingUID bridgeUID, List<LanHost> lanHosts)
164 throws FreeboxException {
166 List<MACAddress> wifiMacs = new ArrayList<>();
167 wifiMacs.addAll(localHandler.getManager(APManager.class).getStations().stream().map(Station::mac).toList());
169 localHandler.getManager(RepeaterManager.class).getHosts().stream().map(LanHost::getMac).toList());
171 lanHosts.forEach(lanHost -> {
172 MACAddress mac = lanHost.getMac();
173 String macString = mac.toColonDelimitedString();
174 ThingUID thingUID = new ThingUID(wifiMacs.contains(mac) ? THING_TYPE_WIFI_HOST : THING_TYPE_HOST,
175 bridgeUID, mac.toHexString(false));
176 logger.debug("Adding new Freebox Network Host {} to inbox", thingUID);
177 DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
178 .withLabel(lanHost.getPrimaryName().orElse("Network Device %s".formatted(macString)))
179 .withTTL(300).withProperty(Thing.PROPERTY_MAC_ADDRESS, macString)
180 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS);
181 thingDiscovered(builder.build());
183 } catch (PermissionException e) {
184 logger.warn("Missing permission to discover Hosts {}", e.getPermission());
188 private void discoverVM(VmManager vmManager, ThingUID bridgeUID, List<LanHost> lanHosts) throws FreeboxException {
190 vmManager.getDevices().forEach(vm -> {
191 MACAddress mac = vm.mac();
192 lanHosts.removeIf(host -> host.getMac().equals(mac));
194 ThingUID thingUID = new ThingUID(THING_TYPE_VM, bridgeUID, mac.toHexString(false));
195 logger.debug("Adding new VM Device {} to inbox", thingUID);
196 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
197 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS)
198 .withLabel("%s (VM)".formatted(vm.name())).withProperty(ClientConfiguration.ID, vm.id())
199 .withProperty(Thing.PROPERTY_MAC_ADDRESS, mac.toColonDelimitedString()).build();
200 thingDiscovered(discoveryResult);
202 } catch (PermissionException e) {
203 logger.warn("Missing permission to discover VM {}", e.getPermission());
207 private void discoverRepeater(RepeaterManager repeaterManager, ThingUID bridgeUID, List<LanHost> lanHosts)
208 throws FreeboxException {
210 List<Repeater> repeaters = repeaterManager.getDevices();
211 repeaters.forEach(repeater -> {
212 MACAddress mac = repeater.mainMac();
213 lanHosts.removeIf(host -> host.getMac().equals(mac));
215 ThingUID thingUID = new ThingUID(THING_TYPE_REPEATER, bridgeUID, Integer.toString(repeater.id()));
216 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
217 .withLabel("Repeater %s".formatted(repeater.name()))
218 .withProperty(Thing.PROPERTY_MAC_ADDRESS, mac.toColonDelimitedString())
219 .withProperty(ClientConfiguration.ID, repeater.id())
220 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
221 thingDiscovered(discoveryResult);
223 } catch (PermissionException e) {
224 logger.warn("Missing permission to discover Repeater {}", e.getPermission());
228 private void discoverServer(SystemManager systemManager, ThingUID bridgeUID) throws FreeboxException {
230 Config config = systemManager.getConfig();
232 ThingTypeUID targetType = config.boardName().startsWith("fbxgw7") ? THING_TYPE_DELTA
233 : THING_TYPE_REVOLUTION;
234 ThingUID thingUID = new ThingUID(targetType, bridgeUID, config.serial());
235 logger.debug("Adding new Freebox Server {} to inbox", thingUID);
237 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
238 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).withLabel(config.modelInfo().prettyName())
239 .withProperty(Thing.PROPERTY_MAC_ADDRESS, config.mac()).build();
240 thingDiscovered(discoveryResult);
241 } catch (PermissionException e) {
242 logger.warn("Missing permission to discover Server {}", e.getPermission());
246 private void discoverPlayer(PlayerManager playerManager, ThingUID bridgeUID, List<LanHost> lanHosts)
247 throws FreeboxException {
249 for (Player player : playerManager.getDevices()) {
250 lanHosts.removeIf(host -> host.getMac().equals(player.mac()));
251 ThingUID thingUID = new ThingUID(player.apiAvailable() ? THING_TYPE_ACTIVE_PLAYER : THING_TYPE_PLAYER,
252 bridgeUID, Integer.toString(player.id()));
253 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
254 .withProperty(Thing.PROPERTY_MAC_ADDRESS, player.mac().toColonDelimitedString())
255 .withProperty(ClientConfiguration.ID, player.id()).withLabel(player.deviceName())
256 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
257 thingDiscovered(discoveryResult);
259 } catch (PermissionException e) {
260 logger.warn("Missing permission to discover Player {}", e.getPermission());