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.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.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.freeboxos.internal.api.FreeboxException;
29 import org.openhab.binding.freeboxos.internal.api.PermissionException;
30 import org.openhab.binding.freeboxos.internal.api.rest.APManager;
31 import org.openhab.binding.freeboxos.internal.api.rest.APManager.Station;
32 import org.openhab.binding.freeboxos.internal.api.rest.FreeplugManager;
33 import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
34 import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager;
35 import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.LanHost;
36 import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager;
37 import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager.Status;
38 import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager;
39 import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Player;
40 import org.openhab.binding.freeboxos.internal.api.rest.RepeaterManager;
41 import org.openhab.binding.freeboxos.internal.api.rest.RepeaterManager.Repeater;
42 import org.openhab.binding.freeboxos.internal.api.rest.SystemManager;
43 import org.openhab.binding.freeboxos.internal.api.rest.SystemManager.Config;
44 import org.openhab.binding.freeboxos.internal.api.rest.VmManager;
45 import org.openhab.binding.freeboxos.internal.config.ClientConfiguration;
46 import org.openhab.binding.freeboxos.internal.config.FreeplugConfigurationBuilder;
47 import org.openhab.binding.freeboxos.internal.config.NodeConfigurationBuilder;
48 import org.openhab.binding.freeboxos.internal.config.PhoneConfigurationBuilder;
49 import org.openhab.binding.freeboxos.internal.handler.FreeboxOsHandler;
50 import org.openhab.core.config.discovery.AbstractDiscoveryService;
51 import org.openhab.core.config.discovery.DiscoveryResult;
52 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
53 import org.openhab.core.thing.Thing;
54 import org.openhab.core.thing.ThingStatus;
55 import org.openhab.core.thing.ThingTypeUID;
56 import org.openhab.core.thing.ThingUID;
57 import org.openhab.core.thing.binding.ThingHandler;
58 import org.openhab.core.thing.binding.ThingHandlerService;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
62 import inet.ipaddr.mac.MACAddress;
65 * The {@link FreeboxOsDiscoveryService} is responsible for discovering all things
66 * except the Freebox API thing itself
68 * @author Gaƫl L'hopital - Initial contribution
71 public class FreeboxOsDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
72 private static final int DISCOVERY_TIME_SECONDS = 10;
73 private static final int BACKGROUND_SCAN_REFRESH_MINUTES = 1;
75 private final Logger logger = LoggerFactory.getLogger(FreeboxOsDiscoveryService.class);
77 private Optional<ScheduledFuture<?>> backgroundFuture = Optional.empty();
78 private @Nullable FreeboxOsHandler bridgeHandler;
80 public FreeboxOsDiscoveryService() {
81 super(Stream.of(THINGS_TYPES_UIDS, HOME_TYPES_UIDS).flatMap(Set::stream).collect(Collectors.toSet()),
82 DISCOVERY_TIME_SECONDS);
86 public void deactivate() {
91 public void setThingHandler(@Nullable ThingHandler handler) {
92 if (handler instanceof FreeboxOsHandler) {
93 bridgeHandler = (FreeboxOsHandler) handler;
99 public @Nullable ThingHandler getThingHandler() {
100 return bridgeHandler;
104 protected void startBackgroundDiscovery() {
105 stopBackgroundDiscovery();
106 backgroundFuture = Optional.of(scheduler.scheduleWithFixedDelay(this::startScan,
107 BACKGROUND_SCAN_REFRESH_MINUTES, BACKGROUND_SCAN_REFRESH_MINUTES, TimeUnit.MINUTES));
111 protected void stopBackgroundDiscovery() {
112 backgroundFuture.ifPresent(future -> future.cancel(true));
113 backgroundFuture = Optional.empty();
117 protected void startScan() {
118 logger.debug("Starting Freebox discovery scan");
119 FreeboxOsHandler localHandler = bridgeHandler;
120 if (localHandler != null && localHandler.getThing().getStatus() == ThingStatus.ONLINE) {
122 ThingUID bridgeUID = localHandler.getThing().getUID();
124 List<LanHost> lanHosts = localHandler.getManager(LanBrowserManager.class).getHosts().stream()
125 .filter(LanHost::reachable).collect(Collectors.toList());
127 discoverServer(localHandler.getManager(SystemManager.class), bridgeUID);
128 discoverPhone(localHandler.getManager(PhoneManager.class), bridgeUID);
129 discoverPlugs(localHandler.getManager(FreeplugManager.class), bridgeUID);
130 discoverRepeater(localHandler.getManager(RepeaterManager.class), bridgeUID, lanHosts);
131 discoverPlayer(localHandler.getManager(PlayerManager.class), bridgeUID, lanHosts);
132 discoverVM(localHandler.getManager(VmManager.class), bridgeUID, lanHosts);
133 discoverHome(localHandler.getManager(HomeManager.class), bridgeUID);
134 if (localHandler.getConfiguration().discoverNetDevice) {
135 discoverHosts(localHandler, bridgeUID, lanHosts);
137 } catch (FreeboxException e) {
138 logger.warn("Error while requesting data for things discovery: {}", e.getMessage());
143 private void discoverHome(HomeManager homeManager, ThingUID bridgeUID) throws FreeboxException {
144 NodeConfigurationBuilder builder = NodeConfigurationBuilder.getInstance();
146 homeManager.getHomeNodes().forEach(
147 node -> builder.configure(bridgeUID, node).ifPresent(result -> thingDiscovered(result.build())));
148 } catch (PermissionException e) {
149 logger.warn("Missing permission to discover Home {}", e.getPermission());
153 private void discoverPlugs(FreeplugManager freeplugManager, ThingUID bridgeUID) {
154 FreeplugConfigurationBuilder builder = FreeplugConfigurationBuilder.getInstance();
156 freeplugManager.getPlugs().forEach(plug -> thingDiscovered(builder.configure(bridgeUID, plug).build()));
157 } catch (FreeboxException e) {
158 logger.warn("Error discovering freeplugs {}", e.getMessage());
162 private void discoverPhone(PhoneManager phoneManager, ThingUID bridgeUID) throws FreeboxException {
163 PhoneConfigurationBuilder builder = PhoneConfigurationBuilder.getInstance();
164 List<Status> statuses = List.of();
166 statuses = phoneManager.getPhoneStatuses();
167 statuses.forEach(phone -> thingDiscovered(builder.configure(bridgeUID, phone).build()));
168 } catch (FreeboxException e) {
169 logger.warn("Error discovering phones {}", e.getMessage());
171 if (!statuses.isEmpty()) {
172 ThingUID thingUID = new ThingUID(THING_TYPE_CALL, bridgeUID, "landline");
173 logger.debug("Adding new Call thing {} to inbox", thingUID);
174 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
175 .withLabel("Freebox Calls").build();
176 thingDiscovered(discoveryResult);
180 private void discoverHosts(FreeboxOsHandler localHandler, ThingUID bridgeUID, List<LanHost> lanHosts)
181 throws FreeboxException {
183 List<MACAddress> wifiMacs = new ArrayList<>();
184 wifiMacs.addAll(localHandler.getManager(APManager.class).getStations().stream().map(Station::mac)
185 .collect(Collectors.toList()));
186 wifiMacs.addAll(localHandler.getManager(RepeaterManager.class).getHosts().stream().map(LanHost::getMac)
187 .collect(Collectors.toList()));
189 lanHosts.forEach(lanHost -> {
190 MACAddress mac = lanHost.getMac();
191 String macString = mac.toColonDelimitedString();
192 ThingUID thingUID = new ThingUID(wifiMacs.contains(mac) ? THING_TYPE_WIFI_HOST : THING_TYPE_HOST,
193 bridgeUID, mac.toHexString(false));
194 logger.debug("Adding new Freebox Network Host {} to inbox", thingUID);
195 DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
196 .withLabel(lanHost.getPrimaryName().orElse("Network Device %s".formatted(macString)))
197 .withTTL(300).withProperty(Thing.PROPERTY_MAC_ADDRESS, macString)
198 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS);
199 thingDiscovered(builder.build());
201 } catch (PermissionException e) {
202 logger.warn("Missing permission to discover Hosts {}", e.getPermission());
206 private void discoverVM(VmManager vmManager, ThingUID bridgeUID, List<LanHost> lanHosts) throws FreeboxException {
208 vmManager.getDevices().forEach(vm -> {
209 MACAddress mac = vm.mac();
210 lanHosts.removeIf(host -> host.getMac().equals(mac));
212 ThingUID thingUID = new ThingUID(THING_TYPE_VM, bridgeUID, mac.toHexString(false));
213 logger.debug("Adding new VM Device {} to inbox", thingUID);
214 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
215 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS)
216 .withLabel("%s (VM)".formatted(vm.name())).withProperty(ClientConfiguration.ID, vm.id())
217 .withProperty(Thing.PROPERTY_MAC_ADDRESS, mac.toColonDelimitedString()).build();
218 thingDiscovered(discoveryResult);
220 } catch (PermissionException e) {
221 logger.warn("Missing permission to discover VM {}", e.getPermission());
225 private void discoverRepeater(RepeaterManager repeaterManager, ThingUID bridgeUID, List<LanHost> lanHosts)
226 throws FreeboxException {
228 List<Repeater> repeaters = repeaterManager.getDevices();
229 repeaters.forEach(repeater -> {
230 MACAddress mac = repeater.mainMac();
231 lanHosts.removeIf(host -> host.getMac().equals(mac));
233 ThingUID thingUID = new ThingUID(THING_TYPE_REPEATER, bridgeUID, Integer.toString(repeater.id()));
234 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
235 .withLabel("Repeater %s".formatted(repeater.name()))
236 .withProperty(Thing.PROPERTY_MAC_ADDRESS, mac.toColonDelimitedString())
237 .withProperty(ClientConfiguration.ID, repeater.id())
238 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
239 thingDiscovered(discoveryResult);
241 } catch (PermissionException e) {
242 logger.warn("Missing permission to discover Repeater {}", e.getPermission());
246 private void discoverServer(SystemManager systemManager, ThingUID bridgeUID) throws FreeboxException {
248 Config config = systemManager.getConfig();
250 ThingTypeUID targetType = config.boardName().startsWith("fbxgw7") ? THING_TYPE_DELTA
251 : THING_TYPE_REVOLUTION;
252 ThingUID thingUID = new ThingUID(targetType, bridgeUID, config.serial());
253 logger.debug("Adding new Freebox Server {} to inbox", thingUID);
255 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
256 .withProperty(Thing.PROPERTY_MAC_ADDRESS, config.mac())
257 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).withLabel(config.modelInfo().prettyName())
259 thingDiscovered(discoveryResult);
260 } catch (PermissionException e) {
261 logger.warn("Missing permission to discover Server {}", e.getPermission());
265 private void discoverPlayer(PlayerManager playerManager, ThingUID bridgeUID, List<LanHost> lanHosts)
266 throws FreeboxException {
268 for (Player player : playerManager.getDevices()) {
269 lanHosts.removeIf(host -> host.getMac().equals(player.mac()));
270 ThingUID thingUID = new ThingUID(player.apiAvailable() ? THING_TYPE_ACTIVE_PLAYER : THING_TYPE_PLAYER,
271 bridgeUID, Integer.toString(player.id()));
272 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
273 .withLabel(player.deviceName())
274 .withProperty(Thing.PROPERTY_MAC_ADDRESS, player.mac().toColonDelimitedString())
275 .withProperty(ClientConfiguration.ID, player.id())
276 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
277 thingDiscovered(discoveryResult);
279 } catch (PermissionException e) {
280 logger.warn("Missing permission to discover Player {}", e.getPermission());