]> git.basschouten.com Git - openhab-addons.git/commitdiff
[freeboxos] Support randomized MAC addresses by using mDNS name for Wi-Fi hosts ...
authorGaël L'hopital <gael@lhopital.org>
Tue, 1 Aug 2023 10:16:59 +0000 (12:16 +0200)
committerGitHub <noreply@github.com>
Tue, 1 Aug 2023 10:16:59 +0000 (12:16 +0200)
* Take care of randomized mac addresses by using mDNS name for wifi hosts.

Signed-off-by: clinique <gael@lhopital.org>
12 files changed:
bundles/org.openhab.binding.freeboxos/README.md
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LanBrowserManager.java
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/HostConfiguration.java
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/WifiHostConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/discovery/FreeboxOsDiscoveryService.java
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ApiConsumerHandler.java
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/FreeplugHandler.java
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/HostHandler.java
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/WifiStationHandler.java
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/wifi-host-config.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/i18n/freeboxos.properties
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/wifihost-thing-type.xml

index a0d76d01763fcaf4ad21d3d6efc74ece9d31f96c..95712571c5d536c462577fb40a5a0a016cccda26 100644 (file)
@@ -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: 
index 694d964f22d5a86ff94675a4dfe6fa71e480000a..ac4e44e7705c06815e6cdd59818785b6a7cb7b59 100644 (file)
@@ -126,8 +126,8 @@ public class LanBrowserManager extends ListableRest<LanBrowserManager.Interface,
     public static record LanHost(String id, @Nullable String primaryName, HostType hostType, boolean primaryNameManual,
             L2Ident l2ident, @Nullable String vendorName, boolean persistent, boolean reachable,
             @Nullable ZonedDateTime lastTimeReachable, boolean active, @Nullable ZonedDateTime lastActivity,
-            @Nullable ZonedDateTime firstActivity, List<HostName> names, List<L3Connectivity> l3connectivities,
-            @Nullable LanAccessPoint accessPoint) {
+            @Nullable ZonedDateTime firstActivity, @Nullable List<HostName> names,
+            List<L3Connectivity> l3connectivities, @Nullable LanAccessPoint accessPoint) {
 
         public @Nullable LanAccessPoint accessPoint() {
             return accessPoint;
@@ -135,15 +135,20 @@ public class LanBrowserManager extends ListableRest<LanBrowserManager.Interface,
 
         public String vendorName() {
             String localVendor = vendorName;
-            return localVendor != null ? localVendor : "Unknown";
+            return localVendor == null || localVendor.isEmpty() ? "Unknown" : localVendor;
         }
 
         public Optional<String> getPrimaryName() {
             return Optional.ofNullable(primaryName);
         }
 
-        public Optional<String> getUPnPName() {
-            return names.stream().filter(name -> name.source == Source.UPNP).findFirst().map(name -> name.name);
+        public List<HostName> getNames() {
+            List<HostName> localNames = names;
+            return localNames != null ? localNames : List.of();
+        }
+
+        public Optional<String> 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<LanBrowserManager.Interface,
         return Optional.empty();
     }
 
+    public Optional<LanHost> getHost(HostName identifier) throws FreeboxException {
+        List<LanHost> hosts = getHosts();
+        LanHost result = null;
+        boolean multiple = false;
+        for (LanHost host : hosts) {
+            Optional<String> 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<HostIntf> target = getHost(mac);
         if (target.isPresent()) {
index c69194e14a04e9f97142bd86a859fdd8f75cacac..3b7954ce22a4572e55fd010028047cdec041df2f 100644 (file)
@@ -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 (file)
index 0000000..0e85d74
--- /dev/null
@@ -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;
+    }
+}
index ac84e6e206235a7bf5536f2416a50cf994c948d2..63e4a005aa40af4d1453dcdc1bb73df8cdd8737c 100644 (file)
@@ -126,8 +126,8 @@ public class FreeboxOsDiscoveryService extends AbstractDiscoveryService implemen
             try {
                 ThingUID bridgeUID = handler.getThing().getUID();
 
-                List<LanHost> lanHosts = handler.getManager(LanBrowserManager.class).getHosts().stream()
-                        .filter(LanHost::reachable).toList();
+                List<LanHost> 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);
index ed4a699a0d1c8b5854de0bda47d27e06a3b6702b..e5078207668aa3a69784251cf94c0904efa3788c 100644 (file)
@@ -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<String, ScheduledFuture<?>> 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();
-    }
 }
index d85e4f30f43688c0f85f1a8254e5b9bb83e4cca8..ba0693c302843568cf19a8e152901d6f9c7ed4dc 100644 (file)
@@ -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());
         }
     }
 
index d7966549eca5caded484aa5efb070013529ac6fe..45557d01bb4b5c6d26f7e208937e1af5f6fe55b7 100644 (file)
@@ -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<String, String> 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<Class<? extends ThingHandlerService>> getServices() {
-        return Collections.singletonList(HostActions.class);
+        return Set.of(HostActions.class);
     }
 }
index c22c9ad33336c996683a54be088f6ac6bec7968a..67b858250828a868978d852990154dcacacb1f84 100644 (file)
@@ -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> 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 (file)
index 0000000..b810f17
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<config-description:config-descriptions
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
+               https://openhab.org/schemas/config-description-1.0.0.xsd">
+
+       <config-description uri="thing-type:freeboxos:wifi-host">
+               <parameter name="refreshInterval" type="integer" min="1" unit="s">
+                       <label>Refresh Interval</label>
+                       <description>The refresh interval in seconds which is used to poll given device</description>
+                       <default>30</default>
+               </parameter>
+               <parameter name="macAddress" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
+                       <label>MAC Address</label>
+                       <description>The MAC address of the network device</description>
+               </parameter>
+               <parameter name="mDNS" type="text">
+                       <label>mDNS Name</label>
+                       <description>The mDNS name of the network device</description>
+                       <advanced>true</advanced>
+               </parameter>
+       </config-description>
+
+</config-description:config-descriptions>
index 86b59db4e61d8f9db8e0deeb30ad5c7b32d1d85a..201eb4c1304401496b1f46408068d600cec57c92 100644 (file)
@@ -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
index 581ba2c50cf7220b0c281c8c2ae61e09cd7352e8..fa69e085345251062a9d0146831a6d327a5c1ef8 100644 (file)
@@ -19,7 +19,7 @@
 
                <representation-property>macAddress</representation-property>
 
-               <config-description-ref uri="thing-type:freeboxos:host"/>
+               <config-description-ref uri="thing-type:freeboxos:wifi-host"/>
        </thing-type>
 
 </thing:thing-descriptions>