From: Gaël L'hopital Date: Tue, 1 Aug 2023 10:16:59 +0000 (+0200) Subject: [freeboxos] Support randomized MAC addresses by using mDNS name for Wi-Fi hosts ... X-Git-Url: https://git.basschouten.com/?a=commitdiff_plain;h=e792a92a38a912b556b7a25f10bade22a4e32bb8;p=openhab-addons.git [freeboxos] Support randomized MAC addresses by using mDNS name for Wi-Fi hosts (#15299) * Take care of randomized mac addresses by using mDNS name for wifi hosts. Signed-off-by: clinique --- diff --git a/bundles/org.openhab.binding.freeboxos/README.md b/bundles/org.openhab.binding.freeboxos/README.md index a0d76d0176..95712571c5 100644 --- a/bundles/org.openhab.binding.freeboxos/README.md +++ b/bundles/org.openhab.binding.freeboxos/README.md @@ -97,15 +97,28 @@ The *landline* thing requires the following configuration parameters: |------------------|-----------------|------------------------------------------------------------------------|----------|---------| | Refresh Interval | refreshInterval | The refresh interval in seconds which is used to poll for phone state. | No | 2 | -### Network devices: Host and WifiHost +### Network devices: Host -The *host* and *wifihost* things requires the following configuration parameters: +The *host* thing requires the following configuration parameters: | Parameter Label | Parameter ID | Description | Required | Default | |------------------|-----------------|------------------------------------------------------------------------|----------|---------| -| MAC Address | macAddress | The MAC address of the network host . | Yes | | +| MAC Address | macAddress | The MAC address of the network host. | Yes | | | Refresh Interval | refreshInterval | The refresh interval in seconds which is used to poll for phone state. | No | 30 | +### Network devices: WifiHost + +The *wifihost* thing requires the following configuration parameters: + +| Parameter Label | Parameter ID | Description | Required | Default | +|------------------|-----------------|------------------------------------------------------------------------|----------|---------| +| MAC Address | macAddress | The MAC address of the network host. | Yes | | +| Refresh Interval | refreshInterval | The refresh interval in seconds which is used to poll for phone state. | No | 30 | +| mDNS Name | mDNS | The mDNS name of the host. Useful in case of virtual MAC. | No | | + +When used, mDNS will search the host based on its mDNS name and eventually update the MAC address accordingly. +This is useful with devices, especially Apple equipment, that uses randomly generated MAC addresses. + ### Repeater and Vm thing The *repeater* thing is a specialized case of a *wifihost*. The *vm* derives from *host*. They share the same configuration definition: diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LanBrowserManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LanBrowserManager.java index 694d964f22..ac4e44e770 100644 --- a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LanBrowserManager.java +++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LanBrowserManager.java @@ -126,8 +126,8 @@ public class LanBrowserManager extends ListableRest names, List l3connectivities, - @Nullable LanAccessPoint accessPoint) { + @Nullable ZonedDateTime firstActivity, @Nullable List names, + List l3connectivities, @Nullable LanAccessPoint accessPoint) { public @Nullable LanAccessPoint accessPoint() { return accessPoint; @@ -135,15 +135,20 @@ public class LanBrowserManager extends ListableRest getPrimaryName() { return Optional.ofNullable(primaryName); } - public Optional getUPnPName() { - return names.stream().filter(name -> name.source == Source.UPNP).findFirst().map(name -> name.name); + public List getNames() { + List localNames = names; + return localNames != null ? localNames : List.of(); + } + + public Optional getName(Source searchedSource) { + return getNames().stream().filter(name -> name.source == searchedSource).findFirst().map(HostName::name); } public MACAddress getMac() { @@ -216,6 +221,29 @@ public class LanBrowserManager extends ListableRest getHost(HostName identifier) throws FreeboxException { + List hosts = getHosts(); + LanHost result = null; + boolean multiple = false; + for (LanHost host : hosts) { + Optional sourcedName = host.getName(identifier.source); + if (sourcedName.isPresent() && sourcedName.get().equals(identifier.name)) { + // We will not return something if multiple hosts are found. This can happen in case of IP change that + // a previous name remains attached to a different host. + if (result == null) { + result = host; + } else if (!result.getMac().equals(host.getMac())) { + // Multiple hosts with different macs + multiple = true; + } + } + } + if (multiple) { + result = null; + } + return Optional.ofNullable(result); + } + public boolean wakeOnLan(MACAddress mac, String password) throws FreeboxException { Optional target = getHost(mac); if (target.isPresent()) { diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/HostConfiguration.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/HostConfiguration.java index c69194e14a..3b7954ce22 100644 --- a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/HostConfiguration.java +++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/HostConfiguration.java @@ -20,7 +20,6 @@ import inet.ipaddr.mac.MACAddress; /** * The {@link HostConfiguration} is responsible for holding * configuration informations associated to a Freebox Network Device - * thing type * * @author Gaël L'hopital - Initial contribution */ diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/WifiHostConfiguration.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/WifiHostConfiguration.java new file mode 100644 index 0000000000..0e85d74997 --- /dev/null +++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/WifiHostConfiguration.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.freeboxos.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager; +import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.HostName; + +/** + * The {@link WifiHostConfiguration} holds configuration information needed to + * access/poll a wifi network device + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class WifiHostConfiguration extends HostConfiguration { + private String mDNS = ""; + + public @Nullable HostName getIdentifier() { + if (!mDNS.isEmpty()) { + return new HostName(mDNS, LanBrowserManager.Source.MDNS); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/discovery/FreeboxOsDiscoveryService.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/discovery/FreeboxOsDiscoveryService.java index ac84e6e206..63e4a005aa 100644 --- a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/discovery/FreeboxOsDiscoveryService.java +++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/discovery/FreeboxOsDiscoveryService.java @@ -126,8 +126,8 @@ public class FreeboxOsDiscoveryService extends AbstractDiscoveryService implemen try { ThingUID bridgeUID = handler.getThing().getUID(); - List lanHosts = handler.getManager(LanBrowserManager.class).getHosts().stream() - .filter(LanHost::reachable).toList(); + List lanHosts = new ArrayList<>(handler.getManager(LanBrowserManager.class).getHosts().stream() + .filter(LanHost::reachable).toList()); discoverServer(handler.getManager(SystemManager.class), bridgeUID); discoverPhone(handler.getManager(PhoneManager.class), bridgeUID); diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ApiConsumerHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ApiConsumerHandler.java index ed4a699a0d..e507820766 100644 --- a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ApiConsumerHandler.java +++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ApiConsumerHandler.java @@ -56,8 +56,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import inet.ipaddr.IPAddress; -import inet.ipaddr.MACAddressString; -import inet.ipaddr.mac.MACAddress; /** * The {@link ServerHandler} is a base abstract class for all devices made available by the FreeboxOs bridge @@ -70,6 +68,7 @@ abstract class ApiConsumerHandler extends BaseThingHandler implements ApiConsume private final Map> jobs = new HashMap<>(); private @Nullable ServiceRegistration reg; + protected boolean statusDrivenByBridge = true; ApiConsumerHandler(Thing thing) { super(thing); @@ -167,10 +166,12 @@ abstract class ApiConsumerHandler extends BaseThingHandler implements ApiConsume Bridge bridge = getBridge(); if (bridge != null) { BridgeHandler handler = bridge.getHandler(); - if (handler instanceof FreeboxOsHandler) { + if (handler instanceof FreeboxOsHandler fbOsHandler) { if (bridge.getStatus() == ThingStatus.ONLINE) { - updateStatus(ThingStatus.ONLINE); - return (FreeboxOsHandler) handler; + if (statusDrivenByBridge) { + updateStatus(ThingStatus.ONLINE); + } + return fbOsHandler; } updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); } else { @@ -343,10 +344,4 @@ abstract class ApiConsumerHandler extends BaseThingHandler implements ApiConsume public int getClientId() { return ((BigDecimal) getConfig().get(ClientConfiguration.ID)).intValue(); } - - @Override - public MACAddress getMac() { - String mac = (String) getConfig().get(Thing.PROPERTY_MAC_ADDRESS); - return new MACAddressString(mac).getAddress(); - } } diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/FreeplugHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/FreeplugHandler.java index d85e4f30f4..ba0693c302 100644 --- a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/FreeplugHandler.java +++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/FreeplugHandler.java @@ -53,7 +53,7 @@ public class FreeplugHandler extends ApiConsumerHandler { properties.put(Thing.PROPERTY_MODEL_ID, plug.model()); properties.put(ROLE, plug.netRole().name()); properties.put(NET_ID, plug.netId()); - properties.put(ETHERNET_SPEED, String.format("%d Mb/s", plug.ethSpeed())); + properties.put(ETHERNET_SPEED, "%d Mb/s".formatted(plug.ethSpeed())); properties.put(LOCAL, Boolean.valueOf(plug.local()).toString()); properties.put(FULL_DUPLEX, Boolean.valueOf(plug.ethFullDuplex()).toString()); @@ -88,7 +88,7 @@ public class FreeplugHandler extends ApiConsumerHandler { getManager(FreeplugManager.class).reboot(getMac()); logger.debug("Freeplug {} succesfully restarted", getMac()); } catch (FreeboxException e) { - logger.warn("Error restarting freeplug: {}", e.getMessage()); + logger.warn("Error restarting freeplug {}: {}", getMac(), e.getMessage()); } } diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/HostHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/HostHandler.java index d7966549ec..45557d01bb 100644 --- a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/HostHandler.java +++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/HostHandler.java @@ -15,14 +15,13 @@ package org.openhab.binding.freeboxos.internal.handler; import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*; import java.util.Collection; -import java.util.Collections; import java.util.Map; +import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.freeboxos.internal.action.HostActions; import org.openhab.binding.freeboxos.internal.api.FreeboxException; import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager; -import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.HostIntf; import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.LanHost; import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.Source; import org.openhab.binding.freeboxos.internal.api.rest.WebSocketManager; @@ -42,46 +41,56 @@ import org.slf4j.LoggerFactory; public class HostHandler extends ApiConsumerHandler { private final Logger logger = LoggerFactory.getLogger(HostHandler.class); - // We start in pull mode and switch to push after a first update + // We start in pull mode and switch to push after a first update... private boolean pushSubscribed = false; public HostHandler(Thing thing) { super(thing); + statusDrivenByBridge = false; } @Override void initializeProperties(Map properties) throws FreeboxException { - getManager(LanBrowserManager.class).getHost(getMac()).ifPresent(result -> { - LanHost host = result.host(); - properties.put(Thing.PROPERTY_VENDOR, host.vendorName()); - host.getUPnPName().ifPresent(upnpName -> properties.put(Source.UPNP.name(), upnpName)); - }); + LanHost host = getLanHost(); + properties.put(Thing.PROPERTY_VENDOR, host.vendorName()); + host.getName(Source.UPNP).ifPresent(upnpName -> properties.put(Source.UPNP.name(), upnpName)); } @Override public void dispose() { - try { - getManager(WebSocketManager.class).unregisterListener(getMac()); - } catch (FreeboxException e) { - logger.warn("Error unregistering host from the websocket: {}", e.getMessage()); - } + cancelPushSubscription(); super.dispose(); } + protected void cancelPushSubscription() { + if (pushSubscribed) { + try { + getManager(WebSocketManager.class).unregisterListener(getMac()); + } catch (FreeboxException e) { + logger.warn("Error unregistering host from the websocket: {}", e.getMessage()); + } + pushSubscribed = false; + } + } + @Override protected void internalPoll() throws FreeboxException { if (pushSubscribed) { return; } - HostIntf data = getManager(LanBrowserManager.class).getHost(getMac()) - .orElseThrow(() -> new FreeboxException("Host data not found")); - updateConnectivityChannels(data.host()); + LanHost host = getLanHost(); + updateConnectivityChannels(host); logger.debug("Switching to push mode - refreshInterval will now be ignored for Connectivity data"); - getManager(WebSocketManager.class).registerListener(data.host().getMac(), this); + getManager(WebSocketManager.class).registerListener(host.getMac(), this); pushSubscribed = true; } + protected LanHost getLanHost() throws FreeboxException { + return getManager(LanBrowserManager.class).getHost(getMac()).map(hostIntf -> hostIntf.host()) + .orElseThrow(() -> new FreeboxException("Host data not found")); + } + public void updateConnectivityChannels(LanHost host) { updateChannelOnOff(CONNECTIVITY, REACHABLE, host.reachable()); updateChannelDateTimeState(CONNECTIVITY, LAST_SEEN, host.getLastSeen()); @@ -100,6 +109,6 @@ public class HostHandler extends ApiConsumerHandler { @Override public Collection> getServices() { - return Collections.singletonList(HostActions.class); + return Set.of(HostActions.class); } } diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/WifiStationHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/WifiStationHandler.java index c22c9ad333..67b8582508 100644 --- a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/WifiStationHandler.java +++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/WifiStationHandler.java @@ -22,13 +22,19 @@ import org.openhab.binding.freeboxos.internal.api.FreeboxException; import org.openhab.binding.freeboxos.internal.api.rest.APManager; import org.openhab.binding.freeboxos.internal.api.rest.APManager.LanAccessPoint; import org.openhab.binding.freeboxos.internal.api.rest.APManager.Station; +import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager; +import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.HostName; import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.LanHost; import org.openhab.binding.freeboxos.internal.api.rest.RepeaterManager; +import org.openhab.binding.freeboxos.internal.config.WifiHostConfiguration; +import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.unit.Units; import org.openhab.core.thing.Channel; import org.openhab.core.thing.Thing; import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The {@link WifiStationHandler} is responsible for handling everything associated to @@ -40,6 +46,8 @@ import org.openhab.core.types.UnDefType; public class WifiStationHandler extends HostHandler { private static final String SERVER_HOST = "Server"; + private final Logger logger = LoggerFactory.getLogger(WifiStationHandler.class); + public WifiStationHandler(Thing thing) { super(thing); } @@ -90,4 +98,26 @@ public class WifiStationHandler extends HostHandler { private int toQoS(int rssi) { return rssi > -50 ? 4 : rssi > -60 ? 3 : rssi > -70 ? 2 : rssi > -85 ? 1 : 0; } + + @Override + protected LanHost getLanHost() throws FreeboxException { + try { + return super.getLanHost(); + } catch (FreeboxException e) { + HostName identifier = getConfigAs(WifiHostConfiguration.class).getIdentifier(); + if (identifier != null) { + cancelPushSubscription(); + Optional lanHost = getManager(LanBrowserManager.class).getHost(identifier); + return lanHost.map(host -> { + Configuration thingConfig = editConfiguration(); + thingConfig.put(Thing.PROPERTY_MAC_ADDRESS, host.getMac().toColonDelimitedString()); + updateConfiguration(thingConfig); + logger.info("MAC address of the wifihost {} changed, configuration updated to {}", thing.getUID(), + host.getMac()); + return host; + }).orElseThrow(() -> new FreeboxException("Host data not found - mDNS failed also")); + } + throw new FreeboxException("Host not found - no mDNS alternative"); + } + } } diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/wifi-host-config.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/wifi-host-config.xml new file mode 100644 index 0000000000..b810f172b3 --- /dev/null +++ b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/wifi-host-config.xml @@ -0,0 +1,25 @@ + + + + + + + The refresh interval in seconds which is used to poll given device + 30 + + + + The MAC address of the network device + + + + The mDNS name of the network device + true + + + + diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/i18n/freeboxos.properties b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/i18n/freeboxos.properties index 86b59db4e6..201eb4c130 100644 --- a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/i18n/freeboxos.properties +++ b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/i18n/freeboxos.properties @@ -67,6 +67,8 @@ bridge-type.config.freeboxos.api.appToken.label = Application Token bridge-type.config.freeboxos.api.appToken.description = Token generated by the Freebox server bridge-type.config.freeboxos.api.discoverNetDevice.label = Network Device Discovery bridge-type.config.freeboxos.api.discoverNetDevice.description = Enable the discovery of network device things +bridge-type.config.freeboxos.api.discoveryInterval.label = Background Discovery Interval +bridge-type.config.freeboxos.api.discoveryInterval.description = Background discovery interval in minutes (default 10 - 0 disables background discovery) bridge-type.config.freeboxos.api.httpsAvailable.label = HTTPS Available bridge-type.config.freeboxos.api.httpsAvailable.description = Tells if https has been configured on the Freebox bridge-type.config.freeboxos.api.httpsPort.label = HTTPS port @@ -77,6 +79,8 @@ thing-type.config.freeboxos.home-node.id.label = ID thing-type.config.freeboxos.home-node.id.description = Id of the Home Node thing-type.config.freeboxos.home-node.refreshInterval.label = Refresh Interval thing-type.config.freeboxos.home-node.refreshInterval.description = The refresh interval in seconds which is used to poll the Node +thing-type.config.freeboxos.host.mDNS.label = mDNS Name +thing-type.config.freeboxos.host.mDNS.description = The mDNS name of the network device thing-type.config.freeboxos.host.macAddress.label = MAC Address thing-type.config.freeboxos.host.macAddress.description = The MAC address of the network device thing-type.config.freeboxos.host.refreshInterval.label = Refresh Interval diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/wifihost-thing-type.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/wifihost-thing-type.xml index 581ba2c50c..fa69e08534 100644 --- a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/wifihost-thing-type.xml +++ b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/wifihost-thing-type.xml @@ -19,7 +19,7 @@ macAddress - +