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.api.rest;
15 import java.time.ZonedDateTime;
16 import java.util.ArrayList;
17 import java.util.List;
18 import java.util.Optional;
20 import javax.ws.rs.core.UriBuilder;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.freeboxos.internal.api.FreeboxException;
25 import org.openhab.binding.freeboxos.internal.api.Response;
26 import org.openhab.binding.freeboxos.internal.api.rest.APManager.LanAccessPoint;
27 import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.InterfacesResponse;
29 import inet.ipaddr.IPAddress;
30 import inet.ipaddr.IPAddressString;
31 import inet.ipaddr.mac.MACAddress;
34 * The {@link LanBrowserManager} is the Java class used to handle api requests related to lan
36 * https://dev.freebox.fr/sdk/os/system/#
38 * @author Gaƫl L'hopital - Initial contribution
41 public class LanBrowserManager extends ListableRest<LanBrowserManager.Interface, InterfacesResponse> {
42 private static final IPAddress NULL_IP = new IPAddressString("0.0.0.0").getAddress();
43 private static final String PATH = "browser";
44 private static final String INTERFACES = "interfaces";
45 private static final String WOL_ACTION = "wol";
47 protected static class HostsResponse extends Response<LanHost> {
50 protected static class InterfacesResponse extends Response<Interface> {
63 public record HostName(@Nullable String name, Source source) {
66 protected static record Interface(String name, int hostCount) {
69 private static record WakeOnLineData(String mac, String password) {
77 private static record L2Ident(MACAddress id, Type type) {
80 private static record L3Connectivity(String addr, Af af, boolean active, boolean reachable,
81 ZonedDateTime lastActivity, ZonedDateTime lastTimeReachable, String model) {
89 public IPAddress getIPAddress() {
90 if (af != Af.UNKNOWN) {
91 return new IPAddressString(addr).getAddress();
97 public static record HostIntf(LanHost host, Interface intf) {
100 private enum HostType {
126 public static record LanHost(String id, @Nullable String primaryName, HostType hostType, boolean primaryNameManual,
127 L2Ident l2ident, @Nullable String vendorName, boolean persistent, boolean reachable,
128 @Nullable ZonedDateTime lastTimeReachable, boolean active, @Nullable ZonedDateTime lastActivity,
129 @Nullable ZonedDateTime firstActivity, @Nullable List<HostName> names,
130 List<L3Connectivity> l3connectivities, @Nullable LanAccessPoint accessPoint) {
132 public @Nullable LanAccessPoint accessPoint() {
136 public String vendorName() {
137 String localVendor = vendorName;
138 return localVendor == null || localVendor.isEmpty() ? "Unknown" : localVendor;
141 public Optional<String> getPrimaryName() {
142 return Optional.ofNullable(primaryName);
145 public List<HostName> getNames() {
146 List<HostName> localNames = names;
147 return localNames != null ? localNames : List.of();
150 public Optional<String> getName(Source searchedSource) {
151 return getNames().stream().filter(name -> name.source == searchedSource).findFirst().map(HostName::name);
154 public MACAddress getMac() {
155 if (Type.MAC_ADDRESS.equals(l2ident.type)) {
158 throw new IllegalArgumentException("This host does not seem to have a Mac Address. Weird.");
161 public @Nullable IPAddress getIpv4() {
162 return l3connectivities.stream().filter(L3Connectivity::reachable).map(L3Connectivity::getIPAddress)
163 .filter(ip -> !ip.equals(NULL_IP) && ip.isIPv4()).findFirst().orElse(null);
166 public @Nullable ZonedDateTime getLastSeen() {
167 ZonedDateTime localLastActivity = lastActivity;
168 if (lastTimeReachable == null && localLastActivity == null) {
171 if (lastTimeReachable == null) {
174 if (localLastActivity == null) {
175 return lastTimeReachable;
177 return localLastActivity.isAfter(lastTimeReachable) ? lastActivity : lastTimeReachable;
182 private final List<Interface> interfaces = new ArrayList<>();
184 public LanBrowserManager(FreeboxOsSession session, UriBuilder uriBuilder) throws FreeboxException {
185 super(session, LoginManager.Permission.NONE, InterfacesResponse.class, uriBuilder.path(PATH));
186 listSubPath = INTERFACES;
189 private List<LanHost> getInterfaceHosts(String lanInterface) throws FreeboxException {
190 return get(HostsResponse.class, lanInterface);
193 private @Nullable LanHost getHost(String lanInterface, String hostId) throws FreeboxException {
194 return getSingle(HostsResponse.class, lanInterface, hostId);
197 // As the list of interfaces on the box may not change, we cache the result
198 private List<Interface> getInterfaces() throws FreeboxException {
199 if (interfaces.isEmpty()) {
200 interfaces.addAll(getDevices());
205 public synchronized List<LanHost> getHosts() throws FreeboxException {
206 List<LanHost> hosts = new ArrayList<>();
208 for (Interface intf : getInterfaces()) {
209 hosts.addAll(getInterfaceHosts(intf.name()));
214 public Optional<HostIntf> getHost(MACAddress searched) throws FreeboxException {
215 for (Interface intf : getInterfaces()) {
216 LanHost host = getHost(intf.name(), "ether-" + searched.toColonDelimitedString());
218 return Optional.of(new HostIntf(host, intf));
221 return Optional.empty();
224 public Optional<LanHost> getHost(HostName identifier) throws FreeboxException {
225 List<LanHost> hosts = getHosts();
226 LanHost result = null;
227 boolean multiple = false;
228 for (LanHost host : hosts) {
229 Optional<String> sourcedName = host.getName(identifier.source);
230 if (sourcedName.isPresent() && sourcedName.get().equals(identifier.name)) {
231 // We will not return something if multiple hosts are found. This can happen in case of IP change that
232 // a previous name remains attached to a different host.
233 if (result == null) {
235 } else if (!result.getMac().equals(host.getMac())) {
236 // Multiple hosts with different macs
244 return Optional.ofNullable(result);
247 public boolean wakeOnLan(MACAddress mac, String password) throws FreeboxException {
248 Optional<HostIntf> target = getHost(mac);
249 if (target.isPresent()) {
250 post(new WakeOnLineData(mac.toColonDelimitedString(), password), GenericResponse.class, WOL_ACTION,
251 target.get().intf.name);