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;
74 private final Logger logger = LoggerFactory.getLogger(FreeboxOsDiscoveryService.class);
76 private Optional<ScheduledFuture<?>> backgroundFuture = Optional.empty();
77 private @Nullable FreeboxOsHandler bridgeHandler;
79 public FreeboxOsDiscoveryService() {
80 super(Stream.of(THINGS_TYPES_UIDS, HOME_TYPES_UIDS).flatMap(Set::stream).collect(Collectors.toSet()),
81 DISCOVERY_TIME_SECONDS);
85 public void deactivate() {
90 public void setThingHandler(@Nullable ThingHandler handler) {
91 if (handler instanceof FreeboxOsHandler freeboxosHandler) {
92 bridgeHandler = freeboxosHandler;
98 public @Nullable ThingHandler getThingHandler() {
103 protected void startBackgroundDiscovery() {
104 stopBackgroundDiscovery();
105 FreeboxOsHandler handler = bridgeHandler;
106 if (handler != null) {
107 int interval = handler.getConfiguration().discoveryInterval;
109 backgroundFuture = Optional
110 .of(scheduler.scheduleWithFixedDelay(this::startScan, 1, interval, TimeUnit.MINUTES));
116 protected void stopBackgroundDiscovery() {
117 backgroundFuture.ifPresent(future -> future.cancel(true));
118 backgroundFuture = Optional.empty();
122 protected void startScan() {
123 logger.debug("Starting Freebox discovery scan");
124 FreeboxOsHandler handler = bridgeHandler;
125 if (handler != null && handler.getThing().getStatus() == ThingStatus.ONLINE) {
127 ThingUID bridgeUID = handler.getThing().getUID();
129 List<LanHost> lanHosts = handler.getManager(LanBrowserManager.class).getHosts().stream()
130 .filter(LanHost::reachable).toList();
132 discoverServer(handler.getManager(SystemManager.class), bridgeUID);
133 discoverPhone(handler.getManager(PhoneManager.class), bridgeUID);
134 discoverPlugs(handler.getManager(FreeplugManager.class), bridgeUID);
135 discoverRepeater(handler.getManager(RepeaterManager.class), bridgeUID, lanHosts);
136 discoverPlayer(handler.getManager(PlayerManager.class), bridgeUID, lanHosts);
137 discoverVM(handler.getManager(VmManager.class), bridgeUID, lanHosts);
138 discoverHome(handler.getManager(HomeManager.class), bridgeUID);
139 if (handler.getConfiguration().discoverNetDevice) {
140 discoverHosts(handler, bridgeUID, lanHosts);
142 } catch (FreeboxException e) {
143 logger.warn("Error while requesting data for things discovery: {}", e.getMessage());
148 private void discoverHome(HomeManager homeManager, ThingUID bridgeUID) throws FreeboxException {
149 NodeConfigurationBuilder builder = NodeConfigurationBuilder.getInstance();
151 homeManager.getHomeNodes().forEach(
152 node -> builder.configure(bridgeUID, node).ifPresent(result -> thingDiscovered(result.build())));
153 } catch (PermissionException e) {
154 logger.warn("Missing permission to discover Home {}", e.getPermission());
158 private void discoverPlugs(FreeplugManager freeplugManager, ThingUID bridgeUID) {
159 FreeplugConfigurationBuilder builder = FreeplugConfigurationBuilder.getInstance();
161 freeplugManager.getPlugs().forEach(plug -> thingDiscovered(builder.configure(bridgeUID, plug).build()));
162 } catch (FreeboxException e) {
163 logger.warn("Error discovering freeplugs {}", e.getMessage());
167 private void discoverPhone(PhoneManager phoneManager, ThingUID bridgeUID) throws FreeboxException {
168 PhoneConfigurationBuilder builder = PhoneConfigurationBuilder.getInstance();
169 List<Status> statuses = List.of();
171 statuses = phoneManager.getPhoneStatuses();
172 statuses.forEach(phone -> thingDiscovered(builder.configure(bridgeUID, phone).build()));
173 } catch (FreeboxException e) {
174 logger.warn("Error discovering phones {}", e.getMessage());
176 if (!statuses.isEmpty()) {
177 ThingUID thingUID = new ThingUID(THING_TYPE_CALL, bridgeUID, "landline");
178 logger.debug("Adding new Call thing {} to inbox", thingUID);
179 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
180 .withLabel("Freebox Calls").build();
181 thingDiscovered(discoveryResult);
185 private void discoverHosts(FreeboxOsHandler localHandler, ThingUID bridgeUID, List<LanHost> lanHosts)
186 throws FreeboxException {
188 List<MACAddress> wifiMacs = new ArrayList<>();
189 wifiMacs.addAll(localHandler.getManager(APManager.class).getStations().stream().map(Station::mac).toList());
191 localHandler.getManager(RepeaterManager.class).getHosts().stream().map(LanHost::getMac).toList());
193 lanHosts.forEach(lanHost -> {
194 MACAddress mac = lanHost.getMac();
195 String macString = mac.toColonDelimitedString();
196 ThingUID thingUID = new ThingUID(wifiMacs.contains(mac) ? THING_TYPE_WIFI_HOST : THING_TYPE_HOST,
197 bridgeUID, mac.toHexString(false));
198 logger.debug("Adding new Freebox Network Host {} to inbox", thingUID);
199 DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
200 .withLabel(lanHost.getPrimaryName().orElse("Network Device %s".formatted(macString)))
201 .withTTL(300).withProperty(Thing.PROPERTY_MAC_ADDRESS, macString)
202 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS);
203 thingDiscovered(builder.build());
205 } catch (PermissionException e) {
206 logger.warn("Missing permission to discover Hosts {}", e.getPermission());
210 private void discoverVM(VmManager vmManager, ThingUID bridgeUID, List<LanHost> lanHosts) throws FreeboxException {
212 vmManager.getDevices().forEach(vm -> {
213 MACAddress mac = vm.mac();
214 lanHosts.removeIf(host -> host.getMac().equals(mac));
216 ThingUID thingUID = new ThingUID(THING_TYPE_VM, bridgeUID, mac.toHexString(false));
217 logger.debug("Adding new VM Device {} to inbox", thingUID);
218 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
219 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS)
220 .withLabel("%s (VM)".formatted(vm.name())).withProperty(ClientConfiguration.ID, vm.id())
221 .withProperty(Thing.PROPERTY_MAC_ADDRESS, mac.toColonDelimitedString()).build();
222 thingDiscovered(discoveryResult);
224 } catch (PermissionException e) {
225 logger.warn("Missing permission to discover VM {}", e.getPermission());
229 private void discoverRepeater(RepeaterManager repeaterManager, ThingUID bridgeUID, List<LanHost> lanHosts)
230 throws FreeboxException {
232 List<Repeater> repeaters = repeaterManager.getDevices();
233 repeaters.forEach(repeater -> {
234 MACAddress mac = repeater.mainMac();
235 lanHosts.removeIf(host -> host.getMac().equals(mac));
237 ThingUID thingUID = new ThingUID(THING_TYPE_REPEATER, bridgeUID, Integer.toString(repeater.id()));
238 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
239 .withLabel("Repeater %s".formatted(repeater.name()))
240 .withProperty(Thing.PROPERTY_MAC_ADDRESS, mac.toColonDelimitedString())
241 .withProperty(ClientConfiguration.ID, repeater.id())
242 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
243 thingDiscovered(discoveryResult);
245 } catch (PermissionException e) {
246 logger.warn("Missing permission to discover Repeater {}", e.getPermission());
250 private void discoverServer(SystemManager systemManager, ThingUID bridgeUID) throws FreeboxException {
252 Config config = systemManager.getConfig();
254 ThingTypeUID targetType = config.boardName().startsWith("fbxgw7") ? THING_TYPE_DELTA
255 : THING_TYPE_REVOLUTION;
256 ThingUID thingUID = new ThingUID(targetType, bridgeUID, config.serial());
257 logger.debug("Adding new Freebox Server {} to inbox", thingUID);
259 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
260 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).withLabel(config.modelInfo().prettyName())
261 .withProperty(Thing.PROPERTY_MAC_ADDRESS, config.mac()).build();
262 thingDiscovered(discoveryResult);
263 } catch (PermissionException e) {
264 logger.warn("Missing permission to discover Server {}", e.getPermission());
268 private void discoverPlayer(PlayerManager playerManager, ThingUID bridgeUID, List<LanHost> lanHosts)
269 throws FreeboxException {
271 for (Player player : playerManager.getDevices()) {
272 lanHosts.removeIf(host -> host.getMac().equals(player.mac()));
273 ThingUID thingUID = new ThingUID(player.apiAvailable() ? THING_TYPE_ACTIVE_PLAYER : THING_TYPE_PLAYER,
274 bridgeUID, Integer.toString(player.id()));
275 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
276 .withProperty(Thing.PROPERTY_MAC_ADDRESS, player.mac().toColonDelimitedString())
277 .withProperty(ClientConfiguration.ID, player.id()).withLabel(player.deviceName())
278 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
279 thingDiscovered(discoveryResult);
281 } catch (PermissionException e) {
282 logger.warn("Missing permission to discover Player {}", e.getPermission());