* Take care of randomized mac addresses by using mDNS name for wifi hosts.
Signed-off-by: clinique <gael@lhopital.org>
|------------------|-----------------|------------------------------------------------------------------------|----------|---------|
| 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:
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;
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() {
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()) {
/**
* The {@link HostConfiguration} is responsible for holding
* configuration informations associated to a Freebox Network Device
- * thing type
*
* @author Gaël L'hopital - Initial contribution
*/
--- /dev/null
+/**
+ * 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;
+ }
+}
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);
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
private final Map<String, ScheduledFuture<?>> jobs = new HashMap<>();
private @Nullable ServiceRegistration<?> reg;
+ protected boolean statusDrivenByBridge = true;
ApiConsumerHandler(Thing thing) {
super(thing);
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 {
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();
- }
}
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());
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());
}
}
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;
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());
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
- return Collections.singletonList(HostActions.class);
+ return Set.of(HostActions.class);
}
}
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
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);
}
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");
+ }
+ }
}
--- /dev/null
+<?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>
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
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
<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>