/bundles/org.openhab.binding.folding/ @fa2k
/bundles/org.openhab.binding.foobot/ @airboxlab @Hilbrand
/bundles/org.openhab.binding.freebox/ @lolodomo
+/bundles/org.openhab.binding.freeboxos/ @clinique
/bundles/org.openhab.binding.fronius/ @trokohl
/bundles/org.openhab.binding.fsinternetradio/ @paphko
/bundles/org.openhab.binding.ftpupload/ @paulianttila
<artifactId>org.openhab.binding.freebox</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.openhab.addons.bundles</groupId>
+ <artifactId>org.openhab.binding.freeboxos</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.fronius</artifactId>
--- /dev/null
+This content is produced and maintained by the openHAB project.\r
+\r
+* Project home: https://www.openhab.org\r
+\r
+== Declared Project Licenses\r
+\r
+This program and the accompanying materials are made available under the terms\r
+of the Eclipse Public License 2.0 which is available at\r
+https://www.eclipse.org/legal/epl-2.0/.\r
+\r
+== Source Code\r
+\r
+https://github.com/openhab/openhab-addons\r
+\r
+== Third-party Content\r
+\r
+IPAddress: Java library for handling IP addresses and subnets, both IPv4 and IPv6\r
+* License: Apache License 2.0\r
+* Project: https://github.com/seancfoley/IPAddress\r
+* Source: https://github.com/seancfoley/IPAddress\r
--- /dev/null
+# FreeboxOS Binding
+
+Free is a French telecom operator providing advanced equipments to manage your broadband access.
+
+This binding integrates the [Freebox Revolution](https://www.free.fr/freebox/freebox-revolution/) and [Freebox Delta](https://www.free.fr/freebox/freebox-delta/) to your openHAB installation.
+
+The server can be connected to Free infrastructures either by xDSL or fiber optic.
+
+This binding provides metrics about your network bridge/router and attached devices (wifi repeaters, TV boxes ...).
+It also provides home automation capabilities when appropriate dongle has been inserted in the server.
+
+IliadBox, italian version of the Freebox Pop are also compatible.
+
+## Supported Things
+
+This binding supports the following thing types:
+
+| Thing | Thing Type | Description |
+|-------------------|------------|---------------------------------------------------------------|
+| api | Bridge | Bridge to access freebox OS API hosted by the server |
+| delta | Thing | A Freebox Delta server |
+| revolution | Thing | A Freebox Revolution server |
+| player | Thing | A TV player equipment |
+| active-player | Thing | The TV player equipment with API capabilities (e.g. Devialet) |
+| landline | Thing | The phone wired to the Freebox Server |
+| host | Thing | A network device on the local network |
+| wifihost | Thing | A wifi networked device on the local network |
+| vm (*) | Thing | A virtual machine hosted on the server |
+| freeplug | Thing | A virtual machine hosted on the server |
+| repeater | Thing | A Free wifi repeater |
+| basic-shutter (*) | Thing | RTS Shutter configured in Freebox Home |
+| shutter (*) | Thing | IO Home Control shutter configured in Freebox Home |
+| kfb (*) | Thing | A keyfob associated with your alarm system |
+| alarm (*) | Thing | The Freebox Home Alarm System |
+
+(*) Restricted to servers >= Delta
+
+## Discovery
+
+The API bridge is discovered automatically through mDNS in the local network.
+After the bridge is discovered and available to openHAB, the binding will automatically discover phone, network devices available on the local network.
+Note that the discovered thing will be setup to use only HTTP API (and not HTTPS) because only an IP can be automatically determined while a domain name is required to use HTTPS with a valid certificate.
+
+## Binding configuration
+
+FreeboxOS binding has the following configuration parameters:
+
+| Name | Description | Mandatory |
+|-----------------|----------------------------------------------------|-----------|
+| timeout | The timeout for reading from the device in seconds | yes |
+
+
+## Thing Configuration
+
+### API bridge
+
+| Parameter Label | Parameter ID | Description | Required | Default |
+|--------------------------|-------------------|--------------------------------------------------------|----------|----------------------|
+| Freebox Server Address | apiDomain | The domain to use in place of hardcoded Freebox ip | No | mafreebox.freebox.fr |
+| Application Token | appToken | Token generated by the Freebox Server. | Yes | |
+| Network Device Discovery | discoverNetDevice | Enable the discovery of network device things. | No | false |
+| HTTPS Available | httpsAvailable | Tells if https has been configured on the Freebox | No | false |
+| HTTPS port | httpsPort | Port to use for remote https access to the Freebox Api | No | 15682 |
+
+If the parameter *apiDomain* is not set, the binding will use the default address used by Free to access your Freebox Server (mafreebox.freebox.fr).
+The bridge thing will initialize only if a valid application token (parameter *appToken*) is filled.
+
+### Server: Revolution or Delta
+
+The *revolution* or *delta* thing requires the following configuration parameters:
+
+| Parameter Label | Parameter ID | Description | Required | Default |
+|------------------|-----------------|--------------------------------------------------------------------------|----------|---------|
+| Refresh Interval | refreshInterval | The refresh interval (seconds) which is used to poll the Freebox Server. | No | 30 |
+
+### Player thing
+
+The *player* thing requires the following configuration parameters:
+
+| Parameter Label | Parameter ID | Description | Required | Default |
+|------------------|-----------------|----------------------------------------------------------------------------|----------|---------|
+| MAC Address | macAddress | The MAC address of the player device. | Yes | |
+| ID | id | Id of the player within Freebox Api | Yes | 1 |
+| Player port | port | | No | 24322 |
+| Password | password | AirPlay password | No | |
+| Remote Code | remoteCode | Code associated to remote control | No | |
+| Accept all MP3 | acceptAllMp3 | Accept any bitrate for MP3 audio or only bitrates greater than 64 kbps | No | true |
+| Refresh Interval | refreshInterval | The refresh interval in seconds which is used to poll the player | Yes | 30 |
+| Callback URL | callbackUrl | URL to use for playing notification sounds, e.g. 'http://192.168.0.2:8080' | No | |
+
+### Landline
+
+The *landline* thing requires the following configuration parameters:
+
+| Parameter Label | Parameter ID | Description | Required | Default |
+|------------------|-----------------|------------------------------------------------------------------------|----------|---------|
+| Refresh Interval | refreshInterval | The refresh interval in seconds which is used to poll for phone state. | No | 2 |
+
+### Network devices: Host and WifiHost
+
+The *host* and *wifihost* things 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 |
+
+### 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:
+
+| Parameter Label | Parameter ID | Description | Required | Default |
+|------------------|-----------------|------------------------------------------------------------------|----------|---------|
+| MAC Address | macAddress | The MAC address of the player device. | No | |
+| ID | id | Id of the repeater within Freebox Api | Yes | 1 |
+| Refresh Interval | refreshInterval | The refresh interval in seconds which is used to poll the player | Yes | 30 |
+
+### Basic shutter thing
+
+The *basic-shutter* thing requires the following configuration parameters:
+
+| Parameter Label | Parameter ID | Description | Required | Default |
+|------------------|-----------------|------------------------------------------------------------------|----------|---------|
+| ID | id | Id of the Home Node | Yes | 1 |
+| UP Slot ID | upSlotId | Id of the UP Slot. | Yes | 0 |
+| STOP Slot ID | stopSlotId | Id of the STOP Slot. | Yes | 1 |
+| DOWN Slot ID | downSlotId | Id of the DOWN Slot. | Yes | 2 |
+| STATE Signal ID | stateSignalId | Id of the STATE Signal. | Yes | 3 |
+
+## Authentication
+
+You will have to authorize openHAB to connect to your Freebox. Here is the process described:
+
+**Step 1** At binding startup, if no token is recorded in the Freebox Server (bridge) configuration, the binding will run a pairing request
+
+**Step 2** Run to your Freebox and approve the pairing request for openHAB FreeboxOS Binding that is displayed on the Freebox screen
+
+**Step 3** the application token is automatically recorded in the Freebox Server (bridge) configuration
+
+**Step 4** you can use the console command `freeboxos apptoken` to display the application token and use it later to set up your thing in a configuration file
+
+**Step 5** log in your Freebox admin console to allocate needed rights to FreeboxOS Binding
+
+Once initialized, the thing will generate all available channels.
+
+## Channels
+
+The following channels are supported:
+
+| Thing | Channel Type ID | Item Type | Access Mode | Description |
+|---------------|-----------------------------|---------------|-------------|--------------------------------------------------------------------------------|
+| revolution | lcd-brightness | Number | RW | Brightness level of the screen in percent |
+| revolution | lcd-orientation | Number | RW | Screen Orientation in degrees (0 or 90 or 180 or 270) |
+| revolution | lcd-forced | Switch | RW | Indicates whether the screen orientation forced |
+| server (*) | uptime | Number | R | Time since last reboot of the Freebox Server |
+| server (*) | restarted | Switch | R | Indicates whether the Freebox server has restarted during the last poll period |
+| server (*) | wifi-status | Switch | RW | Indicates whether the WiFi network is enabled |
+| server (*) | ftp-status | Switch | RW | Indicates whether the FTP server is enabled |
+| server (*) | airmedia-status | Switch | RW | Indicates whether Air Media is enabled |
+| server (*) | upnpav-status | Switch | RW | Indicates whether UPnP AV is enabled |
+| server (*) | samba-file-status | Switch | RW | Indicates whether Window File Sharing is enabled |
+| server (*) | samba-printer-status | Switch | RW | Indicates whether Window Printer Sharing is enabled |
+| server (*) | xdsl-status | String | R | Status of the xDSL line |
+| server (*) | ftth-status | Switch | R | Status of the Ftth line |
+| server (*) | line-status | String | R | Status of network line connexion |
+| server (*) | ipv4 | String | R | Public IP Address of the Freebox Server |
+| server (*) | rate-up | Number | R | Current upload rate in byte/s |
+| server (*) | rate-down | Number | R | Current download rate in byte/s |
+| server (*) | bytes-up | Number | R | Total uploaded bytes since last connection |
+| server (*) | bytes-down | Number | R | Total downloaded bytes since last connection |
+| phone | state#onhook | Switch | R | Indicates whether the phone is on hook |
+| phone | state#ringing | Switch | R | Is the phone ringing |
+| call | incoming#number | Call | R | Current incoming call number |
+| call | incoming#timestamp | DateTime | R | Current incoming call creation timestamp |
+| call | incoming#name | String | R | Current incoming caller name |
+| call | accepted#number | Call | R | Last accepted call number |
+| call | accepted#duration | Number | R | Last accepted call duration in seconds |
+| call | accepted#timestamp | DateTime | R | Last accepted call creation timestamp |
+| call | accepted#name | String | R | Last accepted caller name |
+| call | missed#number | Call | R | Last missed call number |
+| call | missed#timestamp | DateTime | R | Last missed call creation timestamp |
+| call | missed#name | String | R | Last missed caller name |
+| call | outgoing#number | Call | R | Last outgoing call number |
+| call | outgoing#duration | Number | R | Last outgoing call duration in seconds |
+| call | outgoing#timestamp | DateTime | R | Last outgoing call creation timestamp |
+| call | outgoing#name | String | R | Last outgoing called name |
+| net_device | reachable | Switch | R | Indicates whether the network device is reachable |
+| net_interface | reachable | Switch | R | Indicates whether the network interface is reachable |
+| airplay | playurl | String | W | Play an audio or video media from the given URL |
+| airplay | stop | Switch | W | Stop the media playback |
+| basic-shutter | basic-shutter#basic-shutter | RollerShutter | W | Up, stop and down commands for a RTS shutter |
+
+(*): server means *delta* or *revolution*
+
+## Actions for rules
+
+The following actions are available in rules/scripting:
+
+| Thing Type | Action Name | Parameters | Description |
+|-------------|------------------|-------------------------|------------------------------------------------------|
+| host | wol | None | Sends a wake on lan packet to the lan connected host |
+| player | reboot | None | Reboots the player device |
+| player | sendKey | key: String | Send a key (remote emulation) to the player |
+| player | sendLongKey | key: String | Sends the key emulating a longpress on the button |
+| player | sendMultipleKeys | keys: String | Sends multiple keys to the player, comma separated |
+| player | sendKeyRepeat | key: String, count: int | Sends the key multiple times |
+| server | reboot | None | Reboots the Freebox Server |
+| freeplug | reset | None | Resets the Freeplug |
+| call | reset | None | Clears the call log queue |
+| repeater | reboot | None | Reboots the Repeater |
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.openhab.addons.bundles</groupId>
+ <artifactId>org.openhab.addons.reactor.bundles</artifactId>
+ <version>4.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>org.openhab.binding.freeboxos</artifactId>
+
+ <name>openHAB Add-ons :: Bundles :: FreeboxOS Binding</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>2.10.1</version>
+ </dependency>
+ <dependency>
+ <groupId>com.github.seancfoley</groupId>
+ <artifactId>ipaddress</artifactId>
+ <version>5.4.0</version>
+ </dependency>
+ </dependencies>
+
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.freeboxos-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
+ <repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
+
+ <feature name="openhab-binding-freeboxos" description="Freebox OS Binding" version="${project.version}">
+ <feature>openhab-runtime-base</feature>
+ <feature>openhab-transport-mdns</feature>
+ <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.freeboxos/${project.version}</bundle>
+ </feature>
+</features>
--- /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;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Category;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.OpenClosedType;
+import org.openhab.core.library.types.UpDownType;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.types.Command;
+
+/**
+ * The {@link FreeboxBinding} class defines common constants, which are used across the binding.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class FreeboxOsBindingConstants {
+
+ public static final String BINDING_ID = "freeboxos";
+
+ // List of all Bridge Type UIDs
+ public static final ThingTypeUID BRIDGE_TYPE_API = new ThingTypeUID(BINDING_ID, "api");
+
+ // Thing Types ID strings
+ private static final String THING_DECT = "dect";
+ private static final String THING_FXS = "fxs";
+ private static final String THING_REVOLUTION = "revolution";
+ private static final String THING_DELTA = "delta";
+ private static final String THING_WIFI_HOST = "wifihost";
+ private static final String THING_ACTIVE_PLAYER = "active-player";
+
+ public static final String THING_FREEPLUG = "freeplug";
+ public static final String THING_VM = "vm";
+ public static final String THING_CALL = "call";
+ public static final String THING_HOST = "host";
+ public static final String THING_PLAYER = "player";
+ public static final String THING_REPEATER = "repeater";
+
+ // List of all Thing Type UIDs
+ public static final ThingTypeUID THING_TYPE_REVOLUTION = new ThingTypeUID(BINDING_ID, THING_REVOLUTION);
+ public static final ThingTypeUID THING_TYPE_DELTA = new ThingTypeUID(BINDING_ID, THING_DELTA);
+ public static final ThingTypeUID THING_TYPE_FXS = new ThingTypeUID(BINDING_ID, THING_FXS);
+ public static final ThingTypeUID THING_TYPE_DECT = new ThingTypeUID(BINDING_ID, THING_DECT);
+ public static final ThingTypeUID THING_TYPE_CALL = new ThingTypeUID(BINDING_ID, THING_CALL);
+ public static final ThingTypeUID THING_TYPE_FREEPLUG = new ThingTypeUID(BINDING_ID, THING_FREEPLUG);
+ public static final ThingTypeUID THING_TYPE_HOST = new ThingTypeUID(BINDING_ID, THING_HOST);
+ public static final ThingTypeUID THING_TYPE_WIFI_HOST = new ThingTypeUID(BINDING_ID, THING_WIFI_HOST);
+ public static final ThingTypeUID THING_TYPE_PLAYER = new ThingTypeUID(BINDING_ID, THING_PLAYER);
+ public static final ThingTypeUID THING_TYPE_ACTIVE_PLAYER = new ThingTypeUID(BINDING_ID, THING_ACTIVE_PLAYER);
+ public static final ThingTypeUID THING_TYPE_VM = new ThingTypeUID(BINDING_ID, THING_VM);
+ public static final ThingTypeUID THING_TYPE_REPEATER = new ThingTypeUID(BINDING_ID, THING_REPEATER);
+
+ // All supported Thing types
+ public static final Set<ThingTypeUID> BRIDGE_TYPE_UIDS = Set.of(BRIDGE_TYPE_API);
+ public static final Set<ThingTypeUID> THINGS_TYPES_UIDS = Set.of(THING_TYPE_FXS, THING_TYPE_DECT, THING_TYPE_CALL,
+ THING_TYPE_HOST, THING_TYPE_VM, THING_TYPE_PLAYER, THING_TYPE_ACTIVE_PLAYER, THING_TYPE_DELTA,
+ THING_TYPE_REVOLUTION, THING_TYPE_REPEATER, THING_TYPE_WIFI_HOST, THING_TYPE_FREEPLUG);
+ public static final Set<ThingTypeUID> HOME_TYPES_UIDS = Set.of(Category.BASIC_SHUTTER.getThingTypeUID(),
+ Category.SHUTTER.getThingTypeUID(), Category.KFB.getThingTypeUID(), Category.CAMERA.getThingTypeUID(),
+ Category.ALARM.getThingTypeUID());
+
+ protected static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
+ .of(BRIDGE_TYPE_UIDS, THINGS_TYPES_UIDS, HOME_TYPES_UIDS).flatMap(Set::stream).collect(Collectors.toSet());
+
+ // Thing properties
+ // public static final String LAST_CALL_TIMESTAMP = "lastCallTimestamp";
+ public static final String ROLE = "role";
+ public static final String NET_ID = "netId";
+ public static final String ETHERNET_SPEED = "ethernetSpeed";
+ public static final String LOCAL = "local";
+ public static final String FULL_DUPLEX = "fullDuplex";
+
+ // List of all Group Channel ids
+ public static final String GROUP_SENSORS = "sensors";
+ public static final String GROUP_FANS = "fans";
+ public static final String CONNECTION_STATUS = "connection-status";
+ public static final String SYS_INFO = "sysinfo";
+ public static final String ACTIONS = "actions";
+ public static final String FILE_SHARING = "file-sharing";
+ public static final String CONNECTIVITY = "connectivity";
+ public static final String DISPLAY = "display";
+ public static final String VM_STATUS = "vmstatus";
+ public static final String GROUP_WIFI = "wifi";
+ public static final String REPEATER_MISC = "repeater-misc";
+
+ // List of all Channel ids
+ public static final String RSSI = "rssi";
+ public static final String SSID = "ssid";
+ public static final String WIFI_QUALITY = "wifi-quality";
+ public static final String WIFI_HOST = "wifi-host";
+ public static final String UPTIME = "uptime";
+ public static final String BOX_EVENT = "box-event";
+ public static final String LCD_BRIGHTNESS = "lcd-brightness";
+ public static final String LCD_ORIENTATION = "lcd-orientation";
+ public static final String LCD_FORCED = "lcd-forced";
+ public static final String WIFI_STATUS = "wifi-status";
+ public static final String IP_ADDRESS = "ip-address";
+ public static final String IPV6_ADDRESS = "ipv6-address";
+ public static final String LINE_STATUS = "line-status";
+ public static final String LINE_TYPE = "line-type";
+ public static final String LINE_MEDIA = "line-media";
+ public static final String PLAYER_STATUS = "player-status";
+ public static final String PACKAGE = "package";
+ public static final String RATE = "rate";
+ public static final String BYTES_UP = "bytes-up";
+ public static final String BYTES_DOWN = "bytes-down";
+ public static final String BW = "bandwidth";
+ public static final String PCT_BW = "bandwidth-usage";
+ public static final String ONHOOK = "onhook";
+ public static final String RINGING = "ringing";
+ public static final String HARDWARE_STATUS = "hardware-status";
+ public static final String TELEPHONY_SERVICE = "telephony-service";
+ public static final String GAIN_RX = "gain-rx";
+ public static final String GAIN_TX = "gain-tx";
+ public static final String FTP_STATUS = "ftp-status";
+ public static final String SAMBA_FILE_STATUS = "samba-file-status";
+ public static final String SAMBA_PRINTER_STATUS = "samba-printer-status";
+ public static final String AFP_FILE_STATUS = "afp-file-status";
+ public static final String REACHABLE = "reachable";
+ public static final String LAST_SEEN = "last-seen";
+ public static final String ALTERNATE_RING = "lcd-forced";
+ public static final String DECT_ACTIVE = "dect-active";
+ public static final String UPNPAV_STATUS = "upnpav-status";
+
+ // Call channels for groups Accepted, Missed and Outgoing
+ public static final String NUMBER = "number";
+ public static final String DURATION = "duration";
+ public static final String TIMESTAMP = "timestamp";
+ public static final String NAME = "name";
+
+ // Freebox player channels
+ public static final String AIRMEDIA_STATUS = "airmedia-status";
+ public static final String KEY_CODE = "key-code";
+
+ // Virtual machine channels
+ public static final String STATUS = "status";
+
+ // Repeater channels
+ public static final String LED = "led";
+ public static final String HOST_COUNT = "host-count";
+
+ // Home channels
+ public static final String KEYFOB_ENABLE = "enable";
+ public static final String NODE_BATTERY = "battery";
+ public static final String SHUTTER_POSITION = "position-set";
+ public static final String SHUTTER_STOP = "stop";
+ public static final String BASIC_SHUTTER_STATE = "state";
+ public static final String BASIC_SHUTTER_UP = "up";
+ public static final String BASIC_SHUTTER_DOWN = "down";
+ // public static final String BASIC_SHUTTER_CMD = "basic-shutter";
+ public static final String ALARM_PIN = "pin";
+ public static final String ALARM_SOUND = "sound";
+ public static final String ALARM_VOLUME = "volume";
+ public static final String ALARM_TIMEOUT1 = "timeout1";
+ public static final String ALARM_TIMEOUT2 = "timeout2";
+ public static final String ALARM_TIMEOUT3 = "timeout3";
+
+ public static final Set<Command> TRUE_COMMANDS = Set.of(OnOffType.ON, UpDownType.UP, OpenClosedType.OPEN);
+ public static final Set<Class<?>> ON_OFF_CLASSES = Set.of(OnOffType.class, UpDownType.class, OpenClosedType.class);
+}
--- /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;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.freeboxos.internal.api.ApiHandler;
+import org.openhab.binding.freeboxos.internal.api.rest.FreeboxOsSession;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Category;
+import org.openhab.binding.freeboxos.internal.handler.ActivePlayerHandler;
+import org.openhab.binding.freeboxos.internal.handler.AlarmHandler;
+import org.openhab.binding.freeboxos.internal.handler.BasicShutterHandler;
+import org.openhab.binding.freeboxos.internal.handler.CallHandler;
+import org.openhab.binding.freeboxos.internal.handler.CameraHandler;
+import org.openhab.binding.freeboxos.internal.handler.DectHandler;
+import org.openhab.binding.freeboxos.internal.handler.FreeboxOsHandler;
+import org.openhab.binding.freeboxos.internal.handler.FreeplugHandler;
+import org.openhab.binding.freeboxos.internal.handler.FxsHandler;
+import org.openhab.binding.freeboxos.internal.handler.HostHandler;
+import org.openhab.binding.freeboxos.internal.handler.KeyfobHandler;
+import org.openhab.binding.freeboxos.internal.handler.PlayerHandler;
+import org.openhab.binding.freeboxos.internal.handler.RepeaterHandler;
+import org.openhab.binding.freeboxos.internal.handler.RevolutionHandler;
+import org.openhab.binding.freeboxos.internal.handler.ServerHandler;
+import org.openhab.binding.freeboxos.internal.handler.ShutterHandler;
+import org.openhab.binding.freeboxos.internal.handler.VmHandler;
+import org.openhab.binding.freeboxos.internal.handler.WifiStationHandler;
+import org.openhab.core.audio.AudioHTTPServer;
+import org.openhab.core.i18n.TimeZoneProvider;
+import org.openhab.core.io.net.http.HttpClientFactory;
+import org.openhab.core.net.HttpServiceUtil;
+import org.openhab.core.net.NetworkAddressService;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandlerFactory;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link FreeboxOsHandlerFactory} is responsible for creating things and thing handlers.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+@Component(service = ThingHandlerFactory.class, configurationPid = "binding.freeboxos")
+public class FreeboxOsHandlerFactory extends BaseThingHandlerFactory {
+ private static final String TIMEOUT = "timeout";
+ private static final String CALLBACK_URL = "callBackUrl";
+
+ private final Logger logger = LoggerFactory.getLogger(FreeboxOsHandlerFactory.class);
+
+ private final NetworkAddressService networkAddressService;
+ private final AudioHTTPServer audioHTTPServer;
+ private final HttpClient httpClient;
+ private final ApiHandler apiHandler;
+ private String callbackURL = "";
+
+ @Activate
+ public FreeboxOsHandlerFactory(final @Reference AudioHTTPServer audioHTTPServer,
+ final @Reference NetworkAddressService networkAddressService,
+ final @Reference HttpClientFactory httpClientFactory, final @Reference TimeZoneProvider timeZoneProvider,
+ ComponentContext componentContext, Map<String, Object> config) {
+ super.activate(componentContext);
+
+ this.audioHTTPServer = audioHTTPServer;
+ this.httpClient = httpClientFactory.getCommonHttpClient();
+ this.networkAddressService = networkAddressService;
+ this.apiHandler = new ApiHandler(httpClient, timeZoneProvider);
+
+ configChanged(config);
+ }
+
+ @Modified
+ public void configChanged(Map<String, Object> config) {
+ String timeout = (String) config.getOrDefault(TIMEOUT, "8");
+ apiHandler.setTimeout(TimeUnit.SECONDS.toMillis(Long.parseLong(timeout)));
+
+ callbackURL = (String) config.getOrDefault(CALLBACK_URL, "");
+ int port = HttpServiceUtil.getHttpServicePort(bundleContext);
+ if (callbackURL.isEmpty() && port != -1) {
+ String openHabIp = Objects.requireNonNull(networkAddressService.getPrimaryIpv4HostAddress());
+ // we do not use SSL as it can cause certificate validation issues.
+ callbackURL = "http://%s:%d".formatted(openHabIp, port);
+ }
+ if (callbackURL.isEmpty()) {
+ logger.warn("Unable to build a correct call back URL to stream media contents");
+ return;
+ }
+ }
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+ }
+
+ @Override
+ protected @Nullable ThingHandler createHandler(Thing thing) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+ if (BRIDGE_TYPE_API.equals(thingTypeUID)) {
+ return new FreeboxOsHandler((Bridge) thing, new FreeboxOsSession(apiHandler), callbackURL, bundleContext,
+ audioHTTPServer);
+ } else if (THING_TYPE_FREEPLUG.equals(thingTypeUID)) {
+ return new FreeplugHandler(thing);
+ } else if (THING_TYPE_FXS.equals(thingTypeUID)) {
+ return new FxsHandler(thing);
+ } else if (THING_TYPE_DECT.equals(thingTypeUID)) {
+ return new DectHandler(thing);
+ } else if (THING_TYPE_CALL.equals(thingTypeUID)) {
+ return new CallHandler(thing);
+ } else if (THING_TYPE_REVOLUTION.equals(thingTypeUID)) {
+ return new RevolutionHandler(thing);
+ } else if (THING_TYPE_DELTA.equals(thingTypeUID)) {
+ return new ServerHandler(thing);
+ } else if (THING_TYPE_HOST.equals(thingTypeUID)) {
+ return new HostHandler(thing);
+ } else if (THING_TYPE_WIFI_HOST.equals(thingTypeUID)) {
+ return new WifiStationHandler(thing);
+ } else if (THING_TYPE_REPEATER.equals(thingTypeUID)) {
+ return new RepeaterHandler(thing);
+ } else if (THING_TYPE_VM.equals(thingTypeUID)) {
+ return new VmHandler(thing);
+ } else if (THING_TYPE_ACTIVE_PLAYER.equals(thingTypeUID)) {
+ return new ActivePlayerHandler(thing);
+ } else if (THING_TYPE_PLAYER.equals(thingTypeUID)) {
+ return new PlayerHandler(thing);
+ } else if (Category.BASIC_SHUTTER.getThingTypeUID().equals(thingTypeUID)) {
+ return new BasicShutterHandler(thing);
+ } else if (Category.SHUTTER.getThingTypeUID().equals(thingTypeUID)) {
+ return new ShutterHandler(thing);
+ } else if (Category.ALARM.getThingTypeUID().equals(thingTypeUID)) {
+ return new AlarmHandler(thing);
+ } else if (Category.KFB.getThingTypeUID().equals(thingTypeUID)) {
+ return new KeyfobHandler(thing);
+ } else if (Category.CAMERA.getThingTypeUID().equals(thingTypeUID)) {
+ return new CameraHandler(thing);
+ }
+
+ return null;
+ }
+}
--- /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.action;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.handler.ActivePlayerHandler;
+import org.openhab.binding.freeboxos.internal.handler.PlayerHandler;
+import org.openhab.core.automation.annotation.RuleAction;
+import org.openhab.core.thing.binding.ThingActions;
+import org.openhab.core.thing.binding.ThingActionsScope;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {ActivePlayerActions} class is responsible to call corresponding actions on Freebox Player with API
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@ThingActionsScope(name = "freeboxos")
+@NonNullByDefault
+public class ActivePlayerActions extends PlayerActions {
+ private final Logger logger = LoggerFactory.getLogger(ActivePlayerActions.class);
+
+ @RuleAction(label = "reboot freebox player", description = "Reboots the Freebox Player")
+ public void reboot() {
+ logger.debug("Player reboot called");
+ PlayerHandler localHandler = this.handler;
+ if (localHandler instanceof ActivePlayerHandler apHandler) {
+ apHandler.reboot();
+ } else {
+ logger.warn("Freebox Player Action service ThingHandler is null");
+ }
+ }
+
+ public static void reboot(ThingActions actions) {
+ ((ActivePlayerActions) actions).reboot();
+ }
+}
--- /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.action;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.handler.CallHandler;
+import org.openhab.core.automation.annotation.RuleAction;
+import org.openhab.core.thing.binding.ThingActions;
+import org.openhab.core.thing.binding.ThingActionsScope;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {FreeplugActions} class is responsible to call corresponding actions on Freeplugs
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@ThingActionsScope(name = "freeboxos")
+@NonNullByDefault
+public class CallActions implements ThingActions {
+ private final Logger logger = LoggerFactory.getLogger(CallActions.class);
+ private @Nullable CallHandler handler;
+
+ @Override
+ public void setThingHandler(@Nullable ThingHandler handler) {
+ if (handler instanceof CallHandler callHandler) {
+ this.handler = callHandler;
+ }
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return handler;
+ }
+
+ @RuleAction(label = "clear call queue", description = "Delete all call logged in the queue")
+ public void reset() {
+ logger.debug("Call log clear called");
+ CallHandler localHandler = handler;
+ if (localHandler != null) {
+ localHandler.emptyQueue();
+ } else {
+ logger.warn("Call Action service ThingHandler is null");
+ }
+ }
+}
--- /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.action;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.handler.FreeplugHandler;
+import org.openhab.core.automation.annotation.RuleAction;
+import org.openhab.core.thing.binding.ThingActions;
+import org.openhab.core.thing.binding.ThingActionsScope;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {FreeplugActions} class is responsible to call corresponding actions on Freeplugs
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@ThingActionsScope(name = "freeboxos")
+@NonNullByDefault
+public class FreeplugActions implements ThingActions {
+ private final Logger logger = LoggerFactory.getLogger(FreeplugActions.class);
+ private @Nullable FreeplugHandler handler;
+
+ @Override
+ public void setThingHandler(@Nullable ThingHandler handler) {
+ if (handler instanceof FreeplugHandler plugHandler) {
+ this.handler = plugHandler;
+ }
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return this.handler;
+ }
+
+ @RuleAction(label = "reset freeplug", description = "Resets the Freeplug")
+ public void reset() {
+ logger.debug("Freeplug reset requested");
+ FreeplugHandler plugHandler = this.handler;
+ if (plugHandler != null) {
+ plugHandler.reset();
+ } else {
+ logger.warn("Freeplug Action service ThingHandler is null");
+ }
+ }
+}
--- /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.action;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.handler.HostHandler;
+import org.openhab.core.automation.annotation.RuleAction;
+import org.openhab.core.thing.binding.ThingActions;
+import org.openhab.core.thing.binding.ThingActionsScope;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {HostActions} class is responsible to call corresponding actions on a given lan host
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@ThingActionsScope(name = "freeboxos")
+@NonNullByDefault
+public class HostActions implements ThingActions {
+ private final Logger logger = LoggerFactory.getLogger(HostActions.class);
+ private @Nullable HostHandler handler;
+
+ @Override
+ public void setThingHandler(@Nullable ThingHandler handler) {
+ if (handler instanceof HostHandler hostHandler) {
+ this.handler = hostHandler;
+ }
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return this.handler;
+ }
+
+ @RuleAction(label = "wol host", description = "Awakes a lan host")
+ public void wol() {
+ logger.debug("Host WOL called");
+ HostHandler hostHandler = this.handler;
+ if (hostHandler != null) {
+ hostHandler.wol();
+ } else {
+ logger.warn("LanHost Action service ThingHandler is null");
+ }
+ }
+}
--- /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.action;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.handler.PlayerHandler;
+import org.openhab.core.automation.annotation.ActionInput;
+import org.openhab.core.automation.annotation.RuleAction;
+import org.openhab.core.thing.binding.ThingActions;
+import org.openhab.core.thing.binding.ThingActionsScope;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {PlayerActions} class is responsible to call corresponding actions on Freebox Player
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@ThingActionsScope(name = "freeboxos")
+@NonNullByDefault
+public class PlayerActions implements ThingActions {
+ private final Logger logger = LoggerFactory.getLogger(PlayerActions.class);
+ protected @Nullable PlayerHandler handler;
+
+ @Override
+ public void setThingHandler(@Nullable ThingHandler handler) {
+ if (handler instanceof PlayerHandler playerHandler) {
+ this.handler = playerHandler;
+ }
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return this.handler;
+ }
+
+ @RuleAction(label = "send a key to player", description = "Sends a given key to the player")
+ public void sendKey(@ActionInput(name = "key") String key) {
+ logger.debug("Sending key {} to player", key);
+ PlayerHandler playerHandler = this.handler;
+ if (playerHandler != null) {
+ playerHandler.sendKey(key, false, 1);
+ } else {
+ logger.warn("Freebox Player Action service ThingHandler is null");
+ }
+ }
+
+ @RuleAction(label = "send a long key to player", description = "Sends a given key to the player and keep it pressed")
+ public void sendLongKey(@ActionInput(name = "key") String key) {
+ logger.debug("Sending long press key {} to player", key);
+ PlayerHandler playerHandler = this.handler;
+ if (playerHandler != null) {
+ playerHandler.sendKey(key, true, 1);
+ } else {
+ logger.warn("Freebox Player Action service ThingHandler is null");
+ }
+ }
+
+ @RuleAction(label = "send multiple keys to player", description = "Sends multiple keys to the player, comma separated")
+ public void sendMultipleKeys(@ActionInput(name = "keys") String keys) {
+ logger.debug("Sending keys {} to player", keys);
+ PlayerHandler playerHandler = this.handler;
+ if (playerHandler != null) {
+ playerHandler.sendMultipleKeys(keys);
+ } else {
+ logger.warn("Freebox Player Action service ThingHandler is null");
+ }
+ }
+
+ @RuleAction(label = "send repeating key to player", description = "Sends a given key multiple times to the player")
+ public void sendKeyRepeat(@ActionInput(name = "key") String key, @ActionInput(name = "count") int count) {
+ logger.debug("Sending key {} to player {} times", key, count);
+ PlayerHandler playerHandler = this.handler;
+ if (playerHandler != null) {
+ playerHandler.sendKey(key, false, count);
+ } else {
+ logger.warn("Freebox Player Action service ThingHandler is null");
+ }
+ }
+}
--- /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.action;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.handler.RepeaterHandler;
+import org.openhab.core.automation.annotation.RuleAction;
+import org.openhab.core.thing.binding.ThingActions;
+import org.openhab.core.thing.binding.ThingActionsScope;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {RepeaterActions} class is responsible to call corresponding actions on Freebox Repeater
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@ThingActionsScope(name = "freeboxos")
+@NonNullByDefault
+public class RepeaterActions implements ThingActions {
+ private final Logger logger = LoggerFactory.getLogger(RepeaterActions.class);
+ private @Nullable RepeaterHandler handler;
+
+ @Override
+ public void setThingHandler(@Nullable ThingHandler handler) {
+ if (handler instanceof RepeaterHandler repeaterHandler) {
+ this.handler = repeaterHandler;
+ }
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return handler;
+ }
+
+ @RuleAction(label = "reboot free repeater", description = "Reboots the Free Repeater")
+ public void reboot() {
+ logger.debug("Repeater reboot called");
+ RepeaterHandler localHandler = this.handler;
+ if (localHandler != null) {
+ localHandler.reboot();
+ } else {
+ logger.warn("Repeater Action service ThingHandler is null");
+ }
+ }
+}
--- /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.action;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.handler.ServerHandler;
+import org.openhab.core.automation.annotation.RuleAction;
+import org.openhab.core.thing.binding.ThingActions;
+import org.openhab.core.thing.binding.ThingActionsScope;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {ServerActions} class is responsible to call corresponding actions on Freebox Server
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@ThingActionsScope(name = "freeboxos")
+@NonNullByDefault
+public class ServerActions implements ThingActions {
+ private final Logger logger = LoggerFactory.getLogger(ServerActions.class);
+ private @Nullable ServerHandler handler;
+
+ @Override
+ public void setThingHandler(@Nullable ThingHandler handler) {
+ if (handler instanceof ServerHandler serverHandler) {
+ this.handler = serverHandler;
+ }
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return this.handler;
+ }
+
+ @RuleAction(label = "reboot freebox server", description = "Reboots the Freebox Server")
+ public void reboot() {
+ logger.debug("Server reboot called");
+ ServerHandler serverHandler = this.handler;
+ if (serverHandler != null) {
+ serverHandler.reboot();
+ } else {
+ logger.warn("Freebox Action service ThingHandler is null");
+ }
+ }
+}
--- /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.api;
+
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpStatus.Code;
+import org.openhab.binding.freeboxos.internal.api.deserialization.ForegroundAppDeserializer;
+import org.openhab.binding.freeboxos.internal.api.deserialization.ListDeserializer;
+import org.openhab.binding.freeboxos.internal.api.deserialization.StrictEnumTypeAdapterFactory;
+import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.ForegroundApp;
+import org.openhab.core.i18n.TimeZoneProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializer;
+
+import inet.ipaddr.IPAddress;
+import inet.ipaddr.IPAddressString;
+import inet.ipaddr.MACAddressString;
+import inet.ipaddr.mac.MACAddress;
+
+/**
+ * The {@link ApiHandler} is responsible for sending requests toward a given url and transform the answer in appropriate
+ * DTO.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class ApiHandler {
+ public static final String AUTH_HEADER = "X-Fbx-App-Auth";
+ private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
+ private static final String CONTENT_TYPE = "application/json; charset=" + DEFAULT_CHARSET.name();
+
+ private final Logger logger = LoggerFactory.getLogger(ApiHandler.class);
+ private final HttpClient httpClient;
+ private final Gson gson;
+
+ private long timeoutInMs = TimeUnit.SECONDS.toMillis(8);
+
+ public ApiHandler(HttpClient httpClient, TimeZoneProvider timeZoneProvider) {
+ this.httpClient = httpClient;
+ this.gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
+ .registerTypeAdapter(ZonedDateTime.class,
+ (JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> {
+ long timestamp = json.getAsJsonPrimitive().getAsLong();
+ Instant i = Instant.ofEpochSecond(timestamp);
+ return ZonedDateTime.ofInstant(i, timeZoneProvider.getTimeZone());
+ })
+ .registerTypeAdapter(MACAddress.class,
+ (JsonDeserializer<MACAddress>) (json, type,
+ jsonDeserializationContext) -> new MACAddressString(json.getAsString()).getAddress())
+ .registerTypeAdapter(IPAddress.class,
+ (JsonDeserializer<IPAddress>) (json, type,
+ jsonDeserializationContext) -> new IPAddressString(json.getAsString()).getAddress())
+ .registerTypeAdapter(ForegroundApp.class, new ForegroundAppDeserializer())
+ .registerTypeAdapter(List.class, new ListDeserializer()).serializeNulls()
+ .registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory()).create();
+ }
+
+ public synchronized <T> T executeUri(URI uri, HttpMethod method, Class<T> clazz, @Nullable String sessionToken,
+ @Nullable Object payload) throws FreeboxException, InterruptedException {
+ logger.debug("executeUrl {}: {} ", method, uri);
+
+ Request request = httpClient.newRequest(uri).method(method).timeout(timeoutInMs, TimeUnit.MILLISECONDS)
+ .header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE);
+
+ if (sessionToken != null) {
+ request.header(AUTH_HEADER, sessionToken);
+ }
+
+ if (payload != null) {
+ request.content(new StringContentProvider(serialize(payload), DEFAULT_CHARSET), null);
+ }
+
+ try {
+ ContentResponse response = request.send();
+
+ Code statusCode = HttpStatus.getCode(response.getStatus());
+
+ if (statusCode != Code.OK && statusCode != Code.FORBIDDEN) {
+ throw new FreeboxException(statusCode.getMessage());
+ }
+
+ String content = new String(response.getContent(), DEFAULT_CHARSET);
+ T result = deserialize(clazz, content);
+ logger.trace("executeUrl {} - {} returned {}", method, uri, content);
+
+ if (statusCode == Code.OK) {
+ return result;
+ } else if (statusCode == Code.FORBIDDEN) {
+ logger.debug("Fobidden, serviceReponse was {}, ", content);
+ if (result instanceof Response<?> errorResponse) {
+ throw new FreeboxException(errorResponse.getErrorCode(), errorResponse.getMsg());
+ }
+ }
+
+ throw new FreeboxException("Error '%s' requesting: %s", statusCode.getMessage(), uri.toString());
+ } catch (TimeoutException | ExecutionException e) {
+ throw new FreeboxException(e, "Exception while calling %s", request.getURI());
+ }
+ }
+
+ public <T> T deserialize(Class<T> clazz, String json) {
+ @Nullable
+ T result = gson.fromJson(json, clazz);
+ if (result != null) {
+ return result;
+ }
+ throw new IllegalArgumentException("Null result deserializing '%s', please file a bug report.".formatted(json));
+ }
+
+ public String serialize(Object payload) {
+ return gson.toJson(payload);
+ }
+
+ public HttpClient getHttpClient() {
+ return httpClient;
+ }
+
+ public void setTimeout(long millis) {
+ timeoutInMs = millis;
+ logger.debug("Timeout set to {} ms", timeoutInMs);
+ }
+}
--- /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.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.Response.ErrorCode;
+
+/**
+ * Exception for errors when using the Freebox API
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class FreeboxException extends Exception {
+ private static final long serialVersionUID = 9197365222439228186L;
+ private ErrorCode errorCode = ErrorCode.NONE;
+
+ public FreeboxException(String format, Object... args) {
+ super(String.format(format, args));
+ }
+
+ public FreeboxException(Exception cause, String format, Object... args) {
+ super(String.format(format, args), cause);
+ }
+
+ public FreeboxException(ErrorCode errorCode, String message) {
+ this(message);
+ this.errorCode = errorCode;
+ }
+
+ public ErrorCode getErrorCode() {
+ return errorCode;
+ }
+}
--- /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.api;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpStatus.Code;
+import org.openhab.core.i18n.TranslationProvider;
+import org.openhab.core.io.net.http.HttpClientFactory;
+import org.openhab.core.ui.icon.AbstractResourceIconProvider;
+import org.openhab.core.ui.icon.IconProvider;
+import org.openhab.core.ui.icon.IconSet;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@FreeboxOsIconProvider} delivers icons provided by FreeboxOS
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+@Component(immediate = true, service = { IconProvider.class })
+public class FreeboxOsIconProvider extends AbstractResourceIconProvider {
+
+ private final Logger logger = LoggerFactory.getLogger(FreeboxOsIconProvider.class);
+
+ private final HttpClient httpClient;
+ private final UriBuilder uriBuilder;
+
+ @Activate
+ public FreeboxOsIconProvider(final @Reference TranslationProvider i18nProvider,
+ final @Reference HttpClientFactory httpClientFactory) {
+ super(i18nProvider);
+ this.httpClient = httpClientFactory.getCommonHttpClient();
+ this.uriBuilder = UriBuilder.fromPath("/").scheme("http").host(FreeboxTlsCertificateProvider.DEFAULT_NAME)
+ .path("resources/images/home/pictos");
+ }
+
+ @Override
+ public Set<IconSet> getIconSets(@Nullable Locale locale) {
+ return Set.of();
+ }
+
+ @Override
+ protected Integer getPriority() {
+ return 4;
+ }
+
+ @Override
+ protected @Nullable InputStream getResource(String iconSetId, String resourceName) {
+ URI uri = uriBuilder.clone().path(resourceName).build();
+ Request request = httpClient.newRequest(uri).method(HttpMethod.GET);
+
+ try {
+ ContentResponse response = request.send();
+ if (HttpStatus.getCode(response.getStatus()) == Code.OK) {
+ return new ByteArrayInputStream(response.getContent());
+ }
+ } catch (InterruptedException | TimeoutException | ExecutionException e) {
+ logger.warn("Error getting icon {}: {}", resourceName, e.getMessage());
+ }
+ return null;
+ }
+
+ @Override
+ protected boolean hasResource(String iconSetId, String resourceName) {
+ return resourceName.contains(".png") && getResource(iconSetId, resourceName) != null;
+ }
+}
--- /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.api;
+
+import java.net.URL;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.io.net.http.TlsCertificateProvider;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * Provides a CertificateManager for the Freebox SSL certificate
+ *
+ * @author Gaël L'hopital - Initial Contribution
+ */
+@Component
+@NonNullByDefault
+public class FreeboxTlsCertificateProvider implements TlsCertificateProvider {
+
+ private static final String CERTIFICATE_NAME = "freeboxECCRootCA.crt";
+
+ public static final String DEFAULT_NAME = "mafreebox.freebox.fr";
+
+ @Override
+ public String getHostName() {
+ return DEFAULT_NAME;
+ }
+
+ @Override
+ public URL getCertificate() {
+ URL resource = Thread.currentThread().getContextClassLoader().getResource(CERTIFICATE_NAME);
+ if (resource != null) {
+ return resource;
+ }
+ throw new IllegalStateException("Certificate '%s' not found or not accessible".formatted(CERTIFICATE_NAME));
+ }
+}
--- /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.api;
+
+import java.net.URL;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.io.net.http.TlsCertificateProvider;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * Provides a CertificateManager for the IliadBox SSL certificate
+ *
+ * @author Gaël L'hopital - Initial Contribution
+ */
+@Component
+@NonNullByDefault
+public class IliadboxTlsCertificateProvider implements TlsCertificateProvider {
+
+ private static final String CERTIFICATE_NAME = "iliadboxECCRootCA.crt";
+
+ public static final String DEFAULT_NAME = "myiliadbox.iliad.it";
+
+ @Override
+ public String getHostName() {
+ return DEFAULT_NAME;
+ }
+
+ @Override
+ public URL getCertificate() {
+ URL resource = Thread.currentThread().getContextClassLoader().getResource(CERTIFICATE_NAME);
+ if (resource != null) {
+ return resource;
+ }
+ throw new IllegalStateException("Certificate '%s' not found or not accessible".formatted(CERTIFICATE_NAME));
+ }
+}
--- /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.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.rest.LoginManager;
+
+/**
+ * Exception for errors when Session require missing permission
+ *
+ * @author ben12 - Initial contribution
+ */
+@NonNullByDefault
+public class PermissionException extends FreeboxException {
+ private static final long serialVersionUID = 3965810786699311126L;
+
+ private final LoginManager.Permission permission;
+
+ public PermissionException(LoginManager.Permission permission, String format, Object... args) {
+ super(format, args);
+ this.permission = permission;
+ }
+
+ public LoginManager.Permission getPermission() {
+ return permission;
+ }
+}
--- /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.api;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.rest.LoginManager;
+
+/**
+ * Defines an API result that returns a single object
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class Response<ResultType> {
+ public static enum ErrorCode {
+ AUTH_REQUIRED,
+ BAD_LOGIN,
+ TOO_SHORT,
+ IN_DICTIONNARY,
+ BAD_XKCD,
+ NOT_ENOUGH_DIFFERENT_CHARS,
+ INVALID_TOKEN,
+ PENDING_TOKEN,
+ INSUFFICIENT_RIGHTS,
+ DENIED_FROM_EXTERNAL_IP,
+ INVALID_REQUEST,
+ RATELIMITED,
+ NEW_APPS_DENIED,
+ APPS_AUTHORIZATION_DENIED,
+ APPS_AUTHORIZATION_TIMEOUT,
+ PASSWORD_RESET_DENIED,
+ APPS_DENIED,
+ INTERNAL_ERROR,
+ SERVICE_DOWN,
+ DISK_FULL,
+ OP_FAILED,
+ DISK_BUSY,
+ ARRAY_START_FAILED,
+ ARRAY_STOP_FAILED,
+ ARRAY_NOT_FOUND,
+ INVAL,
+ NODEV,
+ NOENT,
+ NETDOWN,
+ BUSY,
+ INVALID_PORT,
+ INSECURE_PASSWORD,
+ INVALID_PROVIDER,
+ INVALID_NEXT_HOP,
+ INVALID_API_VERSION,
+ INVAL_WPS_MACFILTER,
+ INVAL_WPS_NEEDS_CCMP,
+ INVALID_ID,
+ PATH_NOT_FOUND,
+ ACCESS_DENIED,
+ DESTINATION_CONFLICT,
+ CANCELLED,
+ TASK_NOT_FOUND,
+ HTTP,
+ INVALID_URL,
+ INVALID_OPERATION,
+ INVALID_FILE,
+ CTX_FILE_ERROR,
+ HIBERNATING,
+ TOO_MANY_TASKS,
+ EXISTS,
+ EXIST,
+ CONNECTION_REFUSED,
+ NO_FREEBOX,
+ ALREADY_AUTHORIZED,
+ ECRC,
+ ERR_001,
+ ERR_002,
+ ERR_003,
+ ERR_004,
+ ERR_005,
+ ERR_009,
+ ERR_010,
+ ERR_030,
+ ERR_031,
+ NONE,
+ UNKNOWN;
+ }
+
+ private ErrorCode errorCode = ErrorCode.NONE;
+ private LoginManager.Permission missingRight = LoginManager.Permission.NONE;
+ private String msg = "";
+ private List<ResultType> result = List.of();
+ private boolean success;
+
+ // In some cases I did not understand deserialization can still produce null result
+ @SuppressWarnings("null")
+ public List<ResultType> getResult() {
+ List<ResultType> localResult = result;
+ return localResult != null ? localResult : List.of();
+ }
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public LoginManager.Permission getMissingRight() {
+ return missingRight;
+ }
+
+ public ErrorCode getErrorCode() {
+ return errorCode;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+}
--- /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.api.deserialization;
+
+import java.lang.reflect.Type;
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.ForegroundApp;
+import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.PlayerContext;
+import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.TvContext;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+
+/**
+ * Custom deserializer to handle {@link ForegroundApp} object
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class ForegroundAppDeserializer implements JsonDeserializer<ForegroundApp> {
+
+ @Override
+ public @NonNull ForegroundApp deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ Object obj;
+
+ String thePackage = json.getAsJsonObject().get("package").getAsString();
+ JsonElement jsonElement2 = json.getAsJsonObject().get("context");
+ if (jsonElement2 == null) {
+ obj = null;
+ } else if ("fr.freebox.tv".equals(thePackage)) {
+ obj = context.deserialize(jsonElement2, TvContext.class);
+ } else {
+ obj = context.deserialize(jsonElement2, PlayerContext.class);
+ }
+
+ int packageId = json.getAsJsonObject().get("package_id").getAsInt();
+ String curlUrl = json.getAsJsonObject().get("cur_url").getAsString();
+ Objects.requireNonNull(thePackage);
+ return new ForegroundApp(packageId, curlUrl, obj, thePackage);
+ }
+}
--- /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.api.deserialization;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+
+/**
+ * The {@link ListDeserializer} is a specialized deserializer aimed to transform a null object, a single object or
+ * a list of objects into a list containing 0, 1 or n elements.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class ListDeserializer implements JsonDeserializer<List<?>> {
+
+ @Override
+ public @NonNull List<?> deserialize(@Nullable JsonElement json, @Nullable Type clazz,
+ @Nullable JsonDeserializationContext context) throws JsonParseException {
+ if (json != null && clazz != null && context != null) {
+ JsonArray jsonArray = toJsonArray(json);
+ ArrayList<?> result = new ArrayList<>(jsonArray != null ? jsonArray.size() : 0);
+
+ if (jsonArray != null) {
+ Type[] typeArguments = ((ParameterizedType) clazz).getActualTypeArguments();
+ if (typeArguments.length > 0) {
+ Type objectType = typeArguments[0];
+ for (int i = 0; i < jsonArray.size(); i++) {
+ result.add(context.deserialize(jsonArray.get(i), objectType));
+ }
+ return result;
+ }
+ }
+ }
+ return List.of();
+ }
+
+ private @Nullable JsonArray toJsonArray(JsonElement json) {
+ if (json instanceof JsonArray) {
+ return json.getAsJsonArray();
+ } else if (json instanceof JsonObject) {
+ JsonArray jsonArray = new JsonArray();
+ if (json.getAsJsonObject().size() > 0) {
+ jsonArray.add(json);
+ }
+ return jsonArray;
+ }
+ return null;
+ }
+}
--- /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.api.deserialization;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+/**
+ * Enforces a fallback to UNKNOWN when deserializing enum types, marked as @NonNull whereas they were valued
+ * to null if the appropriate value is absent.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class StrictEnumTypeAdapterFactory implements TypeAdapterFactory {
+ private static final StringReader UNKNOWN = new StringReader("\"UNKNOWN\"");
+
+ @Override
+ public @Nullable <T> TypeAdapter<T> create(@NonNullByDefault({}) Gson gson,
+ @NonNullByDefault({}) TypeToken<T> type) {
+ @SuppressWarnings("unchecked")
+ Class<T> rawType = (Class<T>) type.getRawType();
+ return rawType.isEnum() ? newStrictEnumAdapter(gson.getDelegateAdapter(this, type)) : null;
+ }
+
+ private <T> TypeAdapter<T> newStrictEnumAdapter(TypeAdapter<T> delegateAdapter) {
+ return new TypeAdapter<T>() {
+ @Override
+ public void write(JsonWriter out, @Nullable T value) throws IOException {
+ delegateAdapter.write(out, value);
+ }
+
+ @Override
+ public @NonNull T read(JsonReader in) throws IOException {
+ String searched = in.nextString().toUpperCase().replace("/", "_").replace("-", "_");
+ JsonReader delegateReader = new JsonReader(new StringReader('"' + searched + '"'));
+ @Nullable
+ T value = delegateAdapter.read(delegateReader);
+ delegateReader.close();
+ if (value == null) {
+ UNKNOWN.reset();
+ value = delegateAdapter.read(new JsonReader(UNKNOWN));
+ }
+ return Objects.requireNonNull(value);
+ }
+ };
+ }
+}
--- /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.api.rest;
+
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.LanHost;
+
+import inet.ipaddr.mac.MACAddress;
+
+/**
+ * The {@link APManager} is the Java class used to handle api requests related to wifi access points
+ * provided by the Freebox Server
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class APManager extends ListableRest<APManager.WifiAp, APManager.APResponse> {
+ private static final String PATH = "ap";
+ private static final String STATIONS_PATH = "stations";
+
+ protected static record WifiInformation(String ssid, String band, int signal) { // Valid RSSI goes from -120 to 0
+ }
+
+ public static record LanAccessPoint(String mac, String type, String uid, @Nullable String connectivityType,
+ long rxBytes, // received bytes (from station to Freebox)
+ long txBytes, // transmitted bytes (from Freebox to station)
+ long txRate, // reception data rate (in bytes/s)
+ long rxRate, // transmission data rate (in bytes/s)
+ WifiInformation wifiInformation) {
+
+ public int getSignal() {
+ return wifiInformation.signal();
+ }
+
+ public @Nullable String getSsid() {
+ return wifiInformation().ssid();
+ }
+ }
+
+ private static enum State {
+ ASSOCIATED,
+ AUTHENTICATED,
+ UNKNOWN;
+ }
+
+ public static record Station(String id, MACAddress mac, String bssid, @Nullable String hostname, LanHost host,
+ State state, int inactive, int connDuration, //
+ long rxBytes, // received bytes (from station to Freebox)
+ long txBytes, // transmitted bytes (from Freebox to station)
+ long txRate, // reception data rate (in bytes/s)
+ long rxRate, // transmission data rate (in bytes/s)
+ int signal) { // signal attenuation (in dB)
+
+ public @Nullable String getSsid() {
+ LanAccessPoint accessPoint = host.accessPoint();
+ return accessPoint != null ? accessPoint.getSsid() : null;
+ }
+
+ public @Nullable ZonedDateTime getLastSeen() {
+ return host.getLastSeen();
+ }
+ }
+
+ protected static record ApStatus(ApState state, int channelWidth, int primaryChannel, int secondaryChannel,
+ int dfsCacRemainingTime, boolean dfsDisabled) {
+ private static enum ApState {
+ SCANNING, // Ap is probing wifi channels
+ NO_PARAM, // Ap is not configured
+ BAD_PARAM, // Ap has an invalid configuration
+ DISABLED, // Ap is permanently disabled
+ DISABLED_PLANNING, // Ap is currently disabled according to planning
+ NO_ACTIVE_BSS, // Ap has no active BSS
+ STARTING, // Ap is starting
+ ACS, // Ap is selecting the best available channel
+ HT_SCAN, // Ap is scanning for other access point
+ DFS, // Ap is performing dynamic frequency selection
+ ACTIVE, // Ap is active
+ FAILED, // Ap has failed to start
+ UNKNOWN;
+ }
+ }
+
+ protected static record WifiAp(int id, String name, ApStatus status) {
+ }
+
+ private class ApHostsResponse extends Response<Station> {
+ }
+
+ protected class APResponse extends Response<WifiAp> {
+ }
+
+ public APManager(FreeboxOsSession session, UriBuilder uriBuilder) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, APResponse.class, uriBuilder.path(PATH));
+ }
+
+ private List<Station> getApStations(int apId) throws FreeboxException {
+ return get(ApHostsResponse.class, Integer.toString(apId), STATIONS_PATH);
+ }
+
+ public List<Station> getStations() throws FreeboxException {
+ List<Station> hosts = new ArrayList<>();
+ for (WifiAp ap : getDevices()) {
+ hosts.addAll(getApStations(ap.id));
+ }
+ return hosts;
+ }
+
+ public Optional<Station> getStation(MACAddress mac) throws FreeboxException {
+ return getStations().stream().filter(host -> host.mac().equals(mac)).findFirst();
+ }
+}
--- /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.api.rest;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+
+/**
+ * The {@link AfpManager} is the Java class used to handle api requests related to Afp shares
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class AfpManager extends ConfigurableRest<AfpManager.Afp, AfpManager.ConfigResponse> {
+ private static final String AFP_PATH = "afp";
+
+ protected static class ConfigResponse extends Response<Afp> {
+ }
+
+ protected static record Afp(boolean enabled, boolean guestAllow, ServerType serverType, @Nullable String loginName,
+ @Nullable String loginPassword) {
+ private static enum ServerType {
+ POWERBOOK,
+ POWERMAC,
+ MACMINI,
+ IMAC,
+ MACBOOK,
+ MACBOOKPRO,
+ MACBOOKAIR,
+ MACPRO,
+ APPLETV,
+ AIRPORT,
+ XSERVE,
+ UNKNOWN;
+ }
+ }
+
+ public AfpManager(FreeboxOsSession session, UriBuilder uriBuilder) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, ConfigResponse.class, uriBuilder.path(AFP_PATH), null);
+ }
+
+ public boolean getStatus() throws FreeboxException {
+ return getConfig().enabled;
+ }
+
+ public boolean setStatus(boolean enabled) throws FreeboxException {
+ Afp config = getConfig();
+ Afp newConfig = new Afp(enabled, config.guestAllow, config.serverType, config.loginName, config.loginPassword);
+ return setConfig(newConfig).enabled;
+ }
+}
--- /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.api.rest;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+
+/**
+ * The {@link AirMediaManager} is the Java class used to handle api requests related to air media global configuration
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class AirMediaManager extends ConfigurableRest<AirMediaManager.Config, AirMediaManager.ConfigResponse> {
+ private static final String PATH = "airmedia";
+
+ protected static record Config(boolean enabled) {
+ }
+
+ protected static class ConfigResponse extends Response<Config> {
+ }
+
+ public AirMediaManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, ConfigResponse.class, session.getUriBuilder().path(PATH),
+ CONFIG_PATH);
+ session.addManager(MediaReceiverManager.class, new MediaReceiverManager(session, getUriBuilder()));
+ }
+
+ public boolean getStatus() throws FreeboxException {
+ return getConfig().enabled();
+ }
+
+ public boolean setStatus(boolean enabled) throws FreeboxException {
+ return setConfig(new Config(enabled)).enabled();
+ }
+}
--- /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.api.rest;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.THING_CALL;
+
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+
+/**
+ * The {@link CallManager} is the Java class used to handle api requests related to calls
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class CallManager extends RestManager {
+ private static final String LOG_SUB_PATH = "log/";
+ private static final String DELETE_ACTION = "delete_all";
+
+ private static class Calls extends Response<Call> {
+ }
+
+ public static enum Type {
+ ACCEPTED,
+ MISSED,
+ OUTGOING,
+ INCOMING,
+ UNKNOWN;
+ }
+
+ public static record Call(Type type, //
+ ZonedDateTime datetime, // Call creation timestamp.
+ String number, // Calling or called number
+ int duration, // Call duration in seconds.
+ String name) {
+
+ public @Nullable String name() {
+ return name.equals(number) ? null : name;
+ }
+ }
+
+ public CallManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.CALLS, session.getUriBuilder().path(THING_CALL));
+ }
+
+ // Retrieves a sorted list of all call entries
+ public List<Call> getCallEntries() throws FreeboxException {
+ List<Call> callList = new ArrayList<>(
+ get(Calls.class, LOG_SUB_PATH).stream().sorted(Comparator.comparing(Call::datetime)).toList());
+ Call last = callList.get(callList.size() - 1);
+ // The INCOMING type call can only be set on the last call if its duration is 0;
+ if (last.type == Type.MISSED && last.duration == 0) {
+ callList.remove(callList.size() - 1);
+ callList.add(new Call(Type.INCOMING, last.datetime, last.number, 0, last.name));
+ }
+ return callList;
+ }
+
+ public void emptyQueue() throws FreeboxException {
+ post(LOG_SUB_PATH, DELETE_ACTION);
+ }
+}
--- /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.api.rest;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+
+/**
+ * The {@link ConfigurableRest} is the Java class used to handle portions of the Api that accept to get and set
+ * configuration based on a given DTO
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class ConfigurableRest<T, Y extends Response<T>> extends RestManager {
+ protected static final String CONFIG_PATH = "config";
+
+ private final Class<Y> responseClazz;
+ private final @Nullable String configPath;
+
+ protected ConfigurableRest(FreeboxOsSession session, LoginManager.Permission required, Class<Y> responseClazz,
+ UriBuilder uri, @Nullable String configPath) throws FreeboxException {
+ super(session, required, uri);
+ this.responseClazz = responseClazz;
+ this.configPath = configPath;
+ }
+
+ public T getConfig() throws FreeboxException {
+ return configPath != null ? getSingle(responseClazz, configPath) : getSingle(responseClazz);
+ }
+
+ protected T setConfig(T config) throws FreeboxException {
+ return configPath != null ? put(responseClazz, config, configPath) : put(responseClazz, config);
+ }
+}
--- /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.api.rest;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+
+import inet.ipaddr.IPAddress;
+
+/**
+ * The {@link ConnectionManager} is the Java class used to handle api requests related to connection
+ *
+ * https://dev.freebox.fr/sdk/os/system/#
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class ConnectionManager extends ConfigurableRest<ConnectionManager.Status, ConnectionManager.StatusResponse> {
+ private static final String PATH = "connection";
+
+ protected static class StatusResponse extends Response<Status> {
+ }
+
+ private static enum State {
+ GOING_UP,
+ UP,
+ GOING_DOWN,
+ DOWN,
+ UNKNOWN;
+ }
+
+ private static enum Type {
+ ETHERNET,
+ RFC2684,
+ PPPOATM,
+ UNKNOWN;
+ }
+
+ private static enum Media {
+ FTTH,
+ ETHERNET,
+ XDSL,
+ BACKUP_4G,
+ UNKNOWN;
+ }
+
+ public static record Status(State state, Type type, Media media, @Nullable List<Integer> ipv4PortRange,
+ @Nullable IPAddress ipv4, // This can be null if state is not up
+ @Nullable IPAddress ipv6, // This can be null if state is not up
+ long rateUp, // current upload rate in byte/s
+ long rateDown, // current download rate in byte/s
+ long bandwidthUp, // available upload bandwidth in bit/s
+ long bandwidthDown, // available download bandwidth in bit/s
+ long bytesUp, // total uploaded bytes since last connection
+ long bytesDown // total downloaded bytes since last connection
+ ) {
+ }
+
+ public ConnectionManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, StatusResponse.class, session.getUriBuilder().path(PATH), null);
+ }
+}
--- /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.api.rest;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.http.HttpMethod;
+import org.openhab.binding.freeboxos.internal.api.ApiHandler;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.PermissionException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+import org.openhab.binding.freeboxos.internal.api.Response.ErrorCode;
+import org.openhab.binding.freeboxos.internal.api.rest.LoginManager.Session;
+import org.openhab.binding.freeboxos.internal.config.FreeboxOsConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link FreeboxOsSession} is responsible for sending requests toward a given url and transform the answer in
+ * appropriate dto.
+ *
+ * @author Gaël L'Hopital - Initial contribution
+ */
+@NonNullByDefault
+public class FreeboxOsSession {
+ private static final String API_VERSION_PATH = "api_version";
+
+ private final Logger logger = LoggerFactory.getLogger(FreeboxOsSession.class);
+ private final Map<Class<? extends RestManager>, RestManager> restManagers = new HashMap<>();
+ private final ApiHandler apiHandler;
+
+ private @NonNullByDefault({}) UriBuilder uriBuilder;
+ private @Nullable Session session;
+ private String appToken = "";
+
+ public static enum BoxModel {
+ FBXGW_R1_FULL, // Freebox Server (v6) revision 1
+ FBXGW_R2_FULL, // Freebox Server (v6) revision 2
+ FBXGW_R1_MINI, // Freebox Mini revision 1
+ FBXGW_R2_MINI, // Freebox Mini revision 2
+ FBXGW_R1_ONE, // Freebox One revision 1
+ FBXGW_R2_ONE, // Freebox One revision 2
+ FBXGW7_R1_FULL, // Freebox v7 revision 1
+ UNKNOWN;
+ }
+
+ public static record ApiVersion(String apiBaseUrl, @Nullable String apiDomain, String apiVersion, BoxModel boxModel,
+ @Nullable String boxModelName, String deviceName, String deviceType, boolean httpsAvailable, int httpsPort,
+ String uid) {
+
+ /**
+ * @return a string like eg: '/api/v8'
+ */
+ private String baseUrl() {
+ return "%sv%s".formatted(apiBaseUrl, apiVersion.split("\\.")[0]);
+ }
+ }
+
+ public FreeboxOsSession(ApiHandler apiHandler) {
+ this.apiHandler = apiHandler;
+ }
+
+ public void initialize(FreeboxOsConfiguration config) throws FreeboxException, InterruptedException {
+ ApiVersion version = apiHandler.executeUri(config.getUriBuilder(API_VERSION_PATH).build(), HttpMethod.GET,
+ ApiVersion.class, null, null);
+ this.uriBuilder = config.getUriBuilder(version.baseUrl());
+ getManager(LoginManager.class);
+ getManager(NetShareManager.class);
+ getManager(LanManager.class);
+ getManager(WifiManager.class);
+ getManager(FreeplugManager.class);
+ getManager(AirMediaManager.class);
+ }
+
+ public void openSession(String appToken) throws FreeboxException {
+ Session newSession = getManager(LoginManager.class).openSession(appToken);
+ getManager(WebSocketManager.class).openSession(newSession.sessionToken());
+ session = newSession;
+ this.appToken = appToken;
+ }
+
+ public String grant() throws FreeboxException {
+ return getManager(LoginManager.class).checkGrantStatus();
+ }
+
+ public void closeSession() {
+ Session currentSession = session;
+ if (currentSession != null) {
+ try {
+ getManager(WebSocketManager.class).closeSession();
+ getManager(LoginManager.class).closeSession();
+ session = null;
+ } catch (FreeboxException e) {
+ logger.warn("Error closing session: {}", e.getMessage());
+ }
+ }
+ appToken = "";
+ restManagers.clear();
+ }
+
+ private synchronized <F, T extends Response<F>> List<F> execute(URI uri, HttpMethod method, Class<T> clazz,
+ boolean retryAuth, int retryCount, @Nullable Object aPayload) throws FreeboxException {
+ try {
+ T response = apiHandler.executeUri(uri, method, clazz, getSessionToken(), aPayload);
+ if (response.getErrorCode() == ErrorCode.INTERNAL_ERROR && retryCount > 0) {
+ return execute(uri, method, clazz, false, retryCount - 1, aPayload);
+ } else if (retryAuth && response.getErrorCode() == ErrorCode.AUTH_REQUIRED) {
+ openSession(appToken);
+ return execute(uri, method, clazz, false, retryCount, aPayload);
+ }
+ if (!response.isSuccess()) {
+ throw new FreeboxException("Api request failed: %s", response.getMsg());
+ }
+ return response.getResult();
+ } catch (FreeboxException e) {
+ if (ErrorCode.AUTH_REQUIRED.equals(e.getErrorCode())) {
+ openSession(appToken);
+ return execute(uri, method, clazz, false, retryCount, aPayload);
+ }
+ throw e;
+ } catch (InterruptedException ignored) {
+ return List.of();
+ }
+ }
+
+ public <F, T extends Response<F>> List<F> execute(URI uri, HttpMethod method, Class<T> clazz,
+ @Nullable Object aPayload) throws FreeboxException {
+ return execute(uri, method, clazz, getSessionToken() != null, 3, aPayload);
+ }
+
+ @SuppressWarnings("unchecked")
+ public synchronized <T extends RestManager> T getManager(Class<T> clazz) throws FreeboxException {
+ RestManager manager = restManagers.get(clazz);
+ if (manager == null) {
+ try {
+ Constructor<T> managerConstructor = clazz.getConstructor(FreeboxOsSession.class);
+ manager = addManager(clazz, managerConstructor.newInstance(this));
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof PermissionException) {
+ throw (PermissionException) cause;
+ }
+ throw new FreeboxException(e, "Unable to call RestManager constructor for %s", clazz.getName());
+ } catch (ReflectiveOperationException e) {
+ throw new FreeboxException(e, "Unable to call RestManager constructor for %s", clazz.getName());
+ }
+ }
+ return (T) manager;
+ }
+
+ public <T extends RestManager> T addManager(Class<T> clazz, T manager) {
+ restManagers.put(clazz, manager);
+ return manager;
+ }
+
+ boolean hasPermission(LoginManager.Permission required) {
+ Session currentSession = session;
+ return currentSession != null ? currentSession.hasPermission(required) : false;
+ }
+
+ private @Nullable String getSessionToken() {
+ Session currentSession = session;
+ return currentSession != null ? currentSession.sessionToken() : null;
+ }
+
+ public UriBuilder getUriBuilder() {
+ return uriBuilder.clone();
+ }
+
+ public ApiHandler getApiHandler() {
+ return apiHandler;
+ }
+}
--- /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.api.rest;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.THING_FREEPLUG;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+
+import inet.ipaddr.mac.MACAddress;
+
+/**
+ * The {@link FreeplugManager} is the Java class used to handle api requests related to freeplugs
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class FreeplugManager extends RestManager {
+ private static final String RESET_ACTION = "reset";
+
+ private static class Networks extends Response<Network> {
+ }
+
+ public static enum NetRole {
+ STA, // Freeplug station
+ PCO, // Freeplug proxy coordinator
+ CCO, // Central Coordinator
+ UNKNOWN;
+ }
+
+ private enum Status {
+ UP,
+ DOWN,
+ UNKNOWN
+ }
+
+ public static record Freeplug(MACAddress id, String netId, // Id of the network holding the plug
+ boolean local, // if true the Freeplug is connected directly to the Freebox
+ NetRole netRole, // Freeplug network role
+ String model, Status ethPortStatus, //
+ boolean ethFullDuplex, // ethernet link is full duplex
+ boolean hasNetwork, // is connected to the network
+ int ethSpeed, // ethernet port speed
+ int inactive, // seconds since last activity
+ int rxRate, // rx rate (from the freeplugs to the “cco” freeplug) (in Mb/s) -1 if not available
+ int txRate) { // tx rate (from the “cco” freeplug to the freeplugs) (in Mb/s) -1 if not available
+ }
+
+ private static record Network(MACAddress id, List<Freeplug> members) {
+ }
+
+ public FreeplugManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, session.getUriBuilder().path(THING_FREEPLUG));
+ }
+
+ // Most of the users will host only one CPL network on their server, so we hide the network level in the manager
+ public List<Freeplug> getPlugs() throws FreeboxException {
+ return get(Networks.class).stream().map(Network::members).flatMap(List::stream).toList();
+ }
+
+ public Optional<Freeplug> getPlug(MACAddress mac) throws FreeboxException {
+ return getPlugs().stream().filter(plug -> plug.id.equals(mac)).findFirst();
+ }
+
+ public void reboot(MACAddress mac) throws FreeboxException {
+ post(mac.toColonDelimitedString(), RESET_ACTION);
+ }
+}
--- /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.api.rest;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+
+/**
+ * The {@link FtpManager} is the Java class used to handle api requests related to ftp
+ *
+ * https://dev.freebox.fr/sdk/os/system/#
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class FtpManager extends ConfigurableRest<FtpManager.Config, FtpManager.ConfigResponse> {
+ private static final String PATH = "ftp";
+
+ protected static class ConfigResponse extends Response<Config> {
+ }
+
+ protected static record Config(boolean enabled, boolean allowAnonymous, boolean allowAnonymousWrite,
+ boolean allowRemoteAccess, boolean weakPassword, int portCtrl, int portData, String remoteDomain) {
+ }
+
+ public FtpManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, ConfigResponse.class, session.getUriBuilder().path(PATH),
+ CONFIG_PATH);
+ }
+
+ public boolean getStatus() throws FreeboxException {
+ return getConfig().enabled();
+ }
+
+ public boolean setStatus(boolean enabled) throws FreeboxException {
+ Config oldConfig = getConfig();
+ Config newConfig = new Config(enabled, oldConfig.allowAnonymous, oldConfig.allowAnonymousWrite,
+ oldConfig.allowRemoteAccess, oldConfig.weakPassword, oldConfig.portCtrl, oldConfig.portData,
+ oldConfig.remoteDomain);
+ return setConfig(newConfig).enabled();
+ }
+}
--- /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.api.rest;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.BINDING_ID;
+
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+import org.openhab.core.thing.ThingTypeUID;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link HomeManager} is the Java class used to handle api requests related to home
+ *
+ * @author ben12 - Initial contribution
+ */
+@NonNullByDefault
+public class HomeManager extends RestManager {
+ private static final String PATH = "home";
+ private static final String NODES_PATH = "nodes";
+ private static final String ENDPOINTS_PATH = "endpoints";
+
+ private static class EndpointStateResponse extends Response<EndpointState> {
+ }
+
+ private static class HomeNodesResponse extends Response<HomeNode> {
+ }
+
+ private static enum AccessType {
+ R,
+ W,
+ RW,
+ UNKNOWN;
+ }
+
+ private static enum DisplayType {
+ TEXT,
+ ICON,
+ BUTTON,
+ SLIDER,
+ TOGGLE,
+ COLOR,
+ WARNING,
+ UNKNOWN;
+ }
+
+ private static record EndpointValue<T>(T value) {
+ }
+
+ private static record EndpointUi(AccessType access, DisplayType display, String iconUrl, @Nullable String unit) {
+ }
+
+ private static enum ValueType {
+ BOOL,
+ INT,
+ FLOAT,
+ VOID,
+ STRING,
+ UNKNOWN;
+ }
+
+ public static record EndpointState(@Nullable String value, ValueType valueType, long refresh) {
+ public boolean asBoolean() {
+ String local = value;
+ return local != null ? Boolean.valueOf(local) : false;
+ }
+
+ public int asInt() {
+ String local = value;
+ return local != null ? Integer.valueOf(local) : Integer.MIN_VALUE;
+ }
+
+ public @Nullable String value() {
+ return value;
+ }
+ }
+
+ public static enum EpType {
+ SIGNAL,
+ SLOT,
+ UNKNOWN;
+
+ public String asConfId() {
+ return name().toLowerCase();
+ }
+ }
+
+ private static record LogEntry(long timestamp, int value) {
+ }
+
+ public static record Endpoint(int id, String name, String label, EpType epType, Visibility visibility, int refresh,
+ ValueType valueType, EndpointUi ui, @Nullable String category, Object value, List<LogEntry> history) {
+ private static enum Visibility {
+ INTERNAL,
+ NORMAL,
+ DASHBOARD,
+ UNKNOWN;
+ }
+ }
+
+ private static enum Status {
+ UNREACHABLE,
+ DISABLED,
+ ACTIVE,
+ UNPAIRED,
+ UNKNOWN;
+ }
+
+ public static enum Category {
+ BASIC_SHUTTER,
+ SHUTTER,
+ ALARM,
+ KFB,
+ CAMERA,
+ UNKNOWN;
+
+ private final ThingTypeUID thingTypeUID;
+
+ Category() {
+ thingTypeUID = new ThingTypeUID(BINDING_ID, name().toLowerCase());
+ }
+
+ public ThingTypeUID getThingTypeUID() {
+ return thingTypeUID;
+ }
+ }
+
+ public static record NodeType(@SerializedName("abstract") boolean _abstract, List<Endpoint> endpoints,
+ boolean generic, String icon, String inherit, String label, String name, boolean physical) {
+ }
+
+ public static record HomeNode(int id, @Nullable String name, @Nullable String label, Category category,
+ Status status, List<Endpoint> showEndpoints, Map<String, String> props, NodeType type) {
+ }
+
+ public HomeManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.HOME, session.getUriBuilder().path(PATH));
+ }
+
+ public List<HomeNode> getHomeNodes() throws FreeboxException {
+ return get(HomeNodesResponse.class, NODES_PATH);
+ }
+
+ public HomeNode getHomeNode(int nodeId) throws FreeboxException {
+ return getSingle(HomeNodesResponse.class, NODES_PATH, Integer.toString(nodeId));
+ }
+
+ public <T> @Nullable EndpointState getEndpointsState(int nodeId, int stateSignalId) throws FreeboxException {
+ return getSingle(EndpointStateResponse.class, ENDPOINTS_PATH, String.valueOf(nodeId),
+ String.valueOf(stateSignalId));
+ }
+
+ public <T> boolean putCommand(int nodeId, int stateSignalId, T value) throws FreeboxException {
+ put(new EndpointValue<T>(value), ENDPOINTS_PATH, String.valueOf(nodeId), String.valueOf(stateSignalId));
+ return true;
+ }
+}
--- /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.api.rest;
+
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+import org.openhab.binding.freeboxos.internal.api.rest.APManager.LanAccessPoint;
+import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.InterfacesResponse;
+
+import inet.ipaddr.IPAddress;
+import inet.ipaddr.IPAddressString;
+import inet.ipaddr.mac.MACAddress;
+
+/**
+ * The {@link LanBrowserManager} is the Java class used to handle api requests related to lan
+ *
+ * https://dev.freebox.fr/sdk/os/system/#
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class LanBrowserManager extends ListableRest<LanBrowserManager.Interface, InterfacesResponse> {
+ private static final IPAddress NULL_IP = new IPAddressString("0.0.0.0").getAddress();
+ private static final String PATH = "browser";
+ private static final String INTERFACES = "interfaces";
+ private static final String WOL_ACTION = "wol";
+
+ protected static class HostsResponse extends Response<LanHost> {
+ }
+
+ protected static class InterfacesResponse extends Response<Interface> {
+ }
+
+ public static enum Source {
+ DHCP,
+ NETBIOS,
+ MDNS,
+ MDNS_SRV,
+ UPNP,
+ WSD,
+ UNKNOWN;
+ }
+
+ public record HostName(@Nullable String name, Source source) {
+ }
+
+ protected static record Interface(String name, int hostCount) {
+ }
+
+ private static record WakeOnLineData(String mac, String password) {
+ }
+
+ private static enum Type {
+ MAC_ADDRESS,
+ UNKNOWN;
+ }
+
+ private static record L2Ident(MACAddress id, Type type) {
+ }
+
+ private static record L3Connectivity(String addr, Af af, boolean active, boolean reachable,
+ ZonedDateTime lastActivity, ZonedDateTime lastTimeReachable, String model) {
+
+ private static enum Af {
+ IPV4,
+ IPV6,
+ UNKNOWN;
+ }
+
+ public IPAddress getIPAddress() {
+ if (af != Af.UNKNOWN) {
+ return new IPAddressString(addr).getAddress();
+ }
+ return NULL_IP;
+ }
+ }
+
+ public static record HostIntf(LanHost host, Interface intf) {
+ }
+
+ private static enum HostType {
+ WORKSTATION,
+ LAPTOP,
+ SMARTPHONE,
+ TABLET,
+ PRINTER,
+ VG_CONSOLE,
+ TELEVISION,
+ NAS,
+ IP_CAMERA,
+ IP_PHONE,
+ FREEBOX_PLAYER,
+ FREEBOX_HD,
+ FREEBOX_CRYSTAL,
+ FREEBOX_MINI,
+ FREEBOX_DELTA,
+ FREEBOX_ONE,
+ FREEBOX_WIFI,
+ FREEBOX_POP,
+ NETWORKING_DEVICE,
+ MULTIMEDIA_DEVICE,
+ CAR,
+ OTHER,
+ UNKNOWN;
+ }
+
+ 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) {
+
+ public @Nullable LanAccessPoint accessPoint() {
+ return accessPoint;
+ }
+
+ public String vendorName() {
+ String localVendor = vendorName;
+ return localVendor != null ? localVendor : "Unknown";
+ }
+
+ 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 MACAddress getMac() {
+ if (Type.MAC_ADDRESS.equals(l2ident.type)) {
+ return l2ident.id;
+ }
+ throw new IllegalArgumentException("This host does not seem to have a Mac Address. Weird.");
+ }
+
+ public @Nullable IPAddress getIpv4() {
+ return l3connectivities.stream().filter(L3Connectivity::reachable).map(L3Connectivity::getIPAddress)
+ .filter(ip -> !ip.equals(NULL_IP) && ip.isIPv4()).findFirst().orElse(null);
+ }
+
+ public @Nullable ZonedDateTime getLastSeen() {
+ ZonedDateTime localLastActivity = lastActivity;
+ if (lastTimeReachable == null && localLastActivity == null) {
+ return null;
+ }
+ if (lastTimeReachable == null) {
+ return lastActivity;
+ }
+ if (localLastActivity == null) {
+ return lastTimeReachable;
+ } else {
+ return localLastActivity.isAfter(lastTimeReachable) ? lastActivity : lastTimeReachable;
+ }
+ }
+ }
+
+ private final List<Interface> interfaces = new ArrayList<>();
+
+ public LanBrowserManager(FreeboxOsSession session, UriBuilder uriBuilder) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, InterfacesResponse.class, uriBuilder.path(PATH));
+ listSubPath = INTERFACES;
+ }
+
+ private List<LanHost> getInterfaceHosts(String lanInterface) throws FreeboxException {
+ return get(HostsResponse.class, lanInterface);
+ }
+
+ private @Nullable LanHost getHost(String lanInterface, String hostId) throws FreeboxException {
+ return getSingle(HostsResponse.class, lanInterface, hostId);
+ }
+
+ // As the list of interfaces on the box may not change, we cache the result
+ private List<Interface> getInterfaces() throws FreeboxException {
+ if (interfaces.isEmpty()) {
+ interfaces.addAll(getDevices());
+ }
+ return interfaces;
+ }
+
+ public synchronized List<LanHost> getHosts() throws FreeboxException {
+ List<LanHost> hosts = new ArrayList<>();
+
+ for (Interface intf : getInterfaces()) {
+ hosts.addAll(getInterfaceHosts(intf.name()));
+ }
+ return hosts;
+ }
+
+ public Optional<HostIntf> getHost(MACAddress searched) throws FreeboxException {
+ for (Interface intf : getInterfaces()) {
+ LanHost host = getHost(intf.name(), "ether-" + searched.toColonDelimitedString());
+ if (host != null) {
+ return Optional.of(new HostIntf(host, intf));
+ }
+ }
+ return Optional.empty();
+ }
+
+ public boolean wakeOnLan(MACAddress mac, String password) throws FreeboxException {
+ Optional<HostIntf> target = getHost(mac);
+ if (target.isPresent()) {
+ post(new WakeOnLineData(mac.toColonDelimitedString(), password), GenericResponse.class, WOL_ACTION,
+ target.get().intf.name);
+ return true;
+ }
+ return false;
+ }
+}
--- /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.api.rest;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+
+import inet.ipaddr.IPAddress;
+
+/**
+ * The {@link LanManager} is the Java class used to handle api requests related to lan
+ * https://dev.freebox.fr/sdk/os/system/#
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class LanManager extends ConfigurableRest<LanManager.LanConfig, LanManager.Config> {
+ private static final String PATH = "lan";
+
+ protected static class Config extends Response<LanConfig> {
+ }
+
+ private static enum Mode {
+ ROUTER,
+ BRIDGE,
+ UNKNOWN;
+ }
+
+ public static record LanConfig(IPAddress ip, String name, String nameDns, String nameMdns, String nameNetbios,
+ Mode mode) {
+ }
+
+ public LanManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, Config.class, session.getUriBuilder().path(PATH), CONFIG_PATH);
+ session.addManager(LanBrowserManager.class, new LanBrowserManager(session, getUriBuilder()));
+ }
+}
--- /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.api.rest;
+
+import java.util.concurrent.Callable;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+
+/**
+ * The {@link LcdManager} is the Java class used to handle api requests related to lcd screen of the server
+ * https://dev.freebox.fr/sdk/os/system/#
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class LcdManager extends ConfigurableRest<LcdManager.Config, LcdManager.ConfigResponse> {
+ private static final String PATH = "lcd";
+
+ protected static class ConfigResponse extends Response<Config> {
+ }
+
+ public static record Config(int brightness, int orientation, boolean orientationForced) {
+ }
+
+ public LcdManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, ConfigResponse.class, session.getUriBuilder().path(PATH),
+ CONFIG_PATH);
+ }
+
+ private void setBrightness(int brightness) throws FreeboxException {
+ Config oldConfig = getConfig();
+ setConfig(new Config(brightness, oldConfig.orientation, oldConfig.orientationForced));
+ }
+
+ public void setOrientation(int orientation) throws FreeboxException {
+ Config oldConfig = getConfig();
+ setConfig(new Config(oldConfig.brightness, orientation, oldConfig.orientationForced));
+ }
+
+ public void setOrientationForced(boolean forced) throws FreeboxException {
+ Config oldConfig = getConfig();
+ setConfig(new Config(oldConfig.brightness, oldConfig.orientation, forced));
+ }
+
+ public void setBrightness(Callable<Integer> function) throws FreeboxException {
+ try {
+ setBrightness(function.call());
+ } catch (Exception e) {
+ throw new FreeboxException(e, "Error setting brightness");
+ }
+ }
+}
--- /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.api.rest;
+
+import java.util.List;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+
+/**
+ * The {@link ListableRest} is the Java class used to handle rest answers holding a list of known equipments
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class ListableRest<T, Z extends Response<T>> extends RestManager {
+ private final Class<Z> deviceResponseClass;
+
+ protected @Nullable String listSubPath = null;
+
+ public ListableRest(FreeboxOsSession session, LoginManager.Permission required, Class<Z> respClass, UriBuilder uri)
+ throws FreeboxException {
+ super(session, required, uri);
+ this.deviceResponseClass = respClass;
+ }
+
+ public List<T> getDevices() throws FreeboxException {
+ return listSubPath == null ? get(deviceResponseClass) : get(deviceResponseClass, listSubPath);
+ }
+
+ public T getDevice(int deviceId) throws FreeboxException {
+ return getSingle(deviceResponseClass, Integer.toString(deviceId));
+ }
+}
--- /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.api.rest;
+
+import static javax.xml.bind.DatatypeConverter.printHexBinary;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+
+/**
+ * The {@link LoginManager} is the Java class used to handle api requests related to session handling and login
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class LoginManager extends RestManager {
+ private static final Bundle BUNDLE = FrameworkUtil.getBundle(LoginManager.class);
+ private static final String APP_ID = BUNDLE.getSymbolicName();
+ private static final String ALGORITHM = "HmacSHA1";
+ private static final String PATH = "login";
+ private static final String SESSION = "session";
+ private static final String AUTHORIZE_ACTION = "authorize";
+ private static final String LOGOUT = "logout";
+
+ private static enum Status {
+ PENDING, // the user has not confirmed the autorization request yet
+ TIMEOUT, // the user did not confirmed the authorization within the given time
+ GRANTED, // the app_token is valid and can be used to open a session
+ DENIED, // the user denied the authorization request
+ UNKNOWN; // the app_token is invalid or has been revoked
+ }
+
+ private static record AuthorizationStatus(Status status, boolean loggedIn, String challenge,
+ @Nullable String passwordSalt, boolean passwordSet) {
+ }
+
+ private static class AuthStatus extends Response<AuthorizationStatus> {
+ }
+
+ private static record Authorization(String appToken, String trackId) {
+ }
+
+ private static class AuthResponse extends Response<Authorization> {
+ }
+
+ public static enum Permission {
+ PARENTAL,
+ CONTACTS,
+ EXPLORER,
+ TV,
+ WDO,
+ DOWNLOADER,
+ PROFILE,
+ CAMERA,
+ SETTINGS,
+ CALLS,
+ HOME,
+ PVR,
+ VM,
+ PLAYER,
+ NONE,
+ UNKNOWN;
+ }
+
+ public static record Session(Map<LoginManager.Permission, @Nullable Boolean> permissions,
+ @Nullable String sessionToken) {
+ protected boolean hasPermission(LoginManager.Permission checked) {
+ return Boolean.TRUE.equals(permissions.get(checked));
+ }
+ }
+
+ private static class SessionResponse extends Response<Session> {
+ }
+
+ private static record AuthorizeData(String appId, String appName, String appVersion, String deviceName) {
+ AuthorizeData(String appId, Bundle bundle) {
+ this(appId, bundle.getHeaders().get("Bundle-Name"), bundle.getVersion().toString(),
+ bundle.getHeaders().get("Bundle-Vendor"));
+ }
+ }
+
+ private static record OpenSessionData(String appId, String password) {
+ }
+
+ private final Mac mac;
+ private Optional<Authorization> authorize = Optional.empty();
+
+ public LoginManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, session.getUriBuilder().path(PATH));
+ try {
+ this.mac = Mac.getInstance(ALGORITHM);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public Session openSession(String appToken) throws FreeboxException {
+ AuthorizationStatus authorization = getSingle(AuthStatus.class);
+
+ try {
+ // Initialize mac with the signing key
+ mac.init(new SecretKeySpec(appToken.getBytes(), mac.getAlgorithm()));
+ // Compute the hmac on input data bytes
+ byte[] rawHmac = mac.doFinal(authorization.challenge().getBytes());
+ // Convert raw bytes to Hex
+ String password = printHexBinary(rawHmac).toLowerCase();
+ return post(new OpenSessionData(APP_ID, password), SessionResponse.class, SESSION);
+ } catch (InvalidKeyException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public void closeSession() throws FreeboxException {
+ post(LOGOUT);
+ }
+
+ public String checkGrantStatus() throws FreeboxException {
+ if (authorize.isEmpty()) {
+ authorize = Optional.of(post(new AuthorizeData(APP_ID, BUNDLE), AuthResponse.class, AUTHORIZE_ACTION));
+ }
+
+ return switch (getSingle(AuthStatus.class, AUTHORIZE_ACTION, authorize.get().trackId).status()) {
+ case PENDING -> "";
+ case GRANTED -> {
+ String appToken = authorize.get().appToken;
+ authorize = Optional.empty();
+ yield appToken;
+ }
+ case TIMEOUT -> throw new FreeboxException("Unable to grant session, delay expired");
+ case DENIED -> throw new FreeboxException("Unable to grant session, access was denied");
+ case UNKNOWN -> throw new FreeboxException("Unable to grant session");
+ };
+ }
+}
--- /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.api.rest;
+
+import java.util.Map;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+import org.openhab.binding.freeboxos.internal.api.rest.MediaReceiverManager.Receiver;
+
+/**
+ * The {@link MediaReceiverManager} is the Java class used to handle api requests related to air media receivers
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class MediaReceiverManager extends ListableRest<Receiver, MediaReceiverManager.ReceiverResponse> {
+ private static final String SUB_PATH = "receivers";
+
+ public static record Receiver(boolean passwordProtected, //
+ Map<MediaType, Boolean> capabilities, //
+ String name // This name is the UPnP name of the host
+ ) {
+ }
+
+ protected static class ReceiverResponse extends Response<Receiver> {
+ }
+
+ public static enum Action {
+ START,
+ STOP,
+ UNKNOWN;
+ }
+
+ public static enum MediaType {
+ VIDEO,
+ PHOTO,
+ AUDIO,
+ SCREEN,
+ UNKNOWN;
+ }
+
+ private static record Request(String password, Action action, MediaType mediaType, @Nullable String media,
+ int position) {
+ }
+
+ public MediaReceiverManager(FreeboxOsSession session, UriBuilder uriBuilder) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, ReceiverResponse.class, uriBuilder.path(SUB_PATH));
+ }
+
+ public @Nullable Receiver getReceiver(String receiverName) throws FreeboxException {
+ return getDevices().stream().filter(rcv -> receiverName.equals(rcv.name())).findFirst().orElse(null);
+ }
+
+ public void sendToReceiver(String receiver, String password, Action action, MediaType type)
+ throws FreeboxException {
+ sendToReceiver(receiver, new Request(password, action, type, null, 0));
+ }
+
+ public void sendToReceiver(String receiver, String password, Action action, MediaType type, String url)
+ throws FreeboxException {
+ sendToReceiver(receiver, new Request(password, action, type, url, 0));
+ }
+
+ private void sendToReceiver(String receiver, Request payload) throws FreeboxException {
+ post(payload, GenericResponse.class, receiver);
+ }
+}
--- /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.api.rest;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+
+/**
+ * The {@link NetShareManager} is the Java class used to handle api requests related to network shares
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class NetShareManager extends RestManager {
+ private static final String PATH = "netshare";
+
+ public NetShareManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, session.getUriBuilder().path(PATH));
+ session.addManager(SambaManager.class, new SambaManager(session, getUriBuilder()));
+ session.addManager(AfpManager.class, new AfpManager(session, getUriBuilder()));
+ }
+}
--- /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.api.rest;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+
+/**
+ * The {@link PhoneManager} is the Java class used to handle api requests related to phone and calls
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class PhoneManager extends ConfigurableRest<PhoneManager.Config, PhoneManager.ConfigResponse> {
+ private static final String DECT_PAGE_ACTION = "dect_page_%s";
+ private static final String FXS_RING_ACTION = "fxs_ring_%s";
+ private static final String PATH = "phone";
+
+ protected class ConfigResponse extends Response<Config> {
+ }
+
+ protected class StatusResponse extends Response<Status> {
+ }
+
+ private static enum NetworkStatus {
+ WORKING,
+ UNKNOWN;
+ }
+
+ public static record Config(NetworkStatus network, boolean dectEcoMode, String dectPin, int dectRingPattern,
+ boolean dectRegistration, boolean dectNemoMode, boolean dectEnabled, boolean dectRingOnOff) {
+ }
+
+ public enum Type {
+ FXS,
+ DECT,
+ UNKNOWN;
+ }
+
+ public static record Status(int id, boolean isRinging, boolean onHook, boolean hardwareDefect, Type type,
+ @Nullable String vendor, int gainRx, int gainTx) {
+
+ public String vendor() {
+ String localVendor = vendor;
+ return localVendor != null ? localVendor : "Unknown";
+ }
+ }
+
+ public PhoneManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.CALLS, ConfigResponse.class, session.getUriBuilder().path(PATH),
+ CONFIG_PATH);
+ }
+
+ public List<Status> getPhoneStatuses() throws FreeboxException {
+ return get(StatusResponse.class, "");
+ }
+
+ public Optional<Status> getStatus(int id) throws FreeboxException {
+ return Optional.ofNullable(getSingle(StatusResponse.class, Integer.toString(id)));
+ }
+
+ public void ringFxs(boolean startIt) throws FreeboxException {
+ post(FXS_RING_ACTION.formatted(startIt ? "start" : "stop"));
+ }
+
+ public void ringDect(boolean startIt) throws FreeboxException {
+ post(DECT_PAGE_ACTION.formatted(startIt ? "start" : "stop"));
+ }
+
+ public void setGainRx(int clientId, int gain) throws FreeboxException {
+ Optional<Status> result = getStatus(clientId);
+ if (result.isPresent()) {
+ Status status = result.get();
+ Status newStatus = new Status(status.id, status.isRinging, status.onHook, status.hardwareDefect,
+ status.type, status.vendor, gain, status.gainTx);
+ put(StatusResponse.class, newStatus, Integer.toString(clientId));
+ }
+ }
+
+ public void setGainTx(int clientId, int gain) throws FreeboxException {
+ Optional<Status> result = getStatus(clientId);
+ if (result.isPresent()) {
+ Status status = result.get();
+ Status newStatus = new Status(status.id, status.isRinging, status.onHook, status.hardwareDefect,
+ status.type, status.vendor, status.gainRx, gain);
+ put(StatusResponse.class, newStatus, Integer.toString(clientId));
+ }
+ }
+
+ public void alternateRing(boolean status) throws FreeboxException {
+ Config config = getConfig();
+ Config newConfig = new Config(config.network, config.dectEcoMode, config.dectPin, config.dectRingPattern,
+ config.dectRegistration, config.dectNemoMode, config.dectEnabled, status);
+ put(ConfigResponse.class, newConfig, CONFIG_PATH);
+ }
+
+ public boolean setStatus(boolean enabled) throws FreeboxException {
+ Config config = getConfig();
+ Config newConfig = new Config(config.network, config.dectEcoMode, config.dectPin, config.dectRingPattern,
+ config.dectRegistration, config.dectNemoMode, enabled, config.dectRingOnOff);
+ return setConfig(newConfig).dectEnabled;
+ }
+}
--- /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.api.rest;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.THING_PLAYER;
+
+import java.time.ZonedDateTime;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.http.HttpMethod;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Metadata.PlaybackState;
+import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Metadata.SubtitleTrack;
+import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Metadata.VideoTrack;
+import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.PlayerContext.PlayerDetails;
+import org.openhab.binding.freeboxos.internal.api.rest.SystemManager.ModelInfo;
+
+import com.google.gson.annotations.SerializedName;
+
+import inet.ipaddr.mac.MACAddress;
+
+/**
+ * The {@link PlayerManager} is the Java class used to handle api requests related to player
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class PlayerManager extends ListableRest<PlayerManager.Player, PlayerManager.PlayerResponse> {
+ private static final String STATUS_PATH = "status";
+
+ protected static class PlayerResponse extends Response<Player> {
+ }
+
+ public static enum DeviceModel {
+ FBX7HD_DELTA, // Freebox Player Devialet
+ TBX8AM, // Player Pop
+ FBX6HD,
+ FBX6LC,
+ FBX6LCV2,
+ FBX7HD,
+ FBX7HD_ONE,
+ FBX8AM,
+ UNKNOWN;
+ }
+
+ public static record Player(MACAddress mac, StbType stbType, int id, ZonedDateTime lastTimeReachable,
+ boolean apiAvailable, String deviceName, DeviceModel deviceModel, boolean reachable, String uid,
+ @Nullable String apiVersion, List<String> lanGids) {
+ private static enum StbType {
+ STB_ANDROID,
+ STB_V6,
+ STB_V7,
+ STB_V8,
+ UNKNOWN;
+ }
+
+ /**
+ * @return a string like eg: '17/api/v8'
+ */
+ private @Nullable String baseUrl() {
+ String api = apiVersion;
+ return api != null ? "%d/api/v%s/".formatted(id, api.split("\\.")[0]) : null;
+ }
+ }
+
+ private static class StatusResponse extends Response<Status> {
+ }
+
+ public static enum PowerState {
+ STANDBY,
+ RUNNING,
+ UNKNOWN;
+ }
+
+ public static record Status(PowerState powerState, StatusInformation player,
+ @Nullable ForegroundApp foregroundApp) {
+
+ public @Nullable ForegroundApp foregroundApp() {
+ return foregroundApp;
+ }
+ }
+
+ public static record ForegroundApp(int packageId, @Nullable String curlUrl, @Nullable Object context,
+ @SerializedName(value = "package") String _package) {
+ }
+
+ private static record StatusInformation(String name, ZonedDateTime lastActivity) {
+ }
+
+ private static class ConfigurationResponse extends Response<Configuration> {
+ }
+
+ public static record Configuration(String boardName, boolean configured, String firmwareVersion,
+ @Nullable ModelInfo modelInfo, String serial, String uptime, long uptimeVal) {
+ }
+
+ private enum MediaState {
+ READY,
+ UNKNOWN;
+ }
+
+ private static record AudioTrack(int bitrate, @SerializedName("channelCount") int channelCount,
+ @Nullable String codec, @SerializedName("codecId") @Nullable String codecId, @Nullable String language,
+ @SerializedName("metadataId") @Nullable String metadataId, int pid, int samplerate, long uid) {
+ }
+
+ private static enum Type {
+ NORMAL,
+ HEARINGIMPAIRED,
+ UNKNOWN;
+ }
+
+ protected static record Metadata(@Nullable String album,
+ @SerializedName("albumArtist") @Nullable String albumArtist, @Nullable String artist,
+ @Nullable String author, int bpm, @Nullable String comment, boolean compilation, @Nullable String composer,
+ @Nullable String container, @Nullable String copyright, long date,
+ @SerializedName("discId") @Nullable String discId, @SerializedName("discNumber") int discNumber,
+ @SerializedName("discTotal") int discTotal, @Nullable String genre,
+ @SerializedName("musicbrainzDiscId") @Nullable String musicbrainzDiscId, @Nullable String performer,
+ @Nullable String title, @SerializedName("trackNumber") int trackNumber,
+ @SerializedName("trackTotal") int trackTotal, @Nullable String url) {
+
+ protected static enum PlaybackState {
+ PLAY,
+ PAUSE,
+ UNKNOWN;
+ }
+
+ protected static record SubtitleTrack(@Nullable String codec, @Nullable String language, @Nullable String pid,
+ Type type, @Nullable String uid) {
+ }
+
+ protected static record VideoTrack(int bitrate, @Nullable String codec, int height, int pid, int uid,
+ int width) {
+ }
+ }
+
+ public static record PlayerContext(@Nullable PlayerDetails player) {
+ public static record PlayerDetails(@SerializedName("audioIndex") int audioIndex,
+ @SerializedName("audioList") List<AudioTrack> audioList, @SerializedName("curPos") long curPos,
+ int duration, @SerializedName("livePos") long livePos, @SerializedName("maxPos") long maxPos,
+ @SerializedName("mediaState") MediaState mediaState, @Nullable Metadata metadata,
+ @SerializedName("minPos") long minPos, @SerializedName("playbackState") PlaybackState playbackState,
+ long position, @Nullable String source, @SerializedName("subtitleIndex") int subtitleIndex,
+ @SerializedName("subtitleList") List<SubtitleTrack> subtitleList,
+ @SerializedName("videoIndex") int videoIndex, @SerializedName("videoList") List<VideoTrack> videoList) {
+ }
+ }
+
+ private static enum BouquetType {
+ ADSL,
+ UNKNOWN;
+ }
+
+ private static enum ChannelType {
+ REGULAR,
+ UNKNOWN;
+ }
+
+ private static record Service(long id, @Nullable String name,
+ @SerializedName("qualityLabel") @Nullable String qualityLabel,
+ @SerializedName("qualityName") @Nullable String qualityName, @SerializedName("sortInfo") int sortInfo,
+ @SerializedName("typeLabel") @Nullable String typeLabel,
+ @SerializedName("typeName") @Nullable String typeName, @Nullable String url) {
+ }
+
+ private static record Channel(@SerializedName("bouquetId") long bouquetId,
+ @SerializedName("bouquetName") @Nullable String bouquetName,
+ @SerializedName("bouquetType") BouquetType bouquetType,
+ @SerializedName("channelName") @Nullable String channelName,
+ @SerializedName("channelNumber") int channelNumber,
+ @SerializedName("channelSubNumber") int channelSubNumber,
+ @SerializedName("channelType") ChannelType channelType,
+ @SerializedName("channelUuid") @Nullable String channelUuid,
+ @SerializedName("currentServiceIndex") int currentServiceIndex,
+ @SerializedName("isTimeShifting") boolean isTimeShifting, List<Service> services,
+ @SerializedName("videoIsVisible") boolean videoIsVisible) {
+ }
+
+ public static record TvContext(@Nullable Channel channel, @Nullable PlayerDetails player) {
+ }
+
+ private final Map<Integer, String> subPaths = new HashMap<>();
+
+ public PlayerManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.PLAYER, PlayerResponse.class,
+ session.getUriBuilder().path(THING_PLAYER));
+ getDevices().stream().filter(Player::apiAvailable).forEach(player -> {
+ String baseUrl = player.baseUrl();
+ if (baseUrl != null) {
+ subPaths.put(player.id, baseUrl);
+ }
+ });
+ }
+
+ public Status getPlayerStatus(int id) throws FreeboxException {
+ return getSingle(StatusResponse.class, subPaths.get(id), STATUS_PATH);
+ }
+
+ // The player API does not allow to directly request a given player like others api parts
+ @Override
+ public Player getDevice(int id) throws FreeboxException {
+ return getDevices().stream().filter(player -> player.id == id).findFirst().orElse(null);
+ }
+
+ public Configuration getConfig(int id) throws FreeboxException {
+ return getSingle(ConfigurationResponse.class, subPaths.get(id), SYSTEM_PATH);
+ }
+
+ public void sendKey(String ip, String code, String key, boolean longPress, int count) {
+ UriBuilder uriBuilder = UriBuilder.fromPath("pub").scheme("http").host(ip).path("remote_control");
+ uriBuilder.queryParam("code", code).queryParam("key", key);
+ if (longPress) {
+ uriBuilder.queryParam("long", true);
+ }
+ if (count > 1) {
+ uriBuilder.queryParam("repeat", count);
+ }
+ try {
+ session.execute(uriBuilder.build(), HttpMethod.GET, GenericResponse.class, null);
+ } catch (FreeboxException ignore) {
+ // This call does not return anything, we can safely ignore
+ }
+ }
+
+ public void reboot(int id) throws FreeboxException {
+ post(subPaths.get(id), SYSTEM_PATH, REBOOT_ACTION);
+ }
+}
--- /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.api.rest;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import java.time.Duration;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.HostsResponse;
+import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.LanHost;
+
+import inet.ipaddr.mac.MACAddress;
+
+/**
+ * The {@link RepeaterManager} is the Java class used to handle api requests related to repeater
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class RepeaterManager extends ListableRest<RepeaterManager.Repeater, RepeaterManager.RepeaterResponse> {
+
+ protected static class RepeaterResponse extends Response<Repeater> {
+ }
+
+ protected static class RepeaterLedResponse extends Response<RepeaterLed> {
+ }
+
+ public static record RepeaterLed(int id, boolean ledActivated) {
+ }
+
+ private static enum Connection {
+ CONNECTED,
+ DISCONNECTED,
+ UNKNOWN;
+ }
+
+ private static enum Status {
+ STARTING,
+ RUNNING,
+ REBOOTING,
+ UPDATING,
+ REBOOT_FAILURE,
+ UPDATE_FAILURE,
+ UNKNOWN;
+ }
+
+ public static enum Model {
+ FBXWMR, // Répéteur Wifi
+ UNKNOWN;
+ }
+
+ public static record Repeater(int id, boolean ledActivated, boolean enabled, MACAddress mainMac,
+ Connection connection, ZonedDateTime bootTime, Status status, String name, String sn, String apiVer,
+ ZonedDateTime lastSeen, Model model, String firmwareVersion) {
+
+ public long getUptimeVal() {
+ return Duration.between(bootTime, ZonedDateTime.now()).toSeconds();
+ }
+ }
+
+ public RepeaterManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, RepeaterResponse.class,
+ session.getUriBuilder().path(THING_REPEATER));
+ }
+
+ public List<LanHost> getRepeaterHosts(int id) throws FreeboxException {
+ return get(HostsResponse.class, Integer.toString(id), THING_HOST);
+ }
+
+ public synchronized List<LanHost> getHosts() throws FreeboxException {
+ List<LanHost> hosts = new ArrayList<>();
+ for (Repeater rep : getDevices()) {
+ if (Connection.CONNECTED.equals(rep.connection)) {
+ hosts.addAll(getRepeaterHosts(rep.id));
+ }
+ }
+ return hosts;
+ }
+
+ public Optional<LanHost> getHost(MACAddress mac) throws FreeboxException {
+ return getHosts().stream().filter(host -> host.getMac().equals(mac)).findFirst();
+ }
+
+ public void reboot(int id) throws FreeboxException {
+ post(Integer.toString(id), REBOOT_ACTION);
+ }
+
+ public Optional<RepeaterLed> led(int id, boolean enable) throws FreeboxException {
+ RepeaterLed result = put(RepeaterLedResponse.class, new RepeaterLed(id, enable), Integer.toString(id));
+ return Optional.ofNullable(result);
+ }
+}
--- /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.api.rest;
+
+import static org.eclipse.jetty.http.HttpMethod.*;
+
+import java.net.URI;
+import java.util.List;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.PermissionException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+
+/**
+ * Base class for the various rest managers available through the API
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class RestManager {
+ protected static final String REBOOT_ACTION = "reboot";
+ protected static final String SYSTEM_PATH = "system";
+
+ protected class GenericResponse extends Response<Object> {
+ }
+
+ private final UriBuilder uriBuilder;
+ protected final FreeboxOsSession session;
+
+ public RestManager(FreeboxOsSession session, LoginManager.Permission required, UriBuilder uri)
+ throws FreeboxException {
+ this.uriBuilder = uri;
+ this.session = session;
+ if (required != LoginManager.Permission.NONE && !session.hasPermission(required)) {
+ throw new PermissionException(required, "Permission missing: %s", required.toString());
+ }
+ }
+
+ protected UriBuilder getUriBuilder() {
+ return uriBuilder.clone();
+ }
+
+ private URI buildUri(String... pathElements) {
+ UriBuilder localBuilder = getUriBuilder();
+ for (String path : pathElements) {
+ localBuilder.path(path);
+ }
+ return localBuilder.build();
+ }
+
+ // Returns the first and supposed only element from the list. Presence of this element is expected and mandatory
+ private <F> F controlSingleton(List<F> result) {
+ if (result.size() == 1) {
+ return result.get(0);
+ }
+ throw new IllegalArgumentException("Result is empty or not singleton");
+ }
+
+ protected <F, T extends Response<F>> List<F> get(Class<T> clazz, String... pathElements) throws FreeboxException {
+ return session.execute(buildUri(pathElements), GET, clazz, null);
+ }
+
+ protected <F, T extends Response<F>> F getSingle(Class<T> clazz, String... pathElements) throws FreeboxException {
+ return controlSingleton(get(clazz, pathElements));
+ }
+
+ protected <F, T extends Response<F>> F post(Object payload, Class<T> clazz, String... pathElements)
+ throws FreeboxException {
+ return controlSingleton(session.execute(buildUri(pathElements), POST, clazz, payload));
+ }
+
+ protected void post(String... pathElements) throws FreeboxException {
+ session.execute(buildUri(pathElements), POST, GenericResponse.class, null);
+ }
+
+ protected <F, T extends Response<F>> F put(Class<T> clazz, F payload, String... pathElements)
+ throws FreeboxException {
+ return controlSingleton(session.execute(buildUri(pathElements), PUT, clazz, payload));
+ }
+
+ protected <F, T extends Response<F>> void put(F payload, String... pathElements) throws FreeboxException {
+ session.execute(buildUri(pathElements), PUT, GenericResponse.class, payload);
+ }
+}
--- /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.api.rest;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+
+/**
+ * The {@link SambaManager} is the Java class used to handle api requests related to Samba shares
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class SambaManager extends ConfigurableRest<SambaManager.Samba, SambaManager.ConfigResponse> {
+ private static final String PATH = "samba";
+
+ protected static class ConfigResponse extends Response<Samba> {
+ }
+
+ public static record Samba(boolean fileShareEnabled, boolean printShareEnabled, boolean logonEnabled,
+ @Nullable String logonUser, @Nullable String logonPassword, @Nullable String workgroup,
+ boolean smbv2Enabled) {
+ }
+
+ public SambaManager(FreeboxOsSession session, UriBuilder uriBuilder) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, ConfigResponse.class, uriBuilder.path(PATH), null);
+ }
+
+ public boolean setFileShare(boolean enable) throws FreeboxException {
+ Samba config = getConfig();
+ Samba newConfig = new Samba(enable, config.printShareEnabled, config.logonEnabled, config.logonUser,
+ config.logonPassword, config.workgroup, config.smbv2Enabled);
+ return setConfig(newConfig).fileShareEnabled();
+ }
+
+ public boolean setPrintShare(boolean enable) throws FreeboxException {
+ Samba config = getConfig();
+ Samba newConfig = new Samba(config.fileShareEnabled, enable, config.logonEnabled, config.logonUser,
+ config.logonPassword, config.workgroup, config.smbv2Enabled);
+ return setConfig(newConfig).printShareEnabled();
+ }
+}
--- /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.api.rest;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+import org.openhab.binding.freeboxos.internal.api.rest.FreeboxOsSession.BoxModel;
+
+import inet.ipaddr.mac.MACAddress;
+
+/**
+ * The {@link SystemManager} is the Java class used to handle api requests related to system
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class SystemManager extends ConfigurableRest<SystemManager.Config, SystemManager.ConfigurationResponse> {
+
+ protected static class ConfigurationResponse extends Response<Config> {
+ }
+
+ public static record Sensor(String id, String name, int value) {
+ public enum SensorKind {
+ FAN,
+ TEMP,
+ UNKNOWN;
+ }
+
+ public SensorKind getKind() {
+ String[] elements = id.split("_");
+ if (elements.length > 0) {
+ String kind = elements[0].replaceAll("\\d", "").toUpperCase();
+ try {
+ return SensorKind.valueOf(kind);
+ } catch (IllegalArgumentException ignore) { // returning UNKNOWN
+ }
+ }
+ return SensorKind.UNKNOWN;
+ }
+ }
+
+ private static record Expansion(int slot, boolean probeDone, boolean present, boolean supported, String bundle,
+ Type type) {
+ private static enum Type {
+ UNKNOWN, // unknown module
+ DSL_LTE, // xDSL + LTE
+ DSL_LTE_EXTERNAL_ANTENNAS, // xDSL + LTE with external antennas switch
+ FTTH_P2P, // FTTH P2P
+ FTTH_PON, // FTTH PON
+ SECURITY; // Security module
+ }
+ }
+
+ public static record ModelInfo(BoxModel name, String prettyName, boolean hasExpansions, boolean hasLanSfp,
+ boolean hasDect, boolean hasHomeAutomation, boolean hasFemtocellExp, boolean hasFixedFemtocell,
+ boolean hasVm) {
+ }
+
+ public static record Config(String firmwareVersion, MACAddress mac, String serial, String uptime, long uptimeVal,
+ String boardName, boolean boxAuthenticated, DiskStatus diskStatus, String userMainStorage,
+ List<Sensor> sensors, ModelInfo modelInfo, List<Sensor> fans, List<Expansion> expansions) {
+ private static enum DiskStatus {
+ NOT_DETECTED,
+ DISABLED,
+ INITIALIZING,
+ ERROR,
+ ACTIVE,
+ UNKNOWN;
+ }
+ }
+
+ public SystemManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, ConfigurationResponse.class,
+ session.getUriBuilder().path(SYSTEM_PATH), null);
+ }
+
+ public void reboot() throws FreeboxException {
+ post(REBOOT_ACTION);
+ }
+}
--- /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.api.rest;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+
+/**
+ * The {@link UPnPAVManager} is the Java class used to handle api requests related to UPnP AV
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class UPnPAVManager extends ConfigurableRest<UPnPAVManager.Config, UPnPAVManager.ConfigResponse> {
+ private static final String PATH = "upnpav";
+
+ protected static class ConfigResponse extends Response<Config> {
+ }
+
+ protected static record Config(boolean enabled) {
+ }
+
+ public UPnPAVManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, ConfigResponse.class, session.getUriBuilder().path(PATH),
+ CONFIG_PATH);
+ }
+
+ public boolean getStatus() throws FreeboxException {
+ return getConfig().enabled();
+ }
+
+ public boolean setStatus(boolean enabled) throws FreeboxException {
+ return setConfig(new Config(enabled)).enabled();
+ }
+}
--- /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.api.rest;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.THING_VM;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+
+import inet.ipaddr.mac.MACAddress;
+
+/**
+ * The {@link VmManager} is the Java class used to handle api requests related to virtual machines
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class VmManager extends ListableRest<VmManager.VirtualMachine, VmManager.VirtualMachineResponse> {
+
+ protected class VirtualMachineResponse extends Response<VirtualMachine> {
+ }
+
+ public static enum Status {
+ STOPPED,
+ RUNNING,
+ UNKNOWN;
+ }
+
+ public static record VirtualMachine(int id, String name, MACAddress mac, Status status) {
+ }
+
+ public VmManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.VM, VirtualMachineResponse.class,
+ session.getUriBuilder().path(THING_VM));
+ }
+
+ public void power(int vmId, boolean startIt) throws FreeboxException {
+ post(Integer.toString(vmId), startIt ? "start" : "powerbutton");
+ }
+}
--- /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.api.rest;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketListener;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.openhab.binding.freeboxos.internal.api.ApiHandler;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.LanHost;
+import org.openhab.binding.freeboxos.internal.api.rest.VmManager.VirtualMachine;
+import org.openhab.binding.freeboxos.internal.handler.HostHandler;
+import org.openhab.binding.freeboxos.internal.handler.VmHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonElement;
+
+import inet.ipaddr.mac.MACAddress;
+
+/**
+ * The {@link WebSocketManager} is the Java class register to the websocket server and handle notifications
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class WebSocketManager extends RestManager implements WebSocketListener {
+ private static final String HOST_UNREACHABLE = "lan_host_l3addr_unreachable";
+ private static final String HOST_REACHABLE = "lan_host_l3addr_reachable";
+ private static final String VM_CHANGED = "vm_state_changed";
+ private static final Register REGISTRATION = new Register("register",
+ List.of(VM_CHANGED, HOST_REACHABLE, HOST_UNREACHABLE));
+ private static final String WS_PATH = "ws/event";
+
+ private final Logger logger = LoggerFactory.getLogger(WebSocketManager.class);
+ private final Map<MACAddress, HostHandler> lanHosts = new HashMap<>();
+ private final Map<Integer, VmHandler> vms = new HashMap<>();
+ private final ApiHandler apiHandler;
+
+ private volatile @Nullable Session wsSession;
+
+ private record Register(String action, List<String> events) {
+
+ }
+
+ public WebSocketManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, session.getUriBuilder().path(WS_PATH));
+ this.apiHandler = session.getApiHandler();
+ }
+
+ private static enum Action {
+ REGISTER,
+ NOTIFICATION,
+ UNKNOWN;
+ }
+
+ private static record WebSocketResponse(boolean success, Action action, String event, String source,
+ @Nullable JsonElement result) {
+ public String getEvent() {
+ return source + "_" + event;
+ }
+ }
+
+ public void openSession(@Nullable String sessionToken) throws FreeboxException {
+ WebSocketClient client = new WebSocketClient(apiHandler.getHttpClient());
+ URI uri = getUriBuilder().scheme(getUriBuilder().build().getScheme().contains("s") ? "wss" : "ws").build();
+ ClientUpgradeRequest request = new ClientUpgradeRequest();
+ request.setHeader(ApiHandler.AUTH_HEADER, sessionToken);
+
+ try {
+ client.start();
+ client.connect(this, uri, request);
+ } catch (Exception e) {
+ throw new FreeboxException(e, "Exception connecting websocket client");
+ }
+ }
+
+ public void closeSession() {
+ logger.debug("Awaiting closure from remote");
+ Session localSession = wsSession;
+ if (localSession != null) {
+ localSession.close();
+ }
+ }
+
+ @Override
+ public void onWebSocketConnect(@NonNullByDefault({}) Session wsSession) {
+ this.wsSession = wsSession;
+ logger.debug("Websocket connection establisehd");
+ try {
+ wsSession.getRemote().sendString(apiHandler.serialize(REGISTRATION));
+ } catch (IOException e) {
+ logger.warn("Error connecting to websocket: {}", e.getMessage());
+ }
+ }
+
+ @Override
+ public void onWebSocketText(@NonNullByDefault({}) String message) {
+ Session localSession = wsSession;
+ if (message.toLowerCase(Locale.US).contains("bye") && localSession != null) {
+ localSession.close(StatusCode.NORMAL, "Thanks");
+ return;
+ }
+
+ WebSocketResponse result = apiHandler.deserialize(WebSocketResponse.class, message);
+ if (result.success) {
+ switch (result.action) {
+ case REGISTER:
+ logger.debug("Event registration successfull");
+ break;
+ case NOTIFICATION:
+ handleNotification(result);
+ break;
+ default:
+ logger.warn("Unhandled notification received: {}", result.action);
+ }
+ }
+ }
+
+ private void handleNotification(WebSocketResponse result) {
+ JsonElement json = result.result;
+ if (json != null) {
+ switch (result.getEvent()) {
+ case VM_CHANGED:
+ VirtualMachine vm = apiHandler.deserialize(VirtualMachine.class, json.toString());
+ logger.debug("Received notification for VM {}", vm.id());
+ VmHandler vmHandler = vms.get(vm.id());
+ if (vmHandler != null) {
+ vmHandler.updateVmChannels(vm);
+ }
+ break;
+ case HOST_UNREACHABLE, HOST_REACHABLE:
+ LanHost host = apiHandler.deserialize(LanHost.class, json.toString());
+ MACAddress mac = host.getMac();
+ logger.debug("Received notification for LanHost {}", mac.toColonDelimitedString());
+ HostHandler hostHandler = lanHosts.get(mac);
+ if (hostHandler != null) {
+ hostHandler.updateConnectivityChannels(host);
+ }
+ break;
+ default:
+ logger.warn("Unhandled event received: {}", result.getEvent());
+ }
+ } else {
+ logger.warn("Empty json element in notification");
+ }
+ }
+
+ @Override
+ public void onWebSocketClose(int statusCode, @NonNullByDefault({}) String reason) {
+ logger.debug("Socket Closed: [{}] - reason {}", statusCode, reason);
+ this.wsSession = null;
+ }
+
+ @Override
+ public void onWebSocketError(@NonNullByDefault({}) Throwable cause) {
+ logger.warn("Error on websocket: {}", cause.getMessage());
+ }
+
+ @Override
+ public void onWebSocketBinary(byte @Nullable [] payload, int offset, int len) {
+ /* do nothing */
+ }
+
+ public void registerListener(MACAddress mac, HostHandler hostHandler) {
+ lanHosts.put(mac, hostHandler);
+ }
+
+ public void unregisterListener(MACAddress mac) {
+ lanHosts.remove(mac);
+ }
+
+ public void registerVm(int clientId, VmHandler vmHandler) {
+ vms.put(clientId, vmHandler);
+ }
+
+ public void unregisterVm(int clientId) {
+ vms.remove(clientId);
+ }
+}
--- /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.api.rest;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.Response;
+
+/**
+ * The {@link WifiManager} is the Java class used to handle api requests related to wifi
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class WifiManager extends ConfigurableRest<WifiManager.Config, WifiManager.ConfigResponse> {
+ private static final String PATH = "wifi";
+
+ protected static class ConfigResponse extends Response<Config> {
+ }
+
+ protected static record Config(boolean enabled) {
+ }
+
+ public WifiManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, ConfigResponse.class, session.getUriBuilder().path(PATH),
+ CONFIG_PATH);
+ session.addManager(APManager.class, new APManager(session, getUriBuilder()));
+ }
+
+ public boolean getStatus() throws FreeboxException {
+ return getConfig().enabled();
+ }
+
+ public boolean setStatus(boolean enabled) throws FreeboxException {
+ return setConfig(new Config(enabled)).enabled();
+ }
+}
--- /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;
+
+/**
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class ApiConsumerConfiguration {
+ public static final String REFRESH_INTERVAL = "refreshInterval";
+
+ public int refreshInterval = 30;
+ public String password = "";
+ public boolean acceptAllMp3 = true;
+}
--- /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;
+
+/**
+ * The {@link ClientConfiguration} is responsible for holding configuration informations for a controllable client of
+ * the API
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class ClientConfiguration extends HostConfiguration {
+ public static final String ID = "id";
+
+ public int id = 1;
+}
--- /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 javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxTlsCertificateProvider;
+
+/**
+ * The {@link FreeboxOsConfiguration} is responsible for holding configuration informations needed to access the Freebox
+ * API
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class FreeboxOsConfiguration {
+ public static final String API_DOMAIN = "apiDomain";
+ public static final String APP_TOKEN = "appToken";
+ public static final String HTTPS_PORT = "httpsPort";
+ public static final String HTTPS_AVAILABLE = "httpsAvailable";
+
+ private String apiDomain = FreeboxTlsCertificateProvider.DEFAULT_NAME;
+ public String appToken = "";
+ public boolean discoverNetDevice;
+
+ private int httpsPort = 15682;
+ private boolean httpsAvailable;
+
+ private String getScheme() {
+ return httpsAvailable ? "https" : "http";
+ }
+
+ private int getPort() {
+ return httpsAvailable ? httpsPort : 80;
+ }
+
+ public UriBuilder getUriBuilder(String path) {
+ return UriBuilder.fromPath("/").scheme(getScheme()).port(getPort()).host(apiDomain).path(path).clone();
+ }
+}
--- /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 static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.rest.FreeplugManager.Freeplug;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingUID;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import inet.ipaddr.mac.MACAddress;
+
+/**
+ * The {@link FreeplugConfigurationBuilder} is responsible for holding configuration informations associated to a
+ * Freeplug
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class FreeplugConfigurationBuilder {
+ private static final FreeplugConfigurationBuilder BUILDER_INSTANCE = new FreeplugConfigurationBuilder();
+
+ private final Logger logger = LoggerFactory.getLogger(FreeplugConfigurationBuilder.class);
+
+ private FreeplugConfigurationBuilder() {
+ }
+
+ public static FreeplugConfigurationBuilder getInstance() {
+ return BUILDER_INSTANCE;
+ }
+
+ public DiscoveryResultBuilder configure(ThingUID bridgeUID, Freeplug plug) {
+ MACAddress mac = plug.id();
+ String uid = mac.toHexString(false);
+ ThingUID thingUID = new ThingUID(THING_TYPE_FREEPLUG, bridgeUID, uid);
+
+ logger.debug("Adding new {} {} to inbox", THING_FREEPLUG, thingUID);
+
+ return DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
+ .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS)
+ .withLabel("%s (%s) %s".formatted(THING_FREEPLUG, plug.netRole().name(), uid))
+ .withProperty(Thing.PROPERTY_MAC_ADDRESS, mac.toColonDelimitedString());
+ }
+}
--- /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 inet.ipaddr.MACAddressString;
+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
+ */
+@NonNullByDefault
+public class HostConfiguration extends ApiConsumerConfiguration {
+ private String macAddress = "";
+
+ public MACAddress getMac() {
+ return new MACAddressString(macAddress).getAddress();
+ }
+}
--- /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;
+
+/**
+ * The {@link LandlineConfiguration} is responsible for holding
+ * configuration informations associated to a Freebox Phone thing type
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class LandlineConfiguration extends ApiConsumerConfiguration {
+ public int id = 1;
+
+ LandlineConfiguration() {
+ refreshInterval = 2;
+ }
+}
--- /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 java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Category;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.HomeNode;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.thing.ThingUID;
+
+/**
+ * The {@link NodeConfigurationBuilder} is responsible for holding configuration informations associated to a Freebox
+ * Home thing type
+ *
+ * @author ben12 - Initial contribution
+ */
+@NonNullByDefault
+public class NodeConfigurationBuilder {
+ private static final NodeConfigurationBuilder BUILDER_INSTANCE = new NodeConfigurationBuilder();
+
+ private NodeConfigurationBuilder() {
+ }
+
+ public static NodeConfigurationBuilder getInstance() {
+ return BUILDER_INSTANCE;
+ }
+
+ public Optional<DiscoveryResultBuilder> configure(ThingUID bridgeUID, HomeNode node) {
+ if (node.category() == Category.UNKNOWN) {
+ return Optional.empty();
+ }
+ ThingUID thingUID = new ThingUID(node.category().getThingTypeUID(), bridgeUID, Integer.toString(node.id()));
+ DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(thingUID);
+ discoveryResultBuilder.withProperty(ClientConfiguration.ID, node.id()).withLabel(node.label())
+ .withRepresentationProperty(ClientConfiguration.ID).withBridge(bridgeUID);
+ return Optional.of(discoveryResultBuilder);
+ }
+}
--- /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 static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager.Status;
+import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager.Type;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.thing.ThingUID;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link PhoneConfigurationBuilder} is responsible for holding configuration informations associated the phone
+ * lines (DECT and FXS / landline)
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class PhoneConfigurationBuilder {
+ private static final PhoneConfigurationBuilder BUILDER_INSTANCE = new PhoneConfigurationBuilder();
+
+ private final Logger logger = LoggerFactory.getLogger(PhoneConfigurationBuilder.class);
+
+ private PhoneConfigurationBuilder() {
+ }
+
+ public static PhoneConfigurationBuilder getInstance() {
+ return BUILDER_INSTANCE;
+ }
+
+ public DiscoveryResultBuilder configure(ThingUID bridgeUID, Status config) {
+ ThingUID thingUID = new ThingUID(Type.DECT.equals(config.type()) ? THING_TYPE_DECT : THING_TYPE_FXS, bridgeUID,
+ Integer.toString(config.id()));
+
+ logger.debug("Adding new Freebox Phone {} to inbox", thingUID);
+
+ return DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
+ .withProperty(ClientConfiguration.ID, config.id()).withLabel(config.type().name())
+ .withRepresentationProperty(ClientConfiguration.ID);
+ }
+}
--- /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;
+
+/**
+ * The {@link PlayerConfiguration} is responsible for holding configuration informations needed to access/poll the
+ * freebox player
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class PlayerConfiguration extends ClientConfiguration {
+ public static final String REMOTE_CODE = "remoteCode";
+ public String remoteCode = "";
+}
--- /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.console;
+
+import static org.openhab.binding.freeboxos.internal.config.FreeboxOsConfiguration.APP_TOKEN;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants;
+import org.openhab.binding.freeboxos.internal.handler.FreeboxOsHandler;
+import org.openhab.core.io.console.Console;
+import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
+import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingRegistry;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * The {@link FreeboxOsCommandExtension} is responsible for handling console commands
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+
+@NonNullByDefault
+@Component(service = ConsoleCommandExtension.class)
+public class FreeboxOsCommandExtension extends AbstractConsoleCommandExtension {
+
+ private final ThingRegistry thingRegistry;
+
+ @Activate
+ public FreeboxOsCommandExtension(final @Reference ThingRegistry thingRegistry) {
+ super(FreeboxOsBindingConstants.BINDING_ID, "Interact with the Freebox OS binding.");
+ this.thingRegistry = thingRegistry;
+ }
+
+ @Override
+ public void execute(String[] args, Console console) {
+ if (args.length == 2) {
+ Thing thing = null;
+ try {
+ ThingUID thingUID = new ThingUID(args[0]);
+ thing = thingRegistry.get(thingUID);
+ } catch (IllegalArgumentException e) {
+ thing = null;
+ }
+ ThingHandler thingHandler = null;
+ FreeboxOsHandler handler = null;
+ if (thing != null) {
+ thingHandler = thing.getHandler();
+ if (thingHandler instanceof FreeboxOsHandler) {
+ handler = (FreeboxOsHandler) thingHandler;
+ }
+ }
+ if (thing == null) {
+ console.println("Bad thing id '" + args[0] + "'");
+ printUsage(console);
+ } else if (thingHandler == null) {
+ console.println("No handler initialized for the thing id '" + args[0] + "'");
+ printUsage(console);
+ } else if (handler == null) {
+ console.println("'" + args[0] + "' is not a freebox bridge id");
+ printUsage(console);
+ } else {
+ switch (args[1]) {
+ case APP_TOKEN:
+ String token = handler.getConfiguration().appToken;
+ console.println("Your application token is " + (token.isEmpty() ? "undefined" : token));
+ break;
+ default:
+ printUsage(console);
+ break;
+ }
+ }
+ } else {
+ printUsage(console);
+ }
+ }
+
+ @Override
+ public List<String> getUsages() {
+ return Arrays.asList(buildCommandUsage(String.format("<bridgeUID> %s show the application token", APP_TOKEN)));
+ }
+}
--- /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.discovery;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+import static org.openhab.binding.freeboxos.internal.config.FreeboxOsConfiguration.*;
+
+import java.util.Set;
+
+import javax.jmdns.ServiceInfo;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link ApiDiscoveryParticipant} is responsible for discovering the various servers flavors of bridges thing using
+ * mDNS discovery service
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@Component
+@NonNullByDefault
+public class ApiDiscoveryParticipant implements MDNSDiscoveryParticipant {
+ private static final String DOMAIN_PROPERTY = "api_domain";
+ private static final String PORT_PROPERTY = "https_port";
+ private static final String HTTPS_PROPERTY = "https_available";
+
+ private final Logger logger = LoggerFactory.getLogger(ApiDiscoveryParticipant.class);
+
+ @Override
+ public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
+ return BRIDGE_TYPE_UIDS;
+ }
+
+ @Override
+ public String getServiceType() {
+ return "_fbx-api._tcp.local.";
+ }
+
+ @Override
+ public @Nullable DiscoveryResult createResult(ServiceInfo service) {
+ logger.debug("createResult ServiceInfo: {}", service);
+ ThingUID thingUID = getThingUID(service);
+ return thingUID != null
+ ? DiscoveryResultBuilder.create(thingUID).withLabel("Bridge Freebox OS")
+ .withRepresentationProperty(API_DOMAIN)
+ .withProperty(HTTPS_AVAILABLE, "1".equals(service.getPropertyString(HTTPS_PROPERTY)))
+ .withProperty(HTTPS_PORT, service.getPropertyString(PORT_PROPERTY))
+ .withProperty(API_DOMAIN, service.getPropertyString(DOMAIN_PROPERTY)).build()
+ : null;
+ }
+
+ @Override
+ public @Nullable ThingUID getThingUID(ServiceInfo service) {
+ String domain = service.getPropertyString(DOMAIN_PROPERTY);
+ if (domain != null) {
+ String[] elements = domain.split("\\.");
+ if (elements.length > 0) {
+ return new ThingUID(BRIDGE_TYPE_API, elements[0]);
+ }
+ }
+ return null;
+ }
+}
--- /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.discovery;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.PermissionException;
+import org.openhab.binding.freeboxos.internal.api.rest.APManager;
+import org.openhab.binding.freeboxos.internal.api.rest.APManager.Station;
+import org.openhab.binding.freeboxos.internal.api.rest.FreeplugManager;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
+import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager;
+import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.LanHost;
+import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager;
+import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager.Status;
+import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager;
+import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Player;
+import org.openhab.binding.freeboxos.internal.api.rest.RepeaterManager;
+import org.openhab.binding.freeboxos.internal.api.rest.RepeaterManager.Repeater;
+import org.openhab.binding.freeboxos.internal.api.rest.SystemManager;
+import org.openhab.binding.freeboxos.internal.api.rest.SystemManager.Config;
+import org.openhab.binding.freeboxos.internal.api.rest.VmManager;
+import org.openhab.binding.freeboxos.internal.config.ClientConfiguration;
+import org.openhab.binding.freeboxos.internal.config.FreeplugConfigurationBuilder;
+import org.openhab.binding.freeboxos.internal.config.NodeConfigurationBuilder;
+import org.openhab.binding.freeboxos.internal.config.PhoneConfigurationBuilder;
+import org.openhab.binding.freeboxos.internal.handler.FreeboxOsHandler;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import inet.ipaddr.mac.MACAddress;
+
+/**
+ * The {@link FreeboxOsDiscoveryService} is responsible for discovering all things
+ * except the Freebox API thing itself
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class FreeboxOsDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
+ private static final int DISCOVERY_TIME_SECONDS = 10;
+ private static final int BACKGROUND_SCAN_REFRESH_MINUTES = 1;
+
+ private final Logger logger = LoggerFactory.getLogger(FreeboxOsDiscoveryService.class);
+
+ private Optional<ScheduledFuture<?>> backgroundFuture = Optional.empty();
+ private @Nullable FreeboxOsHandler bridgeHandler;
+
+ public FreeboxOsDiscoveryService() {
+ super(Stream.of(THINGS_TYPES_UIDS, HOME_TYPES_UIDS).flatMap(Set::stream).collect(Collectors.toSet()),
+ DISCOVERY_TIME_SECONDS);
+ }
+
+ @Override
+ public void deactivate() {
+ super.deactivate();
+ }
+
+ @Override
+ public void setThingHandler(@Nullable ThingHandler handler) {
+ if (handler instanceof FreeboxOsHandler) {
+ bridgeHandler = (FreeboxOsHandler) handler;
+ activate(null);
+ }
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return bridgeHandler;
+ }
+
+ @Override
+ protected void startBackgroundDiscovery() {
+ stopBackgroundDiscovery();
+ backgroundFuture = Optional.of(scheduler.scheduleWithFixedDelay(this::startScan,
+ BACKGROUND_SCAN_REFRESH_MINUTES, BACKGROUND_SCAN_REFRESH_MINUTES, TimeUnit.MINUTES));
+ }
+
+ @Override
+ protected void stopBackgroundDiscovery() {
+ backgroundFuture.ifPresent(future -> future.cancel(true));
+ backgroundFuture = Optional.empty();
+ }
+
+ @Override
+ protected void startScan() {
+ logger.debug("Starting Freebox discovery scan");
+ FreeboxOsHandler localHandler = bridgeHandler;
+ if (localHandler != null && localHandler.getThing().getStatus() == ThingStatus.ONLINE) {
+ try {
+ ThingUID bridgeUID = localHandler.getThing().getUID();
+
+ List<LanHost> lanHosts = localHandler.getManager(LanBrowserManager.class).getHosts().stream()
+ .filter(LanHost::reachable).collect(Collectors.toList());
+
+ discoverServer(localHandler.getManager(SystemManager.class), bridgeUID);
+ discoverPhone(localHandler.getManager(PhoneManager.class), bridgeUID);
+ discoverPlugs(localHandler.getManager(FreeplugManager.class), bridgeUID);
+ discoverRepeater(localHandler.getManager(RepeaterManager.class), bridgeUID, lanHosts);
+ discoverPlayer(localHandler.getManager(PlayerManager.class), bridgeUID, lanHosts);
+ discoverVM(localHandler.getManager(VmManager.class), bridgeUID, lanHosts);
+ discoverHome(localHandler.getManager(HomeManager.class), bridgeUID);
+ if (localHandler.getConfiguration().discoverNetDevice) {
+ discoverHosts(localHandler, bridgeUID, lanHosts);
+ }
+ } catch (FreeboxException e) {
+ logger.warn("Error while requesting data for things discovery: {}", e.getMessage());
+ }
+ }
+ }
+
+ private void discoverHome(HomeManager homeManager, ThingUID bridgeUID) throws FreeboxException {
+ NodeConfigurationBuilder builder = NodeConfigurationBuilder.getInstance();
+ try {
+ homeManager.getHomeNodes().forEach(
+ node -> builder.configure(bridgeUID, node).ifPresent(result -> thingDiscovered(result.build())));
+ } catch (PermissionException e) {
+ logger.warn("Missing permission to discover Home {}", e.getPermission());
+ }
+ }
+
+ private void discoverPlugs(FreeplugManager freeplugManager, ThingUID bridgeUID) {
+ FreeplugConfigurationBuilder builder = FreeplugConfigurationBuilder.getInstance();
+ try {
+ freeplugManager.getPlugs().forEach(plug -> thingDiscovered(builder.configure(bridgeUID, plug).build()));
+ } catch (FreeboxException e) {
+ logger.warn("Error discovering freeplugs {}", e.getMessage());
+ }
+ }
+
+ private void discoverPhone(PhoneManager phoneManager, ThingUID bridgeUID) throws FreeboxException {
+ PhoneConfigurationBuilder builder = PhoneConfigurationBuilder.getInstance();
+ List<Status> statuses = List.of();
+ try {
+ statuses = phoneManager.getPhoneStatuses();
+ statuses.forEach(phone -> thingDiscovered(builder.configure(bridgeUID, phone).build()));
+ } catch (FreeboxException e) {
+ logger.warn("Error discovering phones {}", e.getMessage());
+ }
+ if (!statuses.isEmpty()) {
+ ThingUID thingUID = new ThingUID(THING_TYPE_CALL, bridgeUID, "landline");
+ logger.debug("Adding new Call thing {} to inbox", thingUID);
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
+ .withLabel("Freebox Calls").build();
+ thingDiscovered(discoveryResult);
+ }
+ }
+
+ private void discoverHosts(FreeboxOsHandler localHandler, ThingUID bridgeUID, List<LanHost> lanHosts)
+ throws FreeboxException {
+ try {
+ List<MACAddress> wifiMacs = new ArrayList<>();
+ wifiMacs.addAll(localHandler.getManager(APManager.class).getStations().stream().map(Station::mac)
+ .collect(Collectors.toList()));
+ wifiMacs.addAll(localHandler.getManager(RepeaterManager.class).getHosts().stream().map(LanHost::getMac)
+ .collect(Collectors.toList()));
+
+ lanHosts.forEach(lanHost -> {
+ MACAddress mac = lanHost.getMac();
+ String macString = mac.toColonDelimitedString();
+ ThingUID thingUID = new ThingUID(wifiMacs.contains(mac) ? THING_TYPE_WIFI_HOST : THING_TYPE_HOST,
+ bridgeUID, mac.toHexString(false));
+ logger.debug("Adding new Freebox Network Host {} to inbox", thingUID);
+ DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
+ .withLabel(lanHost.getPrimaryName().orElse("Network Device %s".formatted(macString)))
+ .withTTL(300).withProperty(Thing.PROPERTY_MAC_ADDRESS, macString)
+ .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS);
+ thingDiscovered(builder.build());
+ });
+ } catch (PermissionException e) {
+ logger.warn("Missing permission to discover Hosts {}", e.getPermission());
+ }
+ }
+
+ private void discoverVM(VmManager vmManager, ThingUID bridgeUID, List<LanHost> lanHosts) throws FreeboxException {
+ try {
+ vmManager.getDevices().forEach(vm -> {
+ MACAddress mac = vm.mac();
+ lanHosts.removeIf(host -> host.getMac().equals(mac));
+
+ ThingUID thingUID = new ThingUID(THING_TYPE_VM, bridgeUID, mac.toHexString(false));
+ logger.debug("Adding new VM Device {} to inbox", thingUID);
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
+ .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS)
+ .withLabel("%s (VM)".formatted(vm.name())).withProperty(ClientConfiguration.ID, vm.id())
+ .withProperty(Thing.PROPERTY_MAC_ADDRESS, mac.toColonDelimitedString()).build();
+ thingDiscovered(discoveryResult);
+ });
+ } catch (PermissionException e) {
+ logger.warn("Missing permission to discover VM {}", e.getPermission());
+ }
+ }
+
+ private void discoverRepeater(RepeaterManager repeaterManager, ThingUID bridgeUID, List<LanHost> lanHosts)
+ throws FreeboxException {
+ try {
+ List<Repeater> repeaters = repeaterManager.getDevices();
+ repeaters.forEach(repeater -> {
+ MACAddress mac = repeater.mainMac();
+ lanHosts.removeIf(host -> host.getMac().equals(mac));
+
+ ThingUID thingUID = new ThingUID(THING_TYPE_REPEATER, bridgeUID, Integer.toString(repeater.id()));
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
+ .withLabel("Repeater %s".formatted(repeater.name()))
+ .withProperty(Thing.PROPERTY_MAC_ADDRESS, mac.toColonDelimitedString())
+ .withProperty(ClientConfiguration.ID, repeater.id())
+ .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
+ thingDiscovered(discoveryResult);
+ });
+ } catch (PermissionException e) {
+ logger.warn("Missing permission to discover Repeater {}", e.getPermission());
+ }
+ }
+
+ private void discoverServer(SystemManager systemManager, ThingUID bridgeUID) throws FreeboxException {
+ try {
+ Config config = systemManager.getConfig();
+
+ ThingTypeUID targetType = config.boardName().startsWith("fbxgw7") ? THING_TYPE_DELTA
+ : THING_TYPE_REVOLUTION;
+ ThingUID thingUID = new ThingUID(targetType, bridgeUID, config.serial());
+ logger.debug("Adding new Freebox Server {} to inbox", thingUID);
+
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
+ .withProperty(Thing.PROPERTY_MAC_ADDRESS, config.mac())
+ .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).withLabel(config.modelInfo().prettyName())
+ .build();
+ thingDiscovered(discoveryResult);
+ } catch (PermissionException e) {
+ logger.warn("Missing permission to discover Server {}", e.getPermission());
+ }
+ }
+
+ private void discoverPlayer(PlayerManager playerManager, ThingUID bridgeUID, List<LanHost> lanHosts)
+ throws FreeboxException {
+ try {
+ for (Player player : playerManager.getDevices()) {
+ lanHosts.removeIf(host -> host.getMac().equals(player.mac()));
+ ThingUID thingUID = new ThingUID(player.apiAvailable() ? THING_TYPE_ACTIVE_PLAYER : THING_TYPE_PLAYER,
+ bridgeUID, Integer.toString(player.id()));
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
+ .withLabel(player.deviceName())
+ .withProperty(Thing.PROPERTY_MAC_ADDRESS, player.mac().toColonDelimitedString())
+ .withProperty(ClientConfiguration.ID, player.id())
+ .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
+ thingDiscovered(discoveryResult);
+ }
+ } catch (PermissionException e) {
+ logger.warn("Missing permission to discover Player {}", e.getPermission());
+ }
+ }
+}
--- /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.handler;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.action.ActivePlayerActions;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager;
+import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Configuration;
+import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.ForegroundApp;
+import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Player;
+import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Status;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link ActivePlayerHandler} is responsible for handling everything associated to Freebox Player with api
+ * capabilities.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class ActivePlayerHandler extends PlayerHandler implements FreeDeviceIntf {
+ private final Logger logger = LoggerFactory.getLogger(ActivePlayerHandler.class);
+
+ private final ChannelUID eventChannelUID;
+
+ private long uptime = -1;
+
+ public ActivePlayerHandler(Thing thing) {
+ super(thing);
+ eventChannelUID = new ChannelUID(getThing().getUID(), SYS_INFO, BOX_EVENT);
+ }
+
+ @Override
+ void initializeProperties(Map<String, String> properties) throws FreeboxException {
+ super.initializeProperties(properties);
+ Player player = getManager(PlayerManager.class).getDevice(getClientId());
+ if (player.reachable()) {
+ Configuration config = getManager(PlayerManager.class).getConfig(player.id());
+ properties.put(Thing.PROPERTY_SERIAL_NUMBER, config.serial());
+ properties.put(Thing.PROPERTY_FIRMWARE_VERSION, config.firmwareVersion());
+ }
+ }
+
+ @Override
+ protected void internalPoll() throws FreeboxException {
+ super.internalPoll();
+ if (thing.getStatus().equals(ThingStatus.ONLINE)) {
+ Status status = getManager(PlayerManager.class).getPlayerStatus(getClientId());
+ updateChannelString(PLAYER_STATUS, PLAYER_STATUS, status.powerState().name());
+ ForegroundApp foreground = status.foregroundApp();
+ if (foreground != null) {
+ updateChannelString(PLAYER_STATUS, PACKAGE, foreground._package());
+ }
+ Configuration config = getManager(PlayerManager.class).getConfig(getClientId());
+
+ uptime = checkUptimeAndFirmware(config.uptimeVal(), uptime, config.firmwareVersion());
+ updateChannelQuantity(SYS_INFO, UPTIME, uptime, Units.SECOND);
+ }
+ }
+
+ public void reboot() {
+ processReboot(() -> {
+ try {
+ getManager(PlayerManager.class).reboot(getClientId());
+ } catch (FreeboxException e) {
+ logger.warn("Error rebooting: {}", e.getMessage());
+ }
+ });
+ }
+
+ @Override
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ return Collections.singletonList(ActivePlayerActions.class);
+ }
+
+ @Override
+ public ChannelUID getEventChannelUID() {
+ return eventChannelUID;
+ }
+
+ @Override
+ public void triggerChannel(ChannelUID channelUID, String event) {
+ super.triggerChannel(channelUID, event);
+ }
+}
--- /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.handler;
+
+import static org.openhab.core.audio.AudioFormat.*;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.rest.MediaReceiverManager;
+import org.openhab.binding.freeboxos.internal.api.rest.MediaReceiverManager.Action;
+import org.openhab.binding.freeboxos.internal.api.rest.MediaReceiverManager.MediaType;
+import org.openhab.core.audio.AudioFormat;
+import org.openhab.core.audio.AudioHTTPServer;
+import org.openhab.core.audio.AudioSinkAsync;
+import org.openhab.core.audio.AudioStream;
+import org.openhab.core.audio.StreamServed;
+import org.openhab.core.audio.URLAudioStream;
+import org.openhab.core.audio.UnsupportedAudioFormatException;
+import org.openhab.core.audio.UnsupportedAudioStreamException;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.thing.ThingStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link AirMediaSink} is holding AudioSink capabilities for various
+ * things.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class AirMediaSink extends AudioSinkAsync {
+ private static final Set<Class<? extends AudioStream>> SUPPORTED_STREAMS = Set.of(AudioStream.class);
+ private static final Set<AudioFormat> BASIC_FORMATS = Set.of(WAV, OGG);
+ private static final Set<AudioFormat> ALL_MP3_FORMATS = Set.of(
+ new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, null, 96000, null),
+ new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, null, 112000, null),
+ new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, null, 128000, null),
+ new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, null, 160000, null),
+ new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, null, 192000, null),
+ new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, null, 224000, null),
+ new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, null, 256000, null),
+ new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, null, 320000, null));
+
+ private final Logger logger = LoggerFactory.getLogger(AirMediaSink.class);
+ private final ApiConsumerHandler thingHandler;
+ private final Set<AudioFormat> supportedFormats = new HashSet<>();
+ private final AudioHTTPServer audioHTTPServer;
+ private final String callbackUrl;
+ private final String playerName;
+ private final String password;
+
+ public AirMediaSink(ApiConsumerHandler thingHandler, AudioHTTPServer audioHTTPServer, String callbackUrl,
+ String playerName, String password, boolean acceptAllMp3) {
+ this.thingHandler = thingHandler;
+ this.audioHTTPServer = audioHTTPServer;
+ this.playerName = playerName;
+ this.callbackUrl = callbackUrl;
+ this.password = password;
+
+ supportedFormats.addAll(BASIC_FORMATS);
+ if (acceptAllMp3) {
+ supportedFormats.addAll(ALL_MP3_FORMATS);
+ } else { // Only accept MP3 bitrates >= 96 kbps
+ supportedFormats.add(MP3);
+ }
+ }
+
+ @Override
+ public Set<Class<? extends AudioStream>> getSupportedStreams() {
+ return SUPPORTED_STREAMS;
+ }
+
+ @Override
+ public PercentType getVolume() throws IOException {
+ logger.debug("getVolume received but AirMedia does not have the capability - returning 100%.");
+ return PercentType.HUNDRED;
+ }
+
+ @Override
+ public void setVolume(PercentType volume) throws IOException {
+ logger.debug("setVolume received but AirMedia does not have the capability - ignoring it.");
+ }
+
+ @Override
+ public String getId() {
+ return thingHandler.getThing().getUID().toString();
+ }
+
+ @Override
+ public @Nullable String getLabel(@Nullable Locale locale) {
+ return thingHandler.getThing().getLabel();
+ }
+
+ @Override
+ protected void processAsynchronously(@Nullable AudioStream audioStream)
+ throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
+ if (thingHandler.getThing().getStatus() == ThingStatus.ONLINE) {
+ try {
+ MediaReceiverManager manager = thingHandler.getManager(MediaReceiverManager.class);
+ if (audioStream == null) {
+ manager.sendToReceiver(playerName, password, Action.STOP, MediaType.VIDEO);
+ return;
+ }
+
+ if (audioStream instanceof URLAudioStream urlAudioStream) {
+ // it is an external URL, we can access it directly
+ logger.debug("AirPlay audio sink: process url {}", urlAudioStream.getURL());
+ playMedia(manager, urlAudioStream.getURL());
+ return;
+ }
+ // we serve it on our own HTTP server
+ StreamServed streamServed;
+ try {
+ streamServed = audioHTTPServer.serve(audioStream, 5, true);
+ } catch (IOException e) {
+ try {
+ audioStream.close();
+ } catch (IOException ex) {
+ logger.debug("Exception while closing audioStream");
+ }
+ throw new UnsupportedAudioStreamException(
+ "AirPlay device was not able to handle the audio stream (cache on disk failed).",
+ audioStream.getClass(), e);
+ }
+ streamServed.playEnd().thenRun(() -> this.playbackFinished(audioStream));
+ logger.debug("AirPlay audio sink: process url {}", callbackUrl + streamServed.url());
+ playMedia(manager, callbackUrl + streamServed.url());
+ } catch (FreeboxException e) {
+ logger.warn("Audio stream playback failed: {}", e.getMessage());
+ }
+ }
+ }
+
+ private void playMedia(MediaReceiverManager manager, String url) throws FreeboxException {
+ manager.sendToReceiver(playerName, password, Action.STOP, MediaType.VIDEO);
+ manager.sendToReceiver(playerName, password, Action.START, MediaType.VIDEO, url);
+ }
+
+ @Override
+ public Set<AudioFormat> getSupportedFormats() {
+ return supportedFormats;
+ }
+}
--- /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.handler;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link AlarmHandler} is responsible for handling everything associated to
+ * any Freebox Home Alarm thing type.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class AlarmHandler extends HomeNodeHandler {
+
+ public AlarmHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ protected State getChannelState(HomeManager homeManager, String channelId, EndpointState state) {
+ String value = state.value();
+
+ if (value == null) {
+ return UnDefType.NULL;
+ }
+
+ return switch (channelId) {
+ case NODE_BATTERY -> DecimalType.valueOf(value);
+ case ALARM_PIN -> StringType.valueOf(value);
+ case ALARM_SOUND, ALARM_VOLUME -> QuantityType.valueOf(value + " %");
+ case ALARM_TIMEOUT1, ALARM_TIMEOUT2, ALARM_TIMEOUT3 -> QuantityType.valueOf(value + " s");
+ default -> UnDefType.NULL;
+ };
+ }
+}
--- /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.handler;
+
+import java.math.BigDecimal;
+import java.time.ZonedDateTime;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.Source;
+import org.openhab.binding.freeboxos.internal.api.rest.MediaReceiverManager;
+import org.openhab.binding.freeboxos.internal.api.rest.MediaReceiverManager.MediaType;
+import org.openhab.binding.freeboxos.internal.api.rest.MediaReceiverManager.Receiver;
+import org.openhab.binding.freeboxos.internal.api.rest.RestManager;
+import org.openhab.binding.freeboxos.internal.config.ApiConsumerConfiguration;
+import org.openhab.binding.freeboxos.internal.config.ClientConfiguration;
+import org.openhab.core.audio.AudioSink;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.DateTimeType;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.thing.binding.BridgeHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+import org.osgi.framework.ServiceRegistration;
+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
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+abstract class ApiConsumerHandler extends BaseThingHandler implements ApiConsumerIntf {
+ private final Logger logger = LoggerFactory.getLogger(ApiConsumerHandler.class);
+ private final Map<String, ScheduledFuture<?>> jobs = new HashMap<>();
+
+ private @Nullable ServiceRegistration<?> reg;
+
+ ApiConsumerHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ FreeboxOsHandler bridgeHandler = checkBridgeHandler();
+ if (bridgeHandler == null) {
+ return;
+ }
+
+ Map<String, String> properties = editProperties();
+ if (properties.isEmpty()) {
+ try {
+ initializeProperties(properties);
+ checkAirMediaCapabilities(properties);
+ updateProperties(properties);
+ } catch (FreeboxException e) {
+ logger.warn("Error getting thing {} properties: {}", thing.getUID(), e.getMessage());
+ }
+ }
+
+ boolean isAudioReceiver = Boolean.parseBoolean(properties.get(MediaType.AUDIO.name()));
+ if (isAudioReceiver) {
+ configureMediaSink(bridgeHandler, properties.getOrDefault(Source.UPNP.name(), ""));
+ }
+
+ startRefreshJob();
+ }
+
+ private void configureMediaSink(FreeboxOsHandler bridgeHandler, String upnpName) {
+ try {
+ Receiver receiver = getManager(MediaReceiverManager.class).getReceiver(upnpName);
+ if (receiver != null && reg == null) {
+ ApiConsumerConfiguration config = getConfig().as(ApiConsumerConfiguration.class);
+ String callbackURL = bridgeHandler.getCallbackURL();
+ if (!config.password.isEmpty() || !receiver.passwordProtected()) {
+ reg = bridgeHandler.getBundleContext().registerService(
+ AudioSink.class.getName(), new AirMediaSink(this, bridgeHandler.getAudioHTTPServer(),
+ callbackURL, receiver.name(), config.password, config.acceptAllMp3),
+ new Hashtable<>());
+ } else {
+ logger.info("A password needs to be configured to enable Air Media capability.");
+ }
+ }
+ } catch (FreeboxException e) {
+ logger.warn("Unable to retrieve Media Receivers: {}", e.getMessage());
+ }
+ }
+
+ public <T extends RestManager> T getManager(Class<T> clazz) throws FreeboxException {
+ FreeboxOsHandler handler = checkBridgeHandler();
+ if (handler != null) {
+ return handler.getManager(clazz);
+ }
+ throw new FreeboxException("Bridge handler not yet defined");
+ }
+
+ abstract void initializeProperties(Map<String, String> properties) throws FreeboxException;
+
+ @Override
+ public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
+ if (checkBridgeHandler() != null) {
+ startRefreshJob();
+ } else {
+ stopJobs();
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ if (command instanceof RefreshType || getThing().getStatus() != ThingStatus.ONLINE) {
+ return;
+ }
+ try {
+ if (checkBridgeHandler() == null || !internalHandleCommand(channelUID.getIdWithoutGroup(), command)) {
+ logger.debug("Unexpected command {} on channel {}", command, channelUID.getId());
+ }
+ } catch (FreeboxException e) {
+ logger.warn("Error handling command: {}", e.getMessage());
+ }
+ }
+
+ private void checkAirMediaCapabilities(Map<String, String> properties) throws FreeboxException {
+ String upnpName = properties.getOrDefault(Source.UPNP.name(), "");
+ Receiver receiver = getManager(MediaReceiverManager.class).getReceiver(upnpName);
+ if (receiver != null) {
+ receiver.capabilities().entrySet()
+ .forEach(entry -> properties.put(entry.getKey().name(), entry.getValue().toString()));
+ }
+ }
+
+ private @Nullable FreeboxOsHandler checkBridgeHandler() {
+ Bridge bridge = getBridge();
+ if (bridge != null) {
+ BridgeHandler handler = bridge.getHandler();
+ if (handler instanceof FreeboxOsHandler) {
+ if (bridge.getStatus() == ThingStatus.ONLINE) {
+ updateStatus(ThingStatus.ONLINE);
+ return (FreeboxOsHandler) handler;
+ }
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_MISSING_ERROR);
+ }
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
+ }
+ return null;
+ }
+
+ @Override
+ public void dispose() {
+ stopJobs();
+ ServiceRegistration<?> localReg = reg;
+ if (localReg != null) {
+ localReg.unregister();
+ }
+ super.dispose();
+ }
+
+ private void startRefreshJob() {
+ removeJob("GlobalJob");
+
+ int refreshInterval = getConfigAs(ApiConsumerConfiguration.class).refreshInterval;
+ logger.debug("Scheduling state update every {} seconds for thing {}...", refreshInterval, getThing().getUID());
+
+ ThingStatusDetail detail = thing.getStatusInfo().getStatusDetail();
+ if (ThingStatusDetail.DUTY_CYCLE.equals(detail)) {
+ try {
+ internalPoll();
+ } catch (FreeboxException ignore) {
+ // An exception is normal if the box is rebooting then let's try again later...
+ addJob("Initialize", this::initialize, 10, TimeUnit.SECONDS);
+ return;
+ }
+ }
+
+ addJob("GlobalJob", () -> {
+ try {
+ internalPoll();
+ } catch (FreeboxException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ }, 0, refreshInterval, TimeUnit.SECONDS);
+ }
+
+ private void removeJob(String name) {
+ ScheduledFuture<?> existing = jobs.get(name);
+ if (existing != null && !existing.isCancelled()) {
+ existing.cancel(true);
+ }
+ }
+
+ @Override
+ public void addJob(String name, Runnable command, long initialDelay, long delay, TimeUnit unit) {
+ removeJob(name);
+ jobs.put(name, scheduler.scheduleWithFixedDelay(command, initialDelay, delay, unit));
+ }
+
+ @Override
+ public void addJob(String name, Runnable command, long delay, TimeUnit unit) {
+ removeJob(name);
+ jobs.put(name, scheduler.schedule(command, delay, unit));
+ }
+
+ @Override
+ public void stopJobs() {
+ jobs.keySet().forEach(name -> removeJob(name));
+ jobs.clear();
+ }
+
+ protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
+ return false;
+ }
+
+ protected abstract void internalPoll() throws FreeboxException;
+
+ private void updateIfActive(String group, String channelId, State state) {
+ ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
+ if (isLinked(id)) {
+ updateState(id, state);
+ }
+ }
+
+ protected void updateIfActive(String channelId, State state) {
+ ChannelUID id = new ChannelUID(getThing().getUID(), channelId);
+ if (isLinked(id)) {
+ updateState(id, state);
+ }
+ }
+
+ protected void updateChannelDateTimeState(String channelId, @Nullable ZonedDateTime timestamp) {
+ updateIfActive(channelId, timestamp == null ? UnDefType.NULL : new DateTimeType(timestamp));
+ }
+
+ protected void updateChannelDateTimeState(String group, String channelId, @Nullable ZonedDateTime timestamp) {
+ updateIfActive(group, channelId, timestamp == null ? UnDefType.NULL : new DateTimeType(timestamp));
+ }
+
+ protected void updateChannelOnOff(String group, String channelId, boolean value) {
+ updateIfActive(group, channelId, OnOffType.from(value));
+ }
+
+ protected void updateChannelOnOff(String channelId, boolean value) {
+ updateIfActive(channelId, OnOffType.from(value));
+ }
+
+ protected void updateChannelString(String group, String channelId, @Nullable String value) {
+ updateIfActive(group, channelId, value != null ? new StringType(value) : UnDefType.NULL);
+ }
+
+ protected void updateChannelString(String group, String channelId, @Nullable IPAddress value) {
+ updateIfActive(group, channelId, value != null ? new StringType(value.toCanonicalString()) : UnDefType.NULL);
+ }
+
+ protected void updateChannelString(String channelId, @Nullable String value) {
+ updateIfActive(channelId, value != null ? new StringType(value) : UnDefType.NULL);
+ }
+
+ protected void updateChannelString(String channelId, Enum<?> value) {
+ updateIfActive(channelId, new StringType(value.name()));
+ }
+
+ protected void updateChannelString(String group, String channelId, Enum<?> value) {
+ updateIfActive(group, channelId, new StringType(value.name()));
+ }
+
+ protected void updateChannelQuantity(String group, String channelId, double d, Unit<?> unit) {
+ updateChannelQuantity(group, channelId, new QuantityType<>(d, unit));
+ }
+
+ protected void updateChannelQuantity(String channelId, @Nullable QuantityType<?> quantity) {
+ updateIfActive(channelId, quantity != null ? quantity : UnDefType.NULL);
+ }
+
+ protected void updateChannelQuantity(String group, String channelId, @Nullable QuantityType<?> quantity) {
+ updateIfActive(group, channelId, quantity != null ? quantity : UnDefType.NULL);
+ }
+
+ protected void updateChannelDecimal(String group, String channelId, @Nullable Integer value) {
+ updateIfActive(group, channelId, value != null ? new DecimalType(value) : UnDefType.NULL);
+ }
+
+ protected void updateChannelQuantity(String group, String channelId, QuantityType<?> qtty, Unit<?> unit) {
+ updateChannelQuantity(group, channelId, qtty.toUnit(unit));
+ }
+
+ @Override
+ public void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
+ super.updateStatus(status, statusDetail, description);
+ }
+
+ @Override
+ public Map<String, String> editProperties() {
+ return super.editProperties();
+ }
+
+ @Override
+ public void updateProperties(@Nullable Map<String, String> properties) {
+ super.updateProperties(properties);
+ }
+
+ @Override
+ public Configuration getConfig() {
+ return super.getConfig();
+ }
+
+ @Override
+ 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();
+ }
+}
--- /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.handler;
+
+import java.math.BigDecimal;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.config.ClientConfiguration;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.ThingHandler;
+
+import inet.ipaddr.MACAddressString;
+import inet.ipaddr.mac.MACAddress;
+
+/**
+ * The {@link ApiConsumerIntf} defines some common methods for various devices (server, player, repeater) not belonging
+ * to the same class hierarchy
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public interface ApiConsumerIntf extends ThingHandler {
+
+ Map<String, String> editProperties();
+
+ Configuration getConfig();
+
+ void updateProperties(@Nullable Map<String, String> properties);
+
+ void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description);
+
+ void stopJobs();
+
+ void addJob(String name, Runnable command, long initialDelay, long delay, TimeUnit unit);
+
+ void addJob(String name, Runnable command, long delay, TimeUnit unit);
+
+ default int getClientId() {
+ return ((BigDecimal) getConfig().get(ClientConfiguration.ID)).intValue();
+ }
+
+ default MACAddress getMac() {
+ String mac = (String) getConfig().get(Thing.PROPERTY_MAC_ADDRESS);
+ return new MACAddressString(mac).getAddress();
+ }
+}
--- /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.handler;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Endpoint;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.OpenClosedType;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link BasicShutterHandler} is responsible for handling everything associated to
+ * any Freebox Home basic-shutter thing type.
+ *
+ * @author ben12 - Initial contribution
+ */
+@NonNullByDefault
+public class BasicShutterHandler extends HomeNodeHandler {
+ private static final Set<String> SHUTTER_ENDPOINTS = Set.of(SHUTTER_STOP, BASIC_SHUTTER_UP, BASIC_SHUTTER_DOWN);
+
+ public BasicShutterHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ protected void internalConfigureChannel(String channelId, Configuration conf, List<Endpoint> endpoints) {
+ endpoints.stream().filter(ep -> channelId.equals(BASIC_SHUTTER_STATE) && SHUTTER_ENDPOINTS.contains(ep.name()))
+ .forEach(endPoint -> conf.put(endPoint.name(), endPoint.id()));
+ }
+
+ @Override
+ protected State getChannelState(HomeManager homeManager, String channelId, EndpointState state) {
+ String value = state.value();
+ return value != null && channelId.equals(BASIC_SHUTTER_STATE)
+ ? state.asBoolean() ? OpenClosedType.CLOSED : OpenClosedType.OPEN
+ : UnDefType.NULL;
+ }
+
+ @Override
+ protected boolean executeChannelCommand(HomeManager homeManager, String channelId, Command command,
+ Configuration config) throws FreeboxException {
+ Integer slot = getSlotId(config, command.toString().toLowerCase());
+ if (BASIC_SHUTTER_STATE.equals(channelId) && slot != null) {
+ return homeManager.putCommand(getClientId(), slot, true);
+ }
+ return false;
+ }
+}
--- /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.handler;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import java.time.ZonedDateTime;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.action.CallActions;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.rest.CallManager;
+import org.openhab.binding.freeboxos.internal.api.rest.CallManager.Call;
+import org.openhab.binding.freeboxos.internal.api.rest.CallManager.Type;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link CallHandler} is responsible for handling everything associated to the phone calls received on the box line
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class CallHandler extends ApiConsumerHandler {
+ private final Logger logger = LoggerFactory.getLogger(CallHandler.class);
+ private final Map<Type, ZonedDateTime> lastCalls = new HashMap<>(Type.values().length);
+
+ public CallHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ void initializeProperties(Map<String, String> properties) throws FreeboxException {
+ // nothing to do here
+ }
+
+ @Override
+ protected void internalPoll() throws FreeboxException {
+ logger.debug("Polling phone calls ...");
+
+ lastCalls.clear();
+
+ List<Call> entries = getManager(CallManager.class).getCallEntries();
+ Arrays.stream(Type.values()).forEach(callType -> entries.stream().filter(call -> call.type().equals(callType))
+ .reduce((first, second) -> second).ifPresent(this::updateCallChannels));
+
+ // Clear incoming call if the youngest is not an incoming call
+ lastCalls.entrySet().stream().sorted(Map.Entry.comparingByValue()).reduce((first, second) -> second)
+ .map(entry -> entry.getKey()).filter(type -> !Type.INCOMING.equals(type)).ifPresent(type -> {
+ String groupName = Type.INCOMING.name().toLowerCase();
+ getThing().getChannelsOfGroup(groupName).stream().map(Channel::getUID).filter(uid -> isLinked(uid))
+ .forEach(uid -> updateState(uid, UnDefType.NULL));
+ });
+
+ updateStatus(ThingStatus.ONLINE);
+ }
+
+ private void updateCallChannels(Call call) {
+ Type lastType = call.type();
+ lastCalls.put(lastType, call.datetime());
+ String group = lastType.name().toLowerCase();
+ String phoneNumber = call.number();
+
+ updateChannelString(group, NUMBER, phoneNumber);
+ updateChannelString(group, NAME, call.name());
+ updateChannelDateTimeState(group, TIMESTAMP, call.datetime());
+
+ // Do not consider duration for Missed & incoming calls
+ if (lastType == Type.ACCEPTED || lastType == Type.OUTGOING) {
+ updateChannelQuantity(group, DURATION, call.duration(), Units.SECOND);
+ }
+ }
+
+ public void emptyQueue() {
+ try {
+ getManager(CallManager.class).emptyQueue();
+ } catch (FreeboxException e) {
+ logger.warn("Error clearing call logs: {}", e.getMessage());
+ }
+ }
+
+ @Override
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ return Collections.singleton(CallActions.class);
+ }
+}
--- /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.handler;
+
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.Command;
+
+/**
+ * The {@link CameraHandler} is responsible for handling everything associated to
+ * any Freebox Home Camera thing type.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class CameraHandler extends ApiConsumerHandler {
+
+ public CameraHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ void initializeProperties(Map<String, String> properties) throws FreeboxException {
+ }
+
+ @Override
+ protected void internalPoll() throws FreeboxException {
+ }
+
+ @Override
+ protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
+ return super.internalHandleCommand(channelId, command);
+ }
+}
--- /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.handler;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager;
+import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager.Config;
+import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager.Status;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.Command;
+
+/**
+ * The {@link DectHandler} is responsible for handling DECT specifics of the Telephony API
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class DectHandler extends FxsHandler {
+
+ public DectHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ protected void updateConfigChannels(Config config) {
+ super.updateConfigChannels(config);
+ updateChannelOnOff(DECT_ACTIVE, config.dectEnabled());
+ updateChannelOnOff(ALTERNATE_RING, config.dectRingOnOff());
+ }
+
+ @Override
+ protected void updateStatusChannels(Status status) {
+ super.updateStatusChannels(status);
+ updateIfActive(GAIN_RX, new PercentType(status.gainRx()));
+ updateIfActive(GAIN_TX, new PercentType(status.gainTx()));
+ }
+
+ @Override
+ protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
+ PhoneManager phoneManager = getManager(PhoneManager.class);
+ if (command instanceof OnOffType) {
+ boolean status = OnOffType.ON.equals(command);
+ if (RINGING.equals(channelId)) {
+ phoneManager.ringDect(status);
+ return true;
+ } else if (DECT_ACTIVE.equals(channelId)) {
+ phoneManager.setStatus(status);
+ return true;
+ } else if (ALTERNATE_RING.equals(channelId)) {
+ phoneManager.alternateRing(status);
+ return true;
+ }
+ }
+ if (command instanceof PercentType) {
+ PercentType percent = (PercentType) command;
+ if (GAIN_RX.equals(channelId)) {
+ phoneManager.setGainRx(getClientId(), percent.intValue());
+ updateIfActive(GAIN_RX, percent);
+ return true;
+ } else if (GAIN_TX.equals(channelId)) {
+ phoneManager.setGainTx(getClientId(), percent.intValue());
+ updateIfActive(GAIN_RX, percent);
+ return true;
+ }
+ }
+ return super.internalHandleCommand(channelId, command);
+ }
+}
--- /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.handler;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+
+/**
+ * The {@link FreeDeviceIntf} defines some common methods for various devices (server, player, repeater) not belonging
+ * to the same class hierarchy
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public interface FreeDeviceIntf extends ApiConsumerIntf {
+ public ChannelUID getEventChannelUID();
+
+ public void triggerChannel(ChannelUID channelUID, String event);
+
+ default long checkUptimeAndFirmware(long newUptime, long oldUptime, String firmwareVersion) {
+ if (newUptime < oldUptime) {
+ triggerChannel(getEventChannelUID(), "restarted");
+ Map<String, String> properties = editProperties();
+ if (!firmwareVersion.equals(properties.get(Thing.PROPERTY_FIRMWARE_VERSION))) {
+ properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmwareVersion);
+ updateProperties(properties);
+ triggerChannel(getEventChannelUID(), "firmware_updated");
+ }
+ }
+ return newUptime;
+ }
+
+ default void processReboot(Runnable actualReboot) {
+ triggerChannel(getEventChannelUID(), "reboot_requested");
+ actualReboot.run();
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.DUTY_CYCLE, "System rebooting...");
+ stopJobs();
+ addJob("Initialize", this::initialize, 30, TimeUnit.SECONDS);
+ }
+}
--- /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.handler;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.rest.FreeboxOsSession;
+import org.openhab.binding.freeboxos.internal.api.rest.RestManager;
+import org.openhab.binding.freeboxos.internal.config.FreeboxOsConfiguration;
+import org.openhab.binding.freeboxos.internal.discovery.FreeboxOsDiscoveryService;
+import org.openhab.core.audio.AudioHTTPServer;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link FreeboxOsHandler} handle common parts of Freebox bridges.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class FreeboxOsHandler extends BaseBridgeHandler {
+ private final Logger logger = LoggerFactory.getLogger(FreeboxOsHandler.class);
+ private final FreeboxOsSession session;
+ private final String callbackURL;
+ private final BundleContext bundleContext;
+ private final AudioHTTPServer audioHTTPServer;
+
+ private Optional<Future<?>> openConnectionJob = Optional.empty();
+ private Optional<Future<?>> grantingJob = Optional.empty();
+
+ public FreeboxOsHandler(Bridge thing, FreeboxOsSession session, String callbackURL, BundleContext bundleContext,
+ AudioHTTPServer audioHTTPServer) {
+ super(thing);
+ this.session = session;
+ this.callbackURL = callbackURL;
+ this.bundleContext = bundleContext;
+ this.audioHTTPServer = audioHTTPServer;
+ }
+
+ @Override
+ public void initialize() {
+ freeConnectionJob();
+
+ FreeboxOsConfiguration config = getConfiguration();
+ openConnectionJob = Optional.of(scheduler.submit(() -> {
+ try {
+ session.initialize(config);
+ if (config.appToken.isBlank()) {
+ updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
+ "@text/info-conf-pending");
+ grantingJob = Optional.of(scheduler.schedule(this::processGranting, 2, TimeUnit.SECONDS));
+ return;
+ } else {
+ updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE);
+ session.openSession(config.appToken);
+ }
+ updateStatus(ThingStatus.ONLINE);
+ } catch (FreeboxException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
+ } catch (InterruptedException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ }));
+ }
+
+ private void processGranting() {
+ try {
+ String appToken = session.grant();
+ if (appToken.isBlank()) {
+ grantingJob = Optional.of(scheduler.schedule(this::processGranting, 2, TimeUnit.SECONDS));
+ } else {
+ Configuration thingConfig = editConfiguration();
+ thingConfig.put(FreeboxOsConfiguration.APP_TOKEN, appToken);
+ updateConfiguration(thingConfig);
+ logger.info("AppToken updated, ensure giving permissions in the Freebox management console");
+ initialize();
+ }
+ } catch (FreeboxException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
+ }
+ }
+
+ public <T extends RestManager> T getManager(Class<T> clazz) throws FreeboxException {
+ return session.getManager(clazz);
+ }
+
+ private void freeConnectionJob() {
+ openConnectionJob.ifPresent(job -> job.cancel(true));
+ openConnectionJob = Optional.empty();
+ grantingJob.ifPresent(job -> job.cancel(true));
+ grantingJob = Optional.empty();
+ }
+
+ @Override
+ public void dispose() {
+ freeConnectionJob();
+ session.closeSession();
+
+ super.dispose();
+ }
+
+ @Override
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ return Collections.singleton(FreeboxOsDiscoveryService.class);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ }
+
+ public FreeboxOsConfiguration getConfiguration() {
+ return getConfigAs(FreeboxOsConfiguration.class);
+ }
+
+ public String getCallbackURL() {
+ return callbackURL;
+ }
+
+ public BundleContext getBundleContext() {
+ return bundleContext;
+ }
+
+ public AudioHTTPServer getAudioHTTPServer() {
+ return audioHTTPServer;
+ }
+}
--- /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.handler;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.action.FreeplugActions;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.rest.FreeplugManager;
+import org.openhab.binding.freeboxos.internal.api.rest.FreeplugManager.NetRole;
+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.thing.binding.ThingHandlerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link FreeplugHandler} is responsible for handling everything associated to a CPL gateway managed by the freebox
+ * server
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class FreeplugHandler extends ApiConsumerHandler {
+ private final Logger logger = LoggerFactory.getLogger(FreeplugHandler.class);
+
+ public FreeplugHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ void initializeProperties(Map<String, String> properties) throws FreeboxException {
+ getManager(FreeplugManager.class).getPlug(getMac()).ifPresent(plug -> {
+ NetRole role = plug.netRole();
+ properties.put(Thing.PROPERTY_MODEL_ID, plug.model());
+ properties.put(ROLE, role.name());
+ properties.put(NET_ID, plug.netId());
+ properties.put(ETHERNET_SPEED, String.format("%d Mb/s", plug.ethSpeed()));
+ properties.put(LOCAL, Boolean.valueOf(plug.local()).toString());
+ properties.put(FULL_DUPLEX, Boolean.valueOf(plug.ethFullDuplex()).toString());
+
+ if (role.equals(NetRole.CCO)) { // Coordinator does not provide rate up or down
+ List<Channel> channels = new ArrayList<>(getThing().getChannels());
+ channels.removeIf(channel -> channel.getUID().getId().contains("rate"));
+ updateThing(editThing().withChannels(channels).build());
+ }
+ });
+ }
+
+ @Override
+ protected void internalPoll() throws FreeboxException {
+ getManager(FreeplugManager.class).getPlug(getMac()).ifPresent(plug -> {
+ ZonedDateTime lastSeen = ZonedDateTime.now().minusSeconds(plug.inactive());
+ updateChannelDateTimeState(LAST_SEEN, lastSeen);
+
+ updateChannelString(LINE_STATUS, plug.ethPortStatus());
+ updateChannelOnOff(REACHABLE, plug.hasNetwork());
+
+ updateRateChannel(RATE + "-down", plug.rxRate());
+ updateRateChannel(RATE + "-up", plug.txRate());
+ });
+ }
+
+ private void updateRateChannel(String channel, int rate) {
+ QuantityType<?> qtty = rate != -1 ? new QuantityType<>(rate, Units.MEGABIT_PER_SECOND) : null;
+ updateChannelQuantity(channel, qtty);
+ }
+
+ public void reset() {
+ try {
+ getManager(FreeplugManager.class).reboot(getMac());
+ logger.debug("Freeplug {} succesfully restarted", getMac());
+ } catch (FreeboxException e) {
+ logger.warn("Error restarting freeplug: {}", e.getMessage());
+ }
+ }
+
+ @Override
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ return Collections.singleton(FreeplugActions.class);
+ }
+}
--- /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.handler;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager;
+import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager.Config;
+import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager.Status;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link FxsHandler} is responsible for handling everything associated to the landline associated with the
+ * Freebox Server.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class FxsHandler extends ApiConsumerHandler {
+ private final Logger logger = LoggerFactory.getLogger(FxsHandler.class);
+
+ public FxsHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ void initializeProperties(Map<String, String> properties) throws FreeboxException {
+ getManager(PhoneManager.class).getStatus(getClientId())
+ .ifPresent(status -> properties.put(Thing.PROPERTY_VENDOR, status.vendor()));
+ }
+
+ @Override
+ protected void internalPoll() throws FreeboxException {
+ logger.debug("Polling landline status...");
+
+ Config config = getManager(PhoneManager.class).getConfig();
+ updateConfigChannels(config);
+
+ getManager(PhoneManager.class).getStatus(getClientId()).ifPresent(this::updateStatusChannels);
+ }
+
+ protected void updateConfigChannels(Config config) {
+ updateChannelString(TELEPHONY_SERVICE, config.network());
+ }
+
+ protected void updateStatusChannels(Status status) {
+ updateChannelOnOff(ONHOOK, status.onHook());
+ updateChannelOnOff(RINGING, status.isRinging());
+ updateChannelString(HARDWARE_STATUS, status.hardwareDefect() ? "KO" : "OK");
+ updateStatus(ThingStatus.ONLINE);
+ }
+
+ @Override
+ protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
+ if (RINGING.equals(channelId) && command instanceof OnOffType) {
+ getManager(PhoneManager.class).ringFxs(TRUE_COMMANDS.contains(command));
+ return true;
+ }
+ return super.internalHandleCommand(channelId, command);
+ }
+}
--- /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.handler;
+
+import java.math.BigDecimal;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Endpoint;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EpType;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.HomeNode;
+import org.openhab.binding.freeboxos.internal.config.ApiConsumerConfiguration;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link HomeNodeHandler} is the base class for handler of home node things.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public abstract class HomeNodeHandler extends ApiConsumerHandler {
+ private final Logger logger = LoggerFactory.getLogger(HomeNodeHandler.class);
+
+ public HomeNodeHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ void initializeProperties(Map<String, String> properties) throws FreeboxException {
+ HomeNode node = getManager(HomeManager.class).getHomeNode(getClientId());
+
+ // Gets the lowest refresh time or else, we'll keep configuration default
+ node.showEndpoints().stream().filter(ep -> ep.epType() == EpType.SIGNAL).filter(ep -> ep.refresh() != 0)
+ .min(Comparator.comparing(Endpoint::refresh)).map(Endpoint::refresh).ifPresent(rate -> {
+ Configuration thingConfig = editConfiguration();
+ thingConfig.put(ApiConsumerConfiguration.REFRESH_INTERVAL, Integer.toString(rate / 1000));
+ updateConfiguration(thingConfig);
+ });
+
+ properties.putAll(node.props());
+
+ getThing().getChannels().forEach(channel -> {
+ Configuration conf = channel.getConfiguration();
+ node.type().endpoints().stream().filter(ep -> ep.name().equals(channel.getUID().getIdWithoutGroup()))
+ .forEach(endPoint -> conf.put(endPoint.epType().asConfId(), endPoint.id()));
+ internalConfigureChannel(channel.getUID().getIdWithoutGroup(), conf, node.type().endpoints());
+ });
+ }
+
+ protected void internalConfigureChannel(String channelId, Configuration conf, List<Endpoint> endpoints) {
+ }
+
+ @Override
+ protected void internalPoll() throws FreeboxException {
+ HomeManager homeManager = getManager(HomeManager.class);
+ getThing().getChannels().stream().filter(channel -> isLinked(channel.getUID())).forEach(channel -> {
+ State result = UnDefType.UNDEF;
+ Integer slotId = getSlotId(channel.getConfiguration(), EpType.SIGNAL.asConfId());
+ if (slotId instanceof Integer) {
+ try {
+ EndpointState state = homeManager.getEndpointsState(getClientId(), slotId);
+ if (state != null) {
+ result = getChannelState(homeManager, channel.getUID().getIdWithoutGroup(), state);
+ } else {
+ result = getChannelState(homeManager, channel.getUID().getIdWithoutGroup());
+ }
+ } catch (FreeboxException e) {
+ logger.warn("Error updating channel: {}", e.getMessage());
+ }
+ } else {
+ result = getChannelState(homeManager, channel.getUID().getIdWithoutGroup());
+ }
+ updateState(channel.getUID(), result);
+ });
+ }
+
+ @Override
+ protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
+ Channel channel = getThing().getChannel(channelId);
+ if (channel != null) {
+ Configuration config = channel.getConfiguration();
+ String channelWG = channel.getUID().getIdWithoutGroup();
+ Integer slotId = getSlotId(config, EpType.SLOT.asConfId());
+ HomeManager homeManager = getManager(HomeManager.class);
+ return slotId instanceof Integer ? executeSlotCommand(homeManager, channelWG, command, config, slotId)
+ : executeChannelCommand(homeManager, channelWG, command, config);
+ }
+ return super.internalHandleCommand(channelId, command);
+ }
+
+ protected @Nullable Integer getSlotId(Configuration configuration, String endPoint) {
+ Object slot = configuration.get(endPoint);
+ return slot instanceof BigDecimal slotId ? slotId.intValue() : null;
+ }
+
+ protected boolean executeChannelCommand(HomeManager homeManager, String channelId, Command command,
+ Configuration config) throws FreeboxException {
+ return false;
+ }
+
+ protected boolean executeSlotCommand(HomeManager homeManager, String channelId, Command command,
+ Configuration config, int slotId) throws FreeboxException {
+ return false;
+ }
+
+ protected State getChannelState(HomeManager homeManager, String channelWG) {
+ return UnDefType.UNDEF;
+ }
+
+ protected abstract State getChannelState(HomeManager homeManager, String channelId, EndpointState state);
+}
--- /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.handler;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+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;
+import org.openhab.binding.freeboxos.internal.config.ApiConsumerConfiguration;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link HostHandler} is responsible for all network equipments hosted on the network
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+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
+ private boolean pushSubscribed = false;
+
+ public HostHandler(Thing thing) {
+ super(thing);
+ }
+
+ @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));
+ });
+ }
+
+ @Override
+ public void dispose() {
+ try {
+ getManager(WebSocketManager.class).unregisterListener(getMac());
+ } catch (FreeboxException e) {
+ logger.warn("Error unregistering host from the websocket: {}", e.getMessage());
+ }
+ super.dispose();
+ }
+
+ @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());
+ logger.debug("Switching to push mode - refreshInterval will now be ignored for Connectivity data");
+ getManager(WebSocketManager.class).registerListener(data.host().getMac(), this);
+ pushSubscribed = true;
+ }
+
+ public void updateConnectivityChannels(LanHost host) {
+ updateChannelOnOff(CONNECTIVITY, REACHABLE, host.reachable());
+ updateChannelDateTimeState(CONNECTIVITY, LAST_SEEN, host.getLastSeen());
+ updateChannelString(CONNECTIVITY, IP_ADDRESS, host.getIpv4());
+ updateStatus(host.reachable() ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
+ }
+
+ public void wol() {
+ try {
+ getManager(LanBrowserManager.class).wakeOnLan(getMac(),
+ getConfigAs(ApiConsumerConfiguration.class).password);
+ } catch (FreeboxException e) {
+ logger.warn("Error waking up host: {}", e.getMessage());
+ }
+ }
+
+ @Override
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ return Collections.singletonList(HostActions.class);
+ }
+}
--- /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.handler;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link KeyfobHandler} is responsible for handling everything associated to
+ * any Freebox Home keyfob thing type.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class KeyfobHandler extends HomeNodeHandler {
+
+ public KeyfobHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ protected State getChannelState(HomeManager homeManager, String channelId, EndpointState state) {
+ String value = state.value();
+ if (value != null) {
+ switch (channelId) {
+ case KEYFOB_ENABLE:
+ return OnOffType.from(state.asBoolean());
+ case NODE_BATTERY:
+ return DecimalType.valueOf(value);
+ }
+ }
+ return UnDefType.NULL;
+ }
+
+ @Override
+ protected boolean executeSlotCommand(HomeManager homeManager, String channelId, Command command,
+ Configuration config, int intValue) throws FreeboxException {
+ if (KEYFOB_ENABLE.equals(channelId) && command instanceof OnOffType onOff) {
+ return getManager(HomeManager.class).putCommand(getClientId(), intValue, onOff);
+ }
+ return false;
+ }
+}
--- /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.handler;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.KEY_CODE;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.action.PlayerActions;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.LanHost;
+import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager;
+import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Player;
+import org.openhab.binding.freeboxos.internal.config.PlayerConfiguration;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import inet.ipaddr.IPAddress;
+
+/**
+ * The {@link PlayerHandler} is responsible for handling everything associated to any Freebox Player thing type.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class PlayerHandler extends HostHandler {
+ private static final List<String> VALID_REMOTE_KEYS = Arrays.asList("red", "green", "blue", "yellow", "power",
+ "list", "tv", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "vol_inc", "vol_dec", "mute", "prgm_inc",
+ "prgm_dec", "prev", "bwd", "play", "rec", "fwd", "next", "up", "right", "down", "left", "back", "swap",
+ "info", "epg", "mail", "media", "help", "options", "pip", "ok", "home");
+
+ private final Logger logger = LoggerFactory.getLogger(PlayerHandler.class);
+ private @Nullable IPAddress ipAddress;
+
+ public PlayerHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ void initializeProperties(Map<String, String> properties) throws FreeboxException {
+ super.initializeProperties(properties);
+ Player player = getManager(PlayerManager.class).getDevice(getClientId());
+ properties.put(Thing.PROPERTY_MODEL_ID, player.deviceModel().name());
+ }
+
+ @Override
+ protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
+ if (KEY_CODE.equals(channelId) && command instanceof StringType) {
+ sendKey(command.toString(), false, 1);
+ return true;
+ }
+
+ return super.internalHandleCommand(channelId, command);
+ }
+
+ @Override
+ public void updateConnectivityChannels(LanHost host) {
+ super.updateConnectivityChannels(host);
+ ipAddress = host.getIpv4();
+ }
+
+ public void sendKey(String key, boolean longPress, int count) {
+ String aKey = key.toLowerCase();
+ IPAddress ip = ipAddress;
+ if (ip == null) {
+ logger.warn("Player IP is unknown");
+ } else if (VALID_REMOTE_KEYS.contains(aKey)) {
+ String remoteCode = (String) getConfig().get(PlayerConfiguration.REMOTE_CODE);
+ if (remoteCode != null) {
+ try {
+ getManager(PlayerManager.class).sendKey(ip.toCanonicalString(), remoteCode, aKey, longPress, count);
+ } catch (FreeboxException e) {
+ logger.warn("Error sending key: {}", e.getMessage());
+ }
+ } else {
+ logger.warn("A remote code must be configured in the on the player thing.");
+ }
+ } else {
+ logger.warn("Key '{}' is not a valid key expression", key);
+ }
+ }
+
+ public void sendMultipleKeys(String keys) {
+ String[] keyChain = keys.split(",");
+ Arrays.stream(keyChain).forEach(key -> {
+ sendKey(key, false, 1);
+ });
+ }
+
+ @Override
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ return Collections.singletonList(PlayerActions.class);
+ }
+}
--- /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.handler;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.action.RepeaterActions;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+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.api.rest.RepeaterManager.Repeater;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link RepeaterHandler} is responsible for interface to a freebox
+ * pop repeater.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class RepeaterHandler extends HostHandler implements FreeDeviceIntf {
+ private final Logger logger = LoggerFactory.getLogger(RepeaterHandler.class);
+ private long uptime = -1;
+ private final ChannelUID eventChannelUID;
+
+ public RepeaterHandler(Thing thing) {
+ super(thing);
+ eventChannelUID = new ChannelUID(getThing().getUID(), REPEATER_MISC, BOX_EVENT);
+ }
+
+ @Override
+ void initializeProperties(Map<String, String> properties) throws FreeboxException {
+ super.initializeProperties(properties);
+
+ Repeater repeater = getManager(RepeaterManager.class).getDevice(getClientId());
+ properties.put(Thing.PROPERTY_SERIAL_NUMBER, repeater.sn());
+ properties.put(Thing.PROPERTY_FIRMWARE_VERSION, repeater.firmwareVersion());
+ properties.put(Thing.PROPERTY_MODEL_ID, repeater.model().name());
+ }
+
+ @Override
+ protected void internalPoll() throws FreeboxException {
+ super.internalPoll();
+
+ if (!thing.getStatus().equals(ThingStatus.ONLINE)) {
+ return;
+ }
+
+ logger.debug("Polling Repeater status");
+ RepeaterManager repeaterManager = getManager(RepeaterManager.class);
+
+ Repeater repeater = repeaterManager.getDevice(getClientId());
+ updateChannelOnOff(REPEATER_MISC, LED, repeater.ledActivated());
+ updateChannelString(REPEATER_MISC, CONNECTION_STATUS, repeater.connection());
+
+ List<LanHost> hosts = repeaterManager.getRepeaterHosts(getClientId());
+ updateChannelDecimal(REPEATER_MISC, HOST_COUNT, hosts.size());
+
+ uptime = checkUptimeAndFirmware(repeater.getUptimeVal(), uptime, repeater.firmwareVersion());
+ updateChannelQuantity(REPEATER_MISC, UPTIME, uptime, Units.SECOND);
+ }
+
+ @Override
+ protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
+ if (ON_OFF_CLASSES.contains(command.getClass()) && LED.equals(channelId)) {
+ getManager(RepeaterManager.class).led(getClientId(), TRUE_COMMANDS.contains(command))
+ .ifPresent(repeater -> updateChannelOnOff(REPEATER_MISC, LED, repeater.ledActivated()));
+ }
+ return super.internalHandleCommand(channelId, command);
+ }
+
+ public void reboot() {
+ processReboot(() -> {
+ try {
+ getManager(RepeaterManager.class).reboot(getClientId());
+ } catch (FreeboxException e) {
+ logger.warn("Error rebooting: {}", e.getMessage());
+ }
+ });
+ }
+
+ @Override
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ return Collections.singleton(RepeaterActions.class);
+ }
+
+ @Override
+ public ChannelUID getEventChannelUID() {
+ return eventChannelUID;
+ }
+
+ @Override
+ public void triggerChannel(ChannelUID channelUID, String event) {
+ super.triggerChannel(channelUID, event);
+ }
+}
--- /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.handler;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+import static org.openhab.core.library.unit.Units.PERCENT;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.rest.LcdManager;
+import org.openhab.binding.freeboxos.internal.api.rest.LcdManager.Config;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.IncreaseDecreaseType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link RevolutionHandler} is responsible for handling take care of revolution server specifics
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class RevolutionHandler extends ServerHandler {
+ private final Logger logger = LoggerFactory.getLogger(RevolutionHandler.class);
+
+ public RevolutionHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
+ LcdManager manager = getManager(LcdManager.class);
+ Config config = manager.getConfig();
+ switch (channelId) {
+ case LCD_BRIGHTNESS:
+ setBrightness(manager, config, command);
+ internalPoll();
+ return true;
+ case LCD_ORIENTATION:
+ setOrientation(manager, config, command);
+ internalPoll();
+ return true;
+ case LCD_FORCED:
+ setForced(manager, config, command);
+ internalPoll();
+ return true;
+ }
+ return super.internalHandleCommand(channelId, command);
+ }
+
+ @Override
+ protected void internalPoll() throws FreeboxException {
+ super.internalPoll();
+ Config config = getManager(LcdManager.class).getConfig();
+ updateChannelQuantity(DISPLAY, LCD_BRIGHTNESS, config.brightness(), PERCENT);
+ updateChannelDecimal(DISPLAY, LCD_ORIENTATION, config.orientation());
+ updateChannelOnOff(DISPLAY, LCD_FORCED, config.orientationForced());
+ }
+
+ private void setOrientation(LcdManager manager, Config config, Command command) throws FreeboxException {
+ if (command instanceof DecimalType) {
+ manager.setOrientation(((DecimalType) command).intValue());
+ } else {
+ logger.warn("Invalid command {} from channel {}", command, LCD_ORIENTATION);
+ }
+ }
+
+ private void setForced(LcdManager manager, Config config, Command command) throws FreeboxException {
+ if (ON_OFF_CLASSES.contains(command.getClass())) {
+ manager.setOrientationForced(TRUE_COMMANDS.contains(command));
+ } else {
+ logger.warn("Invalid command {} from channel {}", command, LCD_FORCED);
+ }
+ }
+
+ private void setBrightness(LcdManager manager, Config config, Command command) throws FreeboxException {
+ if (command instanceof IncreaseDecreaseType) {
+ manager.setBrightness(() -> config.brightness() + (command == IncreaseDecreaseType.INCREASE ? 1 : -1));
+ } else if (command instanceof OnOffType) {
+ manager.setBrightness(() -> command == OnOffType.ON ? 100 : 0);
+ } else if (command instanceof QuantityType) {
+ manager.setBrightness(() -> ((QuantityType<?>) command).intValue());
+ } else if (command instanceof DecimalType || command instanceof PercentType) {
+ manager.setBrightness(() -> ((DecimalType) command).intValue());
+ } else {
+ logger.warn("Invalid command {} from channel {}", command, LCD_BRIGHTNESS);
+ }
+ }
+}
--- /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.handler;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+import static org.openhab.core.library.unit.Units.*;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.action.ServerActions;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.rest.AfpManager;
+import org.openhab.binding.freeboxos.internal.api.rest.AirMediaManager;
+import org.openhab.binding.freeboxos.internal.api.rest.ConnectionManager;
+import org.openhab.binding.freeboxos.internal.api.rest.ConnectionManager.Status;
+import org.openhab.binding.freeboxos.internal.api.rest.FtpManager;
+import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.Source;
+import org.openhab.binding.freeboxos.internal.api.rest.LanManager;
+import org.openhab.binding.freeboxos.internal.api.rest.LanManager.LanConfig;
+import org.openhab.binding.freeboxos.internal.api.rest.SambaManager;
+import org.openhab.binding.freeboxos.internal.api.rest.SambaManager.Samba;
+import org.openhab.binding.freeboxos.internal.api.rest.SystemManager;
+import org.openhab.binding.freeboxos.internal.api.rest.SystemManager.Config;
+import org.openhab.binding.freeboxos.internal.api.rest.UPnPAVManager;
+import org.openhab.binding.freeboxos.internal.api.rest.WifiManager;
+import org.openhab.core.library.CoreItemFactory;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.thing.binding.builder.ChannelBuilder;
+import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link ServerHandler} handle common parts of Freebox bridges.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class ServerHandler extends ApiConsumerHandler implements FreeDeviceIntf {
+ private static final BigDecimal HUNDRED = BigDecimal.valueOf(100);
+
+ private final Logger logger = LoggerFactory.getLogger(ServerHandler.class);
+ private final ChannelUID eventChannelUID;
+
+ private long uptime = -1;
+
+ public ServerHandler(Thing thing) {
+ super(thing);
+ eventChannelUID = new ChannelUID(getThing().getUID(), SYS_INFO, BOX_EVENT);
+ }
+
+ @Override
+ void initializeProperties(Map<String, String> properties) throws FreeboxException {
+ LanConfig lanConfig = getManager(LanManager.class).getConfig();
+ Config config = getManager(SystemManager.class).getConfig();
+ properties.put(Thing.PROPERTY_SERIAL_NUMBER, config.serial());
+ properties.put(Thing.PROPERTY_FIRMWARE_VERSION, config.firmwareVersion());
+ properties.put(Thing.PROPERTY_HARDWARE_VERSION, config.modelInfo().prettyName());
+ properties.put(Source.UPNP.name(), lanConfig.name());
+
+ List<Channel> channels = new ArrayList<>(getThing().getChannels());
+ config.sensors().forEach(sensor -> {
+ ChannelUID sensorId = new ChannelUID(thing.getUID(), GROUP_SENSORS, sensor.id());
+ channels.add(
+ ChannelBuilder.create(sensorId).withLabel(sensor.name()).withAcceptedItemType("Number:Temperature")
+ .withType(new ChannelTypeUID(BINDING_ID + ":temperature")).build());
+ });
+ config.fans().forEach(sensor -> {
+ ChannelUID sensorId = new ChannelUID(thing.getUID(), GROUP_FANS, sensor.id());
+ channels.add(ChannelBuilder.create(sensorId).withLabel(sensor.name())
+ .withAcceptedItemType(CoreItemFactory.NUMBER).withType(new ChannelTypeUID(BINDING_ID + ":fanspeed"))
+ .build());
+ });
+ updateThing(editThing().withChannels(channels).build());
+ }
+
+ @Override
+ protected void internalPoll() throws FreeboxException {
+ logger.debug("Polling server state...");
+ fetchConnectionStatus();
+ fetchSystemConfig();
+
+ updateChannelOnOff(ACTIONS, WIFI_STATUS, getManager(WifiManager.class).getStatus());
+ updateChannelOnOff(ACTIONS, AIRMEDIA_STATUS, getManager(AirMediaManager.class).getStatus());
+ updateChannelOnOff(ACTIONS, UPNPAV_STATUS, getManager(UPnPAVManager.class).getStatus());
+
+ Samba response = getManager(SambaManager.class).getConfig();
+ updateChannelOnOff(FILE_SHARING, SAMBA_FILE_STATUS, response.fileShareEnabled());
+ updateChannelOnOff(FILE_SHARING, SAMBA_PRINTER_STATUS, response.printShareEnabled());
+ updateChannelOnOff(FILE_SHARING, FTP_STATUS, getManager(FtpManager.class).getStatus());
+ updateChannelOnOff(FILE_SHARING, AFP_FILE_STATUS, getManager(AfpManager.class).getStatus());
+ }
+
+ private void fetchSystemConfig() throws FreeboxException {
+ Config config = getManager(SystemManager.class).getConfig();
+
+ config.sensors().forEach(s -> updateChannelQuantity(GROUP_SENSORS, s.id(), s.value(), SIUnits.CELSIUS));
+ config.fans().forEach(f -> updateChannelQuantity(GROUP_FANS, f.id(), f.value(), Units.RPM));
+
+ uptime = checkUptimeAndFirmware(config.uptimeVal(), uptime, config.firmwareVersion());
+ updateChannelQuantity(SYS_INFO, UPTIME, uptime, Units.SECOND);
+
+ LanConfig lanConfig = getManager(LanManager.class).getConfig();
+ updateChannelString(SYS_INFO, IP_ADDRESS, lanConfig.ip());
+ }
+
+ private void fetchConnectionStatus() throws FreeboxException {
+ Status status = getManager(ConnectionManager.class).getConfig();
+ updateChannelString(CONNECTION_STATUS, LINE_STATUS, status.state());
+ updateChannelString(CONNECTION_STATUS, LINE_TYPE, status.type());
+ updateChannelString(CONNECTION_STATUS, LINE_MEDIA, status.media());
+ updateChannelString(CONNECTION_STATUS, IP_ADDRESS, status.ipv4());
+ updateChannelString(CONNECTION_STATUS, IPV6_ADDRESS, status.ipv6());
+
+ updateRateBandwidth(status.rateUp(), status.bandwidthUp(), "up");
+ updateRateBandwidth(status.rateDown(), status.bandwidthDown(), "down");
+
+ updateChannelQuantity(CONNECTION_STATUS, BYTES_UP, new QuantityType<>(status.bytesUp(), OCTET), GIBIOCTET);
+ updateChannelQuantity(CONNECTION_STATUS, BYTES_DOWN, new QuantityType<>(status.bytesDown(), OCTET), GIBIOCTET);
+ }
+
+ private void updateRateBandwidth(long rate, long bandwidth, String orientation) {
+ QuantityType<?> rateUp = new QuantityType<>(rate * 8, Units.BIT_PER_SECOND);
+ QuantityType<?> bandwidthUp = new QuantityType<>(bandwidth, BIT_PER_SECOND);
+ updateChannelQuantity(CONNECTION_STATUS, RATE + "-" + orientation, rateUp, KILOBIT_PER_SECOND);
+ updateChannelQuantity(CONNECTION_STATUS, BW + "-" + orientation, bandwidthUp, KILOBIT_PER_SECOND);
+ updateChannelQuantity(CONNECTION_STATUS, PCT_BW + "-" + orientation,
+ !bandwidthUp.equals(QuantityType.ZERO) ? rateUp.multiply(HUNDRED).divide(bandwidthUp)
+ : QuantityType.ZERO,
+ Units.PERCENT);
+ }
+
+ @Override
+ protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
+ if (ON_OFF_CLASSES.contains(command.getClass())) {
+ boolean enable = TRUE_COMMANDS.contains(command);
+ switch (channelId) {
+ case WIFI_STATUS:
+ updateChannelOnOff(ACTIONS, WIFI_STATUS, getManager(WifiManager.class).setStatus(enable));
+ return true;
+ case FTP_STATUS:
+ updateChannelOnOff(FILE_SHARING, FTP_STATUS, getManager(FtpManager.class).setStatus(enable));
+ return true;
+ case SAMBA_FILE_STATUS:
+ updateChannelOnOff(FILE_SHARING, SAMBA_FILE_STATUS,
+ getManager(SambaManager.class).setFileShare(enable));
+ return true;
+ case SAMBA_PRINTER_STATUS:
+ updateChannelOnOff(FILE_SHARING, SAMBA_PRINTER_STATUS,
+ getManager(SambaManager.class).setPrintShare(enable));
+ return true;
+ case UPNPAV_STATUS:
+ updateChannelOnOff(ACTIONS, UPNPAV_STATUS, getManager(UPnPAVManager.class).setStatus(enable));
+ return true;
+ case AFP_FILE_STATUS:
+ updateChannelOnOff(FILE_SHARING, AFP_FILE_STATUS, getManager(AfpManager.class).setStatus(enable));
+ return true;
+ case AIRMEDIA_STATUS:
+ updateChannelOnOff(ACTIONS, AIRMEDIA_STATUS, getManager(AirMediaManager.class).setStatus(enable));
+ return true;
+ default:
+ break;
+ }
+ }
+ return super.internalHandleCommand(channelId, command);
+ }
+
+ public void reboot() {
+ processReboot(() -> {
+ try {
+ getManager(SystemManager.class).reboot();
+ } catch (FreeboxException e) {
+ logger.warn("Error rebooting: {}", e.getMessage());
+ }
+ });
+ }
+
+ @Override
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ return Collections.singleton(ServerActions.class);
+ }
+
+ @Override
+ public ChannelUID getEventChannelUID() {
+ return eventChannelUID;
+ }
+
+ @Override
+ public void triggerChannel(ChannelUID channelUID, String event) {
+ super.triggerChannel(channelUID, event);
+ }
+}
--- /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.handler;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Endpoint;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StopMoveType;
+import org.openhab.core.library.types.UpDownType;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link ShutterHandler} is responsible for handling everything associated to any Freebox Home shutter.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class ShutterHandler extends HomeNodeHandler {
+
+ public ShutterHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ protected void internalConfigureChannel(String channelId, Configuration conf, List<Endpoint> endpoints) {
+ endpoints.stream().filter(ep -> channelId.equals(SHUTTER_POSITION) && ep.name().equals(SHUTTER_STOP))
+ .forEach(endPoint -> conf.put(endPoint.name(), endPoint.id()));
+ }
+
+ @Override
+ protected State getChannelState(HomeManager homeManager, String channelId, EndpointState state) {
+ String value = state.value();
+ return value != null && channelId.equals(SHUTTER_POSITION) ? QuantityType.valueOf(value + " %")
+ : UnDefType.NULL;
+ }
+
+ @Override
+ protected boolean executeSlotCommand(HomeManager homeManager, String channelId, Command command,
+ Configuration config, int positionSlot) throws FreeboxException {
+ Integer stopSlot = getSlotId(config, SHUTTER_STOP);
+ if (SHUTTER_POSITION.equals(channelId) && stopSlot instanceof Integer) {
+ if (command instanceof UpDownType upDownCmd) {
+ return operateShutter(homeManager, stopSlot, positionSlot, upDownCmd == UpDownType.DOWN ? 100 : 0);
+ } else if (command instanceof StopMoveType stopMove && stopMove == StopMoveType.STOP) {
+ return operateShutter(homeManager, stopSlot, positionSlot, -1);
+ } else if (command instanceof Number numberCmd) {
+ return operateShutter(homeManager, stopSlot, positionSlot, numberCmd.intValue());
+ }
+ }
+ return false;
+ }
+
+ private boolean operateShutter(HomeManager homeManager, int stopSlot, int positionSlot, int target)
+ throws FreeboxException {
+ homeManager.putCommand(getClientId(), stopSlot, true);
+ if (target >= 0) {
+ homeManager.putCommand(getClientId(), positionSlot, target);
+ }
+ return true;
+ }
+}
--- /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.handler;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+import org.openhab.binding.freeboxos.internal.api.rest.VmManager;
+import org.openhab.binding.freeboxos.internal.api.rest.VmManager.Status;
+import org.openhab.binding.freeboxos.internal.api.rest.VmManager.VirtualMachine;
+import org.openhab.binding.freeboxos.internal.api.rest.WebSocketManager;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link VmHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class VmHandler extends HostHandler {
+ private final Logger logger = LoggerFactory.getLogger(VmHandler.class);
+
+ // We start in pull mode and switch to push after a first update
+ private boolean pushSubscribed = false;
+
+ public VmHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void dispose() {
+ try {
+ getManager(WebSocketManager.class).unregisterVm(getClientId());
+ } catch (FreeboxException e) {
+ logger.warn("Error unregistering VM from the websocket: {}", e.getMessage());
+ }
+ super.dispose();
+ }
+
+ @Override
+ protected void internalPoll() throws FreeboxException {
+ if (pushSubscribed) {
+ return;
+ }
+ super.internalPoll();
+
+ logger.debug("Polling Virtual machine status");
+ VirtualMachine vm = getManager(VmManager.class).getDevice(getClientId());
+ updateVmChannels(vm);
+ getManager(WebSocketManager.class).registerVm(vm.id(), this);
+ pushSubscribed = true;
+ }
+
+ public void updateVmChannels(VirtualMachine vm) {
+ boolean running = Status.RUNNING.equals(vm.status());
+ updateChannelOnOff(VM_STATUS, STATUS, running);
+ updateChannelOnOff(CONNECTIVITY, REACHABLE, running);
+ updateStatus(running ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
+ }
+
+ @Override
+ protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
+ if (STATUS.equals(channelId) && command instanceof OnOffType) {
+ getManager(VmManager.class).power(getClientId(), OnOffType.ON.equals(command));
+ return true;
+ }
+ return super.internalHandleCommand(channelId, command);
+ }
+}
--- /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.handler;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+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.LanHost;
+import org.openhab.binding.freeboxos.internal.api.rest.RepeaterManager;
+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;
+
+/**
+ * The {@link WifiStationHandler} is responsible for handling everything associated to
+ * any Freebox thing types except the bridge thing type.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class WifiStationHandler extends HostHandler {
+ private static final String SERVER_HOST = "Server";
+
+ public WifiStationHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ protected void internalPoll() throws FreeboxException {
+ super.internalPoll();
+
+ // Search if the wifi-host is hosted on server access-points
+ Optional<Station> station = getManager(APManager.class).getStation(getMac());
+ if (station.isPresent()) {
+ Station data = station.get();
+ updateChannelDateTimeState(CONNECTIVITY, LAST_SEEN, data.getLastSeen());
+ updateChannelString(GROUP_WIFI, WIFI_HOST, SERVER_HOST);
+ updateWifiStationChannels(data.signal(), data.getSsid(), data.rxRate(), data.txRate());
+ return;
+ }
+
+ // Search if it is hosted by a repeater
+ Optional<LanHost> wifiHost = getManager(RepeaterManager.class).getHost(getMac());
+ if (wifiHost.isPresent()) {
+ updateChannelDateTimeState(CONNECTIVITY, LAST_SEEN, wifiHost.get().getLastSeen());
+ LanAccessPoint lanAp = wifiHost.get().accessPoint();
+ if (lanAp != null) {
+ updateChannelString(GROUP_WIFI, WIFI_HOST, "%s-%s".formatted(lanAp.type(), lanAp.uid()));
+ updateWifiStationChannels(lanAp.getSignal(), lanAp.getSsid(), lanAp.rxRate(), lanAp.txRate());
+ return;
+ }
+ }
+ // Not found a wifi repeater/host, so update all wifi channels to NULL
+ getThing().getChannelsOfGroup(GROUP_WIFI).stream().map(Channel::getUID).filter(uid -> isLinked(uid))
+ .forEach(uid -> updateState(uid, UnDefType.NULL));
+ }
+
+ private void updateWifiStationChannels(int rssi, @Nullable String ssid, long rxRate, long txRate) {
+ updateChannelString(GROUP_WIFI, SSID, ssid);
+ updateChannelQuantity(GROUP_WIFI, RSSI, rssi <= 0 ? new QuantityType<>(rssi, Units.DECIBEL_MILLIWATTS) : null);
+ updateChannelDecimal(GROUP_WIFI, WIFI_QUALITY, rssi <= 0 ? toQoS(rssi) : null);
+ updateRateChannel(RATE + "-down", rxRate);
+ updateRateChannel(RATE + "-up", txRate);
+ }
+
+ private void updateRateChannel(String channel, long rate) {
+ QuantityType<?> qtty = rate != -1 ? new QuantityType<>(rate * 8, Units.BIT_PER_SECOND) : null;
+ updateChannelQuantity(GROUP_WIFI, channel, qtty);
+ }
+
+ private int toQoS(int rssi) {
+ return rssi > -50 ? 4 : rssi > -60 ? 3 : rssi > -70 ? 2 : rssi > -85 ? 1 : 0;
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<addon:addon id="freeboxos" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
+
+ <type>binding</type>
+ <name>Freebox OS Binding</name>
+ <description>The Freebox OS binding integrates Free equipments in your home automation.</description>
+ <connection>local</connection>
+ <countries>fr,it</countries>
+
+ <config-description>
+ <parameter name="timeout" type="integer" required="false" min="1" unit="s">
+ <label>Timeout</label>
+ <description>The timeout for reading from the API in seconds.</description>
+ <default>8</default>
+ </parameter>
+ <parameter name="callbackUrl" type="text" required="false">
+ <label>Callback URL</label>
+ <description>URL to use for playing notification sounds hosted by openHAB, e.g. 'http://192.168.0.2:8080'</description>
+ </parameter>
+ </config-description>
+
+</addon:addon>
--- /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="bridge-type:freeboxos:api">
+ <parameter name="apiDomain" type="text">
+ <label>Freebox Server Address</label>
+ <context>network-address</context>
+ <description>The domain to use in place of hardcoded Freebox ip</description>
+ <default>mafreebox.freebox.fr</default>
+ </parameter>
+ <parameter name="appToken" type="text" required="false">
+ <label>Application Token</label>
+ <context>password</context>
+ <description>Token generated by the Freebox server</description>
+ </parameter>
+ <parameter name="discoverNetDevice" type="boolean">
+ <label>Network Device Discovery</label>
+ <description>Enable the discovery of network device things</description>
+ <default>false</default>
+ </parameter>
+ <parameter name="httpsAvailable" type="boolean">
+ <label>HTTPS Available</label>
+ <description>Tells if https has been configured on the Freebox</description>
+ <advanced>true</advanced>
+ <default>false</default>
+ </parameter>
+ <parameter name="httpsPort" type="integer">
+ <label>HTTPS port</label>
+ <description>Port to use for remote https access to the Freebox Api</description>
+ <advanced>true</advanced>
+ <default>15682</default>
+ </parameter>
+ </config-description>
+
+</config-description:config-descriptions>
--- /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:home-node">
+ <parameter name="id" type="integer" required="true">
+ <label>ID</label>
+ <description>Id of the Home Node</description>
+ <default>1</default>
+ </parameter>
+ <parameter name="refreshInterval" type="integer" min="1" unit="s">
+ <label>Refresh Interval</label>
+ <description>The refresh interval in seconds which is used to poll the Node</description>
+ <default>30</default>
+ </parameter>
+ </config-description>
+
+</config-description:config-descriptions>
--- /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: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>
+ </config-description>
+
+</config-description:config-descriptions>
--- /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:phone">
+ <parameter name="refreshInterval" type="integer" min="1" unit="s">
+ <label>State Refresh Interval</label>
+ <description>The refresh interval in seconds which is used to poll for phone state.</description>
+ <default>30</default>
+ </parameter>
+ <parameter name="id" type="integer">
+ <label>ID</label>
+ <description>Id of the phone line</description>
+ <default>1</default>
+ </parameter>
+ </config-description>
+
+ <config-description uri="thing-type:freeboxos:call">
+ <parameter name="refreshInterval" type="integer" min="1" unit="s">
+ <label>State Refresh Interval</label>
+ <description>The refresh interval in seconds which is used to poll for phone state.</description>
+ <default>2</default>
+ </parameter>
+ </config-description>
+
+
+</config-description:config-descriptions>
--- /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:player">
+ <parameter name="macAddress" type="text" required="true" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})">
+ <label>MAC Address</label>
+ <description>The MAC address of the player device</description>
+ </parameter>
+ <parameter name="id" type="integer">
+ <label>ID</label>
+ <description>Id of the player</description>
+ <default>1</default>
+ </parameter>
+ <parameter name="port" type="integer">
+ <label>Player port</label>
+ <default>24322</default>
+ <advanced>true</advanced>
+ </parameter>
+ <parameter name="password" type="text" required="false">
+ <context>password</context>
+ <label>Password</label>
+ <description>AirPlay password</description>
+ <advanced>true</advanced>
+ </parameter>
+ <parameter name="remoteCode" type="text" required="false">
+ <label>Remote Code</label>
+ <description>Code associated to remote control</description>
+ <advanced>true</advanced>
+ </parameter>
+ <parameter name="acceptAllMp3" type="boolean" required="false">
+ <label>Accept All MP3</label>
+ <description>Accept any bitrate for MP3 audio or only bitrates greater than 64 kbps</description>
+ <default>true</default>
+ <advanced>true</advanced>
+ </parameter>
+ <parameter name="refreshInterval" type="integer" min="1" unit="s">
+ <label>Refresh Interval</label>
+ <description>The refresh interval in seconds which is used to poll the player</description>
+ <default>30</default>
+ </parameter>
+ </config-description>
+
+</config-description:config-descriptions>
--- /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:repeater">
+ <parameter name="refreshInterval" type="integer" min="1" unit="s">
+ <label>Refresh Interval</label>
+ <description>The refresh interval in seconds which is used to poll the repeater</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="id" type="integer" required="true">
+ <label>ID</label>
+ <description>Id of the repeater</description>
+ <default>1</default>
+ </parameter>
+ </config-description>
+
+</config-description:config-descriptions>
--- /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:server">
+ <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 Freebox Server</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>
+ </config-description>
+
+</config-description:config-descriptions>
--- /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:vm">
+ <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 virtual machine</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="id" type="integer" required="true">
+ <label>ID</label>
+ <description>Id of the Virtual Machine</description>
+ </parameter>
+ </config-description>
+
+</config-description:config-descriptions>
--- /dev/null
+# add-on
+
+addon.freeboxos.name = Freebox OS Binding
+addon.freeboxos.description = The Freebox OS binding integrates Free equipments in your home automation.
+
+# add-on config
+
+addon.config.freeboxos.callbackUrl.label = Callback URL
+addon.config.freeboxos.callbackUrl.description = URL to use for playing notification sounds hosted by openHAB, e.g. 'http://192.168.0.2:8080'
+addon.config.freeboxos.timeout.label = Timeout
+addon.config.freeboxos.timeout.description = The timeout for reading from the API in seconds.
+
+# thing types
+
+thing-type.freeboxos.active-player.label = Freebox Player
+thing-type.freeboxos.active-player.description = The player is the device connected to your TV with API capabilities
+thing-type.freeboxos.alarm.label = Freebox Alarm
+thing-type.freeboxos.alarm.description = The Alarm system configured in your Freebox Delta Server
+thing-type.freeboxos.alarm.channel.sound.label = Bips Volume
+thing-type.freeboxos.alarm.channel.timeout1.label = Alarm Activation Duration
+thing-type.freeboxos.alarm.channel.timeout2.label = Safe Zone Alert Timeout
+thing-type.freeboxos.alarm.channel.timeout3.label = Alert Duration
+thing-type.freeboxos.alarm.channel.volume.label = Alarm Volume
+thing-type.freeboxos.api.label = Freebox OS Api
+thing-type.freeboxos.api.description = Bridge between hosts and the API rest service
+thing-type.freeboxos.basic-shutter.label = Freebox Home Basic Shutter
+thing-type.freeboxos.basic-shutter.description = The Basic Shutter (UP,DOWN,STOP) configured in your Freebox Delta Server
+thing-type.freeboxos.call.label = Calls
+thing-type.freeboxos.call.description = Provides various informations regarding the phone calls
+thing-type.freeboxos.dect.label = DECT
+thing-type.freeboxos.dect.description = Provides various informations regarding the DECT state and configuration
+thing-type.freeboxos.dect.channel.gain-rx.label = Gain RX
+thing-type.freeboxos.dect.channel.gain-tx.label = Gain TX
+thing-type.freeboxos.delta.label = Freebox Delta
+thing-type.freeboxos.delta.description = Provides various informations regarding the status of the Freebox Delta Server
+thing-type.freeboxos.freeplug.label = Freeplug
+thing-type.freeboxos.freeplug.description = Ethernet / CPL gateway
+thing-type.freeboxos.freeplug.channel.last-seen.label = Last Activity
+thing-type.freeboxos.freeplug.channel.rate-down.label = Rx Rate
+thing-type.freeboxos.freeplug.channel.rate-down.description = Current RX rate
+thing-type.freeboxos.freeplug.channel.rate-up.label = Tx Rate
+thing-type.freeboxos.freeplug.channel.rate-up.description = Current TX Rate
+thing-type.freeboxos.fxs.label = Landline
+thing-type.freeboxos.fxs.description = Provides various informations regarding the landline state
+thing-type.freeboxos.host.label = Network Device
+thing-type.freeboxos.host.description = Provides network device reachability
+thing-type.freeboxos.kfb.label = Freebox Keyfob
+thing-type.freeboxos.kfb.description = A keyfob configured for your Freebox Security system
+thing-type.freeboxos.player.label = Freebox Player
+thing-type.freeboxos.player.description = The player is the device connected to your TV
+thing-type.freeboxos.repeater.label = Wifi Repeater
+thing-type.freeboxos.repeater.description = Provides informations and control over a Wifi Repeater
+thing-type.freeboxos.revolution.label = Freebox Revolution
+thing-type.freeboxos.revolution.description = Provides various informations regarding the status of the Freebox Revolution Server
+thing-type.freeboxos.shutter.label = Freebox Home Shutter
+thing-type.freeboxos.shutter.description = The Shutter configured in your Freebox Delta Server
+thing-type.freeboxos.vm.label = Virtual Machine
+thing-type.freeboxos.vm.description = Provides informations and control over virtual machine hosted on the server
+thing-type.freeboxos.wifihost.label = Wifi Device
+thing-type.freeboxos.wifihost.description = Provides Wifi device reachability
+
+# thing types config
+
+bridge-type.config.freeboxos.api.apiDomain.label = Freebox Server Address
+bridge-type.config.freeboxos.api.apiDomain.description = The domain to use in place of hardcoded Freebox ip
+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.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
+bridge-type.config.freeboxos.api.httpsPort.description = Port to use for remote https access to the Freebox Api
+thing-type.config.freeboxos.call.refreshInterval.label = State Refresh Interval
+thing-type.config.freeboxos.call.refreshInterval.description = The refresh interval in seconds which is used to poll for phone state.
+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.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
+thing-type.config.freeboxos.host.refreshInterval.description = The refresh interval in seconds which is used to poll given device
+thing-type.config.freeboxos.phone.id.label = ID
+thing-type.config.freeboxos.phone.id.description = Id of the phone line
+thing-type.config.freeboxos.phone.refreshInterval.label = State Refresh Interval
+thing-type.config.freeboxos.phone.refreshInterval.description = The refresh interval in seconds which is used to poll for phone state.
+thing-type.config.freeboxos.player.acceptAllMp3.label = Accept All MP3
+thing-type.config.freeboxos.player.acceptAllMp3.description = Accept any bitrate for MP3 audio or only bitrates greater than 64 kbps
+thing-type.config.freeboxos.player.id.label = ID
+thing-type.config.freeboxos.player.id.description = Id of the player
+thing-type.config.freeboxos.player.macAddress.label = MAC Address
+thing-type.config.freeboxos.player.macAddress.description = The MAC address of the player device
+thing-type.config.freeboxos.player.password.label = Password
+thing-type.config.freeboxos.player.password.description = AirPlay password
+thing-type.config.freeboxos.player.port.label = Player port
+thing-type.config.freeboxos.player.refreshInterval.label = Refresh Interval
+thing-type.config.freeboxos.player.refreshInterval.description = The refresh interval in seconds which is used to poll the player
+thing-type.config.freeboxos.player.remoteCode.label = Remote Code
+thing-type.config.freeboxos.player.remoteCode.description = Code associated to remote control
+thing-type.config.freeboxos.repeater.id.label = ID
+thing-type.config.freeboxos.repeater.id.description = Id of the repeater
+thing-type.config.freeboxos.repeater.macAddress.label = MAC Address
+thing-type.config.freeboxos.repeater.macAddress.description = The MAC address of the network device
+thing-type.config.freeboxos.repeater.refreshInterval.label = Refresh Interval
+thing-type.config.freeboxos.repeater.refreshInterval.description = The refresh interval in seconds which is used to poll the repeater
+thing-type.config.freeboxos.server.macAddress.label = MAC Address
+thing-type.config.freeboxos.server.macAddress.description = The MAC address of the network device
+thing-type.config.freeboxos.server.refreshInterval.label = Refresh Interval
+thing-type.config.freeboxos.server.refreshInterval.description = The refresh interval in seconds which is used to poll given Freebox Server
+thing-type.config.freeboxos.vm.id.label = ID
+thing-type.config.freeboxos.vm.id.description = Id of the Virtual Machine
+thing-type.config.freeboxos.vm.macAddress.label = MAC Address
+thing-type.config.freeboxos.vm.macAddress.description = The MAC address of the network device
+thing-type.config.freeboxos.vm.refreshInterval.label = Refresh Interval
+thing-type.config.freeboxos.vm.refreshInterval.description = The refresh interval in seconds which is used to poll given virtual machine
+
+# channel group types
+
+channel-group-type.freeboxos.accepted.label = Accepted Call
+channel-group-type.freeboxos.accepted.description = The last accepted phone call
+channel-group-type.freeboxos.accepted.channel.duration.label = Incoming Call Duration
+channel-group-type.freeboxos.accepted.channel.name.label = Accepted Caller
+channel-group-type.freeboxos.accepted.channel.name.description = Caller name
+channel-group-type.freeboxos.accepted.channel.number.label = Calling Number
+channel-group-type.freeboxos.accepted.channel.number.description = Caller phone number
+channel-group-type.freeboxos.accepted.channel.timestamp.label = Incoming Call Timestamp
+channel-group-type.freeboxos.actions.label = Server Settings
+channel-group-type.freeboxos.connection-status.label = Connection Status Details
+channel-group-type.freeboxos.connection-status.channel.bandwidth-down.label = Bandwidth Down
+channel-group-type.freeboxos.connection-status.channel.bandwidth-down.description = Raw value of the download bandwidth currently used
+channel-group-type.freeboxos.connection-status.channel.bandwidth-up.label = Bandwidth Up
+channel-group-type.freeboxos.connection-status.channel.bandwidth-up.description = Raw value of the upload bandwidth currently used
+channel-group-type.freeboxos.connection-status.channel.bandwidth-usage-down.label = Bandwidth Usage Down
+channel-group-type.freeboxos.connection-status.channel.bandwidth-usage-down.description = Portion of the download bandwidth currently used
+channel-group-type.freeboxos.connection-status.channel.bandwidth-usage-up.label = Bandwidth Usage Up
+channel-group-type.freeboxos.connection-status.channel.bandwidth-usage-up.description = Portion of the upload bandwidth currently used
+channel-group-type.freeboxos.connection-status.channel.bytes-down.label = Downloaded
+channel-group-type.freeboxos.connection-status.channel.bytes-down.description = Total data downloaded since last restart
+channel-group-type.freeboxos.connection-status.channel.bytes-up.label = Uploaded
+channel-group-type.freeboxos.connection-status.channel.bytes-up.description = Total data uploaded since last restart
+channel-group-type.freeboxos.connection-status.channel.ip-address.label = Public IPv4
+channel-group-type.freeboxos.connection-status.channel.ip-address.description = Public IPv4 Address of the Freebox (this field is only available when connection state is up)
+channel-group-type.freeboxos.connection-status.channel.ipv6-address.label = Public IPv6
+channel-group-type.freeboxos.connection-status.channel.ipv6-address.description = Public IPv6 Address of the Freebox (this field is only available when connection state is up)
+channel-group-type.freeboxos.connection-status.channel.rate-down.label = Download Rate
+channel-group-type.freeboxos.connection-status.channel.rate-down.description = Current download rate
+channel-group-type.freeboxos.connection-status.channel.rate-up.label = Upload Rate
+channel-group-type.freeboxos.connection-status.channel.rate-up.description = Current upload rate
+channel-group-type.freeboxos.connectivity.label = Network Connectivity
+channel-group-type.freeboxos.connectivity.channel.ip-address.label = IP Address
+channel-group-type.freeboxos.connectivity.channel.ip-address.description = IPv4 Address of the host
+channel-group-type.freeboxos.connectivity.channel.last-seen.label = Last Activity
+channel-group-type.freeboxos.display.label = Front Display Panel
+channel-group-type.freeboxos.fans.label = Fans
+channel-group-type.freeboxos.file-sharing.label = File Sharing
+channel-group-type.freeboxos.incoming.label = Incoming Call
+channel-group-type.freeboxos.incoming.description = Currently presented phone call
+channel-group-type.freeboxos.incoming.channel.name.label = Incoming Caller
+channel-group-type.freeboxos.incoming.channel.name.description = Caller name
+channel-group-type.freeboxos.incoming.channel.number.label = Calling Number
+channel-group-type.freeboxos.incoming.channel.number.description = Caller phone number
+channel-group-type.freeboxos.incoming.channel.timestamp.label = Call Timestamp
+channel-group-type.freeboxos.missed.label = Missed Call
+channel-group-type.freeboxos.missed.description = The last missed phone call
+channel-group-type.freeboxos.missed.channel.name.label = Missed Caller
+channel-group-type.freeboxos.missed.channel.name.description = Caller name
+channel-group-type.freeboxos.missed.channel.number.label = Missed Calling Number
+channel-group-type.freeboxos.missed.channel.number.description = Caller phone number
+channel-group-type.freeboxos.missed.channel.timestamp.label = Missed Call Timestamp
+channel-group-type.freeboxos.outgoing.label = Outgoing Call
+channel-group-type.freeboxos.outgoing.description = The last outgoing phone call
+channel-group-type.freeboxos.outgoing.channel.duration.label = Outgoing Call Duration
+channel-group-type.freeboxos.outgoing.channel.name.label = Called Name
+channel-group-type.freeboxos.outgoing.channel.name.description = Name, if known, of the called person
+channel-group-type.freeboxos.outgoing.channel.number.label = Called Number
+channel-group-type.freeboxos.outgoing.channel.number.description = Called phone number
+channel-group-type.freeboxos.outgoing.channel.timestamp.label = Outgoing Call Timestamp
+channel-group-type.freeboxos.player-actions.label = Player Actions
+channel-group-type.freeboxos.player-status.label = Player Status
+channel-group-type.freeboxos.player-sysinfo.label = System Informations
+channel-group-type.freeboxos.repeater-misc.label = Repeater Settings
+channel-group-type.freeboxos.repeater-misc.channel.box-event.label = Repeater Event
+channel-group-type.freeboxos.sensors.label = System Sensors
+channel-group-type.freeboxos.sysinfo.label = System Informations
+channel-group-type.freeboxos.sysinfo.channel.ip-address.label = Internal IP
+channel-group-type.freeboxos.sysinfo.channel.ip-address.description = Internal IPv4 Address of the host
+channel-group-type.freeboxos.vmstatus.label = VM Status
+channel-group-type.freeboxos.wifi.label = Wifi Related Information
+channel-group-type.freeboxos.wifi.channel.rate-down.label = Rx Rate
+channel-group-type.freeboxos.wifi.channel.rate-down.description = Current RX rate
+channel-group-type.freeboxos.wifi.channel.rate-up.label = Tx Rate
+channel-group-type.freeboxos.wifi.channel.rate-up.description = Current TX Rate
+
+# channel types
+
+channel-type.freeboxos.afp-file-status.label = Mac OS File Sharing
+channel-type.freeboxos.afp-file-status.description = Status of Mac OS File Sharing
+channel-type.freeboxos.airmedia-status.label = Air Media Enabled
+channel-type.freeboxos.airmedia-status.description = Indicates whether Air Media is enabled
+channel-type.freeboxos.alarm-pin.label = PIN Code
+channel-type.freeboxos.alarm-timeout.label = Alarm Duration
+channel-type.freeboxos.alarm-volume.label = Alarm Volume
+channel-type.freeboxos.alternate-ring.label = Alternating Ring
+channel-type.freeboxos.bandwidth-usage.label = Bandwidth Usage
+channel-type.freeboxos.bandwidth-usage.description = Current bandwidth usage
+channel-type.freeboxos.bandwidth.label = Bandwidth
+channel-type.freeboxos.bandwidth.description = Available bandwidth
+channel-type.freeboxos.basic-shutter.label = Shutter
+channel-type.freeboxos.basic-shutter.description = Shutter command
+channel-type.freeboxos.box-event.label = Server Event
+channel-type.freeboxos.box-event.description = Triggers when an event related to the Freebox server has been detected
+channel-type.freeboxos.connection-status.label = Connection
+channel-type.freeboxos.connection-status.description = Connection Status
+channel-type.freeboxos.dect-active.label = DECT Enabled
+channel-type.freeboxos.dect-active.description = Activates / stops the integrated DECT base
+channel-type.freeboxos.duration.label = Duration
+channel-type.freeboxos.duration.description = Call duration in seconds
+channel-type.freeboxos.fanspeed.label = Fan Speed
+channel-type.freeboxos.fanspeed.description = Actual measured rotation speed of the fan
+channel-type.freeboxos.ftp-status.label = FTP Server Enabled
+channel-type.freeboxos.ftp-status.description = Indicates whether the FTP server is enabled
+channel-type.freeboxos.gain.label = Gain
+channel-type.freeboxos.hardware-status.label = Hardware Status
+channel-type.freeboxos.hardware-status.description = Hardware status of the phone line
+channel-type.freeboxos.host-count.label = Host Count
+channel-type.freeboxos.host-count.description = Number of hosts connected to the device
+channel-type.freeboxos.ip-address.label = IP Address
+channel-type.freeboxos.ip-address.description = IP address of the client
+channel-type.freeboxos.key-code.label = Remote Key Code
+channel-type.freeboxos.key-code.description = Simulates pushing a remote control button
+channel-type.freeboxos.key-code.state.option.red = Red
+channel-type.freeboxos.key-code.state.option.green = Green
+channel-type.freeboxos.key-code.state.option.blue = Blue
+channel-type.freeboxos.key-code.state.option.yellow = Yellow
+channel-type.freeboxos.key-code.state.option.power = On/Off
+channel-type.freeboxos.key-code.state.option.list = List
+channel-type.freeboxos.key-code.state.option.tv = TV
+channel-type.freeboxos.key-code.state.option.0 = 0
+channel-type.freeboxos.key-code.state.option.1 = 1
+channel-type.freeboxos.key-code.state.option.2 = 2
+channel-type.freeboxos.key-code.state.option.3 = 3
+channel-type.freeboxos.key-code.state.option.4 = 4
+channel-type.freeboxos.key-code.state.option.5 = 5
+channel-type.freeboxos.key-code.state.option.6 = 6
+channel-type.freeboxos.key-code.state.option.7 = 7
+channel-type.freeboxos.key-code.state.option.8 = 8
+channel-type.freeboxos.key-code.state.option.9 = 9
+channel-type.freeboxos.key-code.state.option.vol_inc = Volume Up
+channel-type.freeboxos.key-code.state.option.vol_dec = Volume Down
+channel-type.freeboxos.key-code.state.option.mute = Mute
+channel-type.freeboxos.key-code.state.option.prgm_inc = Prog Up
+channel-type.freeboxos.key-code.state.option.prgm_dec = Prog Down
+channel-type.freeboxos.key-code.state.option.prev = Previous
+channel-type.freeboxos.key-code.state.option.bwd = Backward
+channel-type.freeboxos.key-code.state.option.play = Play/Pause
+channel-type.freeboxos.key-code.state.option.rec = Record
+channel-type.freeboxos.key-code.state.option.fwd = Forward
+channel-type.freeboxos.key-code.state.option.next = Next
+channel-type.freeboxos.key-code.state.option.up = Up
+channel-type.freeboxos.key-code.state.option.right = Right
+channel-type.freeboxos.key-code.state.option.down = Down
+channel-type.freeboxos.key-code.state.option.left = Left
+channel-type.freeboxos.key-code.state.option.back = Back
+channel-type.freeboxos.key-code.state.option.swap = Swap
+channel-type.freeboxos.key-code.state.option.info = Info
+channel-type.freeboxos.key-code.state.option.epg = EPG
+channel-type.freeboxos.key-code.state.option.mail = Mail
+channel-type.freeboxos.key-code.state.option.media = Media
+channel-type.freeboxos.key-code.state.option.help = Help
+channel-type.freeboxos.key-code.state.option.options = Options
+channel-type.freeboxos.key-code.state.option.pip = PIP
+channel-type.freeboxos.key-code.state.option.ok = OK
+channel-type.freeboxos.key-code.state.option.home = Home
+channel-type.freeboxos.keyfob-enable.label = Keyfob Enabled
+channel-type.freeboxos.keyfob-enable.description = Activates / deactivates the keyfob
+channel-type.freeboxos.lcd-brightness.label = Screen Brightness
+channel-type.freeboxos.lcd-brightness.description = Brightness level of the screen in percent
+channel-type.freeboxos.lcd-forced.label = Forced Orientation
+channel-type.freeboxos.lcd-forced.description = Indicates whether the screen orientation is forced
+channel-type.freeboxos.lcd-orientation.label = Screen Orientation
+channel-type.freeboxos.lcd-orientation.description = Screen Orientation in degrees
+channel-type.freeboxos.lcd-orientation.state.option.0 = Horizontal
+channel-type.freeboxos.lcd-orientation.state.option.90 = Turned left
+channel-type.freeboxos.lcd-orientation.state.option.180 = Reversed
+channel-type.freeboxos.lcd-orientation.state.option.270 = Turned right
+channel-type.freeboxos.led.label = Led Activated
+channel-type.freeboxos.led.description = Led indicator status
+channel-type.freeboxos.line-media.label = Line Media
+channel-type.freeboxos.line-media.description = Media of network line connection
+channel-type.freeboxos.line-media.state.option.FTTH = FTTH
+channel-type.freeboxos.line-media.state.option.ETHERNET = Ethernet
+channel-type.freeboxos.line-media.state.option.XDSL = xDSL
+channel-type.freeboxos.line-media.state.option.BACKUP_4G = Internet Backup
+channel-type.freeboxos.line-status.label = Line Status
+channel-type.freeboxos.line-status.description = Status of network line connection
+channel-type.freeboxos.line-status.state.option.GOING_UP = Connection is initializing
+channel-type.freeboxos.line-status.state.option.UP = Connection is active
+channel-type.freeboxos.line-status.state.option.GOING_DOWN = Connection is about to become inactive
+channel-type.freeboxos.line-status.state.option.DOWN = Connection is inactive
+channel-type.freeboxos.line-type.label = Line Type
+channel-type.freeboxos.line-type.description = Type of network line connection
+channel-type.freeboxos.line-type.state.option.ETHERNET = FTTH/ethernet
+channel-type.freeboxos.line-type.state.option.RFC2684 = xDSL (unbundled)
+channel-type.freeboxos.line-type.state.option.PPPOATM = xDSL
+channel-type.freeboxos.name.label = Name
+channel-type.freeboxos.name.description = Called name for outgoing calls. Caller name for incoming calls
+channel-type.freeboxos.number.label = Incoming Call
+channel-type.freeboxos.number.description = Details about call
+channel-type.freeboxos.onhook.label = On Hook
+channel-type.freeboxos.onhook.description = Indicates whether the phone is on hook
+channel-type.freeboxos.package.label = Active Package
+channel-type.freeboxos.package.description = Name of the package currently active on the player
+channel-type.freeboxos.phone-event.label = Phone Event
+channel-type.freeboxos.phone-event.description = Triggers when an event related to the phone has been detected
+channel-type.freeboxos.phone-number.label = Phone Number
+channel-type.freeboxos.player-status.label = Player Status
+channel-type.freeboxos.player-status.description = Status of the Freebox TV player
+channel-type.freeboxos.reachable.label = Reachable
+channel-type.freeboxos.reachable.description = Indicates if the network device is reachable or not
+channel-type.freeboxos.ringing.label = Ringing
+channel-type.freeboxos.ringing.description = Is the phone ringing
+channel-type.freeboxos.rssi.label = RSSI
+channel-type.freeboxos.rssi.description = Received signal strength indicator
+channel-type.freeboxos.samba-file-status.label = Windows File Sharing
+channel-type.freeboxos.samba-file-status.description = Status of Windows File Sharing (Samba)
+channel-type.freeboxos.samba-printer-status.label = Windows Printer Sharing
+channel-type.freeboxos.samba-printer-status.description = Status of Windows Printer Sharing
+channel-type.freeboxos.shutter.label = Shutter Position
+channel-type.freeboxos.shutter.description = Read / Write position of the shutter
+channel-type.freeboxos.ssid.label = SSID
+channel-type.freeboxos.status.label = VM Status
+channel-type.freeboxos.telephony-service.label = Telephony Service
+channel-type.freeboxos.telephony-service.description = Status of the telephony service
+channel-type.freeboxos.temperature.label = Temperature
+channel-type.freeboxos.temperature.description = Actual measured temperature of the sensor
+channel-type.freeboxos.timestamp.label = Timestamp
+channel-type.freeboxos.transfer-bytes.label = Transfered Bytes
+channel-type.freeboxos.transfer-bytes.description = Total data transfered since last connection
+channel-type.freeboxos.transfer-rate-bps.label = Transfer Rate
+channel-type.freeboxos.transfer-rate-bps.description = Current transfer rate
+channel-type.freeboxos.transfer-rate.label = Transfer Rate
+channel-type.freeboxos.transfer-rate.description = Current transfer rate
+channel-type.freeboxos.upnpav-status.label = UPnP AV Enabled
+channel-type.freeboxos.upnpav-status.description = Indicates whether UPnP AV is enabled
+channel-type.freeboxos.uptime.label = Uptime
+channel-type.freeboxos.uptime.description = Time since last reboot of the equipment
+channel-type.freeboxos.wifi-host.label = Access Point
+channel-type.freeboxos.wifi-status.label = Wifi Enabled
+channel-type.freeboxos.wifi-status.description = Indicates whether the wifi network is enabled
+
+# messages
+
+info-conf-pending = Please accept pairing request directly on your freebox
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <bridge-type id="api">
+ <label>Freebox OS Api</label>
+ <description>Bridge between hosts and the API rest service</description>
+
+ <representation-property>apiDomain</representation-property>
+
+ <config-description-ref uri="bridge-type:freeboxos:api"/>
+ </bridge-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <channel-type id="lcd-brightness" advanced="true">
+ <item-type>Number:Dimensionless</item-type>
+ <label>Screen Brightness</label>
+ <description>Brightness level of the screen in percent</description>
+ <category>DimmableLight</category>
+ <state pattern="%d %unit%" min="0" max="100"/>
+ </channel-type>
+
+ <channel-type id="lcd-orientation">
+ <item-type>Number</item-type>
+ <label>Screen Orientation</label>
+ <description>Screen Orientation in degrees</description>
+ <state pattern="%d °">
+ <options>
+ <option value="0">Horizontal</option>
+ <option value="90">Turned left</option>
+ <option value="180">Reversed</option>
+ <option value="270">Turned right</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="rssi">
+ <item-type>Number:Power</item-type>
+ <label>RSSI</label>
+ <description>Received signal strength indicator</description>
+ <category>QualityOfService</category>
+ <state readOnly="true" pattern="%d %unit%"/>
+ </channel-type>
+
+ <channel-type id="ssid" advanced="true">
+ <item-type>String</item-type>
+ <label>SSID</label>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
+
+ <channel-type id="wifi-host" advanced="true">
+ <item-type>String</item-type>
+ <label>Access Point</label>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
+
+ <channel-type id="lcd-forced" advanced="true">
+ <item-type>Switch</item-type>
+ <label>Forced Orientation</label>
+ <description>Indicates whether the screen orientation is forced</description>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="dect-active">
+ <item-type>Switch</item-type>
+ <label>DECT Enabled</label>
+ <description>Activates / stops the integrated DECT base</description>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="alternate-ring">
+ <item-type>Switch</item-type>
+ <label>Alternating Ring</label>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="temperature" advanced="true">
+ <item-type>Number:Temperature</item-type>
+ <label>Temperature</label>
+ <description>Actual measured temperature of the sensor</description>
+ <category>Temperature</category>
+ <state readOnly="true" pattern="%d %unit%"/>
+ </channel-type>
+
+ <channel-type id="fanspeed" advanced="true">
+ <item-type>Number:Frequency</item-type>
+ <label>Fan Speed</label>
+ <description>Actual measured rotation speed of the fan</description>
+ <category>Fan</category>
+ <state readOnly="true" pattern="%d %unit%"/>
+ </channel-type>
+
+ <channel-type id="samba-file-status" advanced="true">
+ <item-type>Switch</item-type>
+ <label>Windows File Sharing</label>
+ <description>Status of Windows File Sharing (Samba)</description>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="afp-file-status" advanced="true">
+ <item-type>Switch</item-type>
+ <label>Mac OS File Sharing</label>
+ <description>Status of Mac OS File Sharing</description>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="samba-printer-status" advanced="true">
+ <item-type>Switch</item-type>
+ <label>Windows Printer Sharing</label>
+ <description>Status of Windows Printer Sharing</description>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="bandwidth-usage">
+ <item-type>Number:Dimensionless</item-type>
+ <label>Bandwidth Usage</label>
+ <description>Current bandwidth usage</description>
+ <state readOnly="true" pattern="%.2f %unit%"/>
+ </channel-type>
+
+ <channel-type id="transfer-rate">
+ <item-type>Number:DataTransferRate</item-type>
+ <label>Transfer Rate</label>
+ <description>Current transfer rate</description>
+ <state readOnly="true" pattern="%.2f %unit%"/>
+ </channel-type>
+
+ <channel-type id="transfer-rate-bps" advanced="true">
+ <item-type>Number:DataTransferRate</item-type>
+ <label>Transfer Rate</label>
+ <description>Current transfer rate</description>
+ <state readOnly="true" pattern="%.2f bit/s"/>
+ </channel-type>
+
+ <channel-type id="transfer-bytes" advanced="true">
+ <item-type>Number:DataAmount</item-type>
+ <label>Transfered Bytes</label>
+ <description>Total data transfered since last connection</description>
+ <state readOnly="true" pattern="%.2f %unit%"/>
+ </channel-type>
+
+ <channel-type id="bandwidth" advanced="true">
+ <item-type>Number:DataTransferRate</item-type>
+ <label>Bandwidth</label>
+ <description>Available bandwidth</description>
+ <state readOnly="true" pattern="%.2f %unit%"/>
+ </channel-type>
+
+ <channel-type id="uptime" advanced="true">
+ <item-type>Number:Time</item-type>
+ <label>Uptime</label>
+ <description>Time since last reboot of the equipment</description>
+ <category>time</category>
+ <state readOnly="true" pattern="%d %unit%"/>
+ </channel-type>
+
+ <channel-type id="line-status">
+ <item-type>String</item-type>
+ <label>Line Status</label>
+ <description>Status of network line connection</description>
+ <state readOnly="true" pattern="%s">
+ <options>
+ <option value="GOING_UP">Connection is initializing</option>
+ <option value="UP">Connection is active</option>
+ <option value="GOING_DOWN">Connection is about to become inactive</option>
+ <option value="DOWN">Connection is inactive</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="line-type">
+ <item-type>String</item-type>
+ <label>Line Type</label>
+ <description>Type of network line connection</description>
+ <state readOnly="true" pattern="%s">
+ <options>
+ <option value="ETHERNET">FTTH/ethernet</option>
+ <option value="RFC2684">xDSL (unbundled)</option>
+ <option value="PPPOATM">xDSL</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="line-media">
+ <item-type>String</item-type>
+ <label>Line Media</label>
+ <description>Media of network line connection</description>
+ <state readOnly="true" pattern="%s">
+ <options>
+ <option value="FTTH">FTTH</option>
+ <option value="ETHERNET">Ethernet</option>
+ <option value="XDSL">xDSL</option>
+ <option value="BACKUP_4G">Internet Backup</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="player-status">
+ <item-type>String</item-type>
+ <label>Player Status</label>
+ <description>Status of the Freebox TV player</description>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
+
+ <channel-type id="package">
+ <item-type>String</item-type>
+ <label>Active Package</label>
+ <description>Name of the package currently active on the player</description>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
+
+ <channel-type id="wifi-status">
+ <item-type>Switch</item-type>
+ <label>Wifi Enabled</label>
+ <description>Indicates whether the wifi network is enabled</description>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="ftp-status" advanced="true">
+ <item-type>Switch</item-type>
+ <label>FTP Server Enabled</label>
+ <description>Indicates whether the FTP server is enabled</description>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="airmedia-status">
+ <item-type>Switch</item-type>
+ <label>Air Media Enabled</label>
+ <description>Indicates whether Air Media is enabled</description>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="upnpav-status" advanced="true">
+ <item-type>Switch</item-type>
+ <label>UPnP AV Enabled</label>
+ <description>Indicates whether UPnP AV is enabled</description>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="onhook">
+ <item-type>Switch</item-type>
+ <label>On Hook</label>
+ <description>Indicates whether the phone is on hook</description>
+ <category>Switch</category>
+ <state readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="ringing">
+ <item-type>Switch</item-type>
+ <label>Ringing</label>
+ <description>Is the phone ringing</description>
+ <category>Alarm</category>
+ </channel-type>
+
+ <channel-type id="phone-number">
+ <item-type>String</item-type>
+ <label>Phone Number</label>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
+
+ <channel-type id="number">
+ <item-type>Call</item-type>
+ <label>Incoming Call</label>
+ <description>Details about call</description>
+ <state pattern="%1$s => %2$s" readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="duration">
+ <item-type>Number:Time</item-type>
+ <label>Duration</label>
+ <description>Call duration in seconds</description>
+ <category>time</category>
+ <state readOnly="true" pattern="%d %unit%"/>
+ </channel-type>
+
+ <channel-type id="timestamp">
+ <item-type>DateTime</item-type>
+ <label>Timestamp</label>
+ <category>time</category>
+ <state readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="name" advanced="true">
+ <item-type>String</item-type>
+ <label>Name</label>
+ <description>Called name for outgoing calls. Caller name for incoming calls</description>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
+
+ <channel-type id="reachable">
+ <item-type>Switch</item-type>
+ <label>Reachable</label>
+ <description>Indicates if the network device is reachable or not</description>
+ <category>Switch</category>
+ <state readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="status">
+ <item-type>Switch</item-type>
+ <label>VM Status</label>
+ </channel-type>
+
+ <channel-type id="box-event">
+ <kind>trigger</kind>
+ <label>Server Event</label>
+ <description>Triggers when an event related to the Freebox server has been detected</description>
+ </channel-type>
+
+ <channel-type id="phone-event">
+ <kind>trigger</kind>
+ <label>Phone Event</label>
+ <description>Triggers when an event related to the phone has been detected</description>
+ </channel-type>
+
+ <channel-type id="key-code">
+ <item-type>String</item-type>
+ <label>Remote Key Code</label>
+ <description>Simulates pushing a remote control button</description>
+ <state readOnly="false" pattern="%s">
+ <options>
+ <option value="red">Red</option>
+ <option value="green">Green</option>
+ <option value="blue">Blue</option>
+ <option value="yellow">Yellow</option>
+ <option value="power">On/Off</option>
+ <option value="list">List</option>
+ <option value="tv">TV</option>
+ <option value="0">0</option>
+ <option value="1">1</option>
+ <option value="2">2</option>
+ <option value="3">3</option>
+ <option value="4">4</option>
+ <option value="5">5</option>
+ <option value="6">6</option>
+ <option value="7">7</option>
+ <option value="8">8</option>
+ <option value="9">9</option>
+ <option value="vol_inc">Volume Up</option>
+ <option value="vol_dec">Volume Down</option>
+ <option value="mute">Mute</option>
+ <option value="prgm_inc">Prog Up</option>
+ <option value="prgm_dec">Prog Down</option>
+ <option value="prev">Previous</option>
+ <option value="bwd">Backward</option>
+ <option value="play">Play/Pause</option>
+ <option value="rec">Record</option>
+ <option value="fwd">Forward</option>
+ <option value="next">Next</option>
+ <option value="up">Up</option>
+ <option value="right">Right</option>
+ <option value="down">Down</option>
+ <option value="left">Left</option>
+ <option value="back">Back</option>
+ <option value="swap">Swap</option>
+ <option value="info">Info</option>
+ <option value="epg">EPG</option>
+ <option value="mail">Mail</option>
+ <option value="media">Media</option>
+ <option value="help">Help</option>
+ <option value="options">Options</option>
+ <option value="pip">PIP</option>
+ <option value="ok">OK</option>
+ <option value="home">Home</option>
+ </options>
+ </state>
+ <autoUpdatePolicy>veto</autoUpdatePolicy>
+ </channel-type>
+
+ <channel-type id="ip-address" advanced="true">
+ <item-type>String</item-type>
+ <label>IP Address</label>
+ <description>IP address of the client</description>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
+
+ <channel-type id="led">
+ <item-type>Switch</item-type>
+ <label>Led Activated</label>
+ <description>Led indicator status</description>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="gain">
+ <item-type>Dimmer</item-type>
+ <label>Gain</label>
+ </channel-type>
+
+ <channel-type id="connection-status">
+ <item-type>String</item-type>
+ <label>Connection</label>
+ <description>Connection Status</description>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
+
+ <channel-type id="hardware-status">
+ <item-type>String</item-type>
+ <label>Hardware Status</label>
+ <description>Hardware status of the phone line</description>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
+
+ <channel-type id="telephony-service">
+ <item-type>String</item-type>
+ <label>Telephony Service</label>
+ <description>Status of the telephony service</description>
+ <state readOnly="true" pattern="%s"/>
+ </channel-type>
+
+ <channel-type id="host-count">
+ <item-type>Number</item-type>
+ <label>Host Count</label>
+ <description>Number of hosts connected to the device</description>
+ <state readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="keyfob-enable">
+ <item-type>Switch</item-type>
+ <label>Keyfob Enabled</label>
+ <description>Activates / deactivates the keyfob</description>
+ <category>Switch</category>
+ <config-description>
+ <parameter name="slot" type="integer"/>
+ <parameter name="signal" type="integer"/>
+ </config-description>
+ </channel-type>
+
+ <channel-type id="basic-shutter">
+ <item-type>Rollershutter</item-type>
+ <label>Shutter</label>
+ <description>Shutter command</description>
+ <category>Blinds</category>
+ <config-description>
+ <parameter name="up" type="integer"/>
+ <parameter name="down" type="integer"/>
+ <parameter name="stop" type="integer"/>
+ <parameter name="signal" type="integer"/>
+ </config-description>
+ </channel-type>
+
+ <channel-type id="shutter">
+ <item-type>Rollershutter</item-type>
+ <label>Shutter Position</label>
+ <description>Read / Write position of the shutter</description>
+ <state pattern="%d %unit%"/>
+ <config-description>
+ <parameter name="slot" type="integer"/>
+ <parameter name="signal" type="integer"/>
+ <parameter name="stop" type="integer"/>
+ </config-description>
+ </channel-type>
+
+ <channel-type id="alarm-timeout">
+ <item-type>Number:Time</item-type>
+ <label>Alarm Duration</label>
+ <category>oh:freeboxos:zone_temporisee</category>
+ <state pattern="%d %unit%"/>
+ </channel-type>
+
+ <channel-type id="alarm-volume">
+ <item-type>Number:Dimensionless</item-type>
+ <label>Alarm Volume</label>
+ <category>oh:freeboxos:sirene</category>
+ <state min="0" max="100" step="1" pattern="%d %unit%"/>
+ </channel-type>
+
+ <channel-type id="alarm-pin">
+ <item-type>String</item-type>
+ <label>PIN Code</label>
+ <category>oh:freeboxos:pin_code</category>
+ <state pattern="%s"/>
+ </channel-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <thing-type id="freeplug">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="api"/>
+ </supported-bridge-type-refs>
+
+ <label>Freeplug</label>
+ <description>Ethernet / CPL gateway</description>
+
+ <channels>
+ <channel id="line-status" typeId="line-status"/>
+ <channel id="last-seen" typeId="timestamp">
+ <label>Last Activity</label>
+ </channel>
+ <channel id="reachable" typeId="reachable"/>
+ <channel id="rate-up" typeId="transfer-rate">
+ <label>Tx Rate</label>
+ <description>Current TX Rate</description>
+ </channel>
+ <channel id="rate-down" typeId="transfer-rate">
+ <label>Rx Rate</label>
+ <description>Current RX rate</description>
+ </channel>
+ </channels>
+
+ <representation-property>macAddress</representation-property>
+
+ <config-description-ref uri="thing-type:freeboxos:host"/>
+ </thing-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <thing-type id="basic-shutter">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="api"/>
+ </supported-bridge-type-refs>
+
+ <label>Freebox Home Basic Shutter</label>
+ <description>The Basic Shutter (UP,DOWN,STOP) configured in your Freebox Delta Server</description>
+
+ <channels>
+ <channel id="state" typeId="basic-shutter"/>
+ </channels>
+
+ <representation-property>id</representation-property>
+
+ <config-description-ref uri="thing-type:freeboxos:home-node"/>
+ </thing-type>
+
+ <thing-type id="alarm">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="api"/>
+ </supported-bridge-type-refs>
+
+ <label>Freebox Alarm</label>
+ <description>The Alarm system configured in your Freebox Delta Server</description>
+
+ <channels>
+ <channel id="pin" typeId="alarm-pin"/>
+ <channel id="sound" typeId="alarm-volume">
+ <label>Bips Volume</label>
+ </channel>
+ <channel id="volume" typeId="alarm-volume">
+ <label>Alarm Volume</label>
+ </channel>
+ <channel id="timeout1" typeId="alarm-timeout">
+ <label>Alarm Activation Duration</label>
+ </channel>
+ <channel id="timeout2" typeId="alarm-timeout">
+ <label>Safe Zone Alert Timeout</label>
+ </channel>
+ <channel id="timeout3" typeId="alarm-timeout">
+ <label>Alert Duration</label>
+ </channel>
+ <channel id="battery" typeId="system.battery-level"/>
+ </channels>
+
+ <representation-property>id</representation-property>
+
+ <config-description-ref uri="thing-type:freeboxos:home-node"/>
+ </thing-type>
+
+ <thing-type id="kfb">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="api"/>
+ </supported-bridge-type-refs>
+
+
+ <label>Freebox Keyfob</label>
+ <description>A keyfob configured for your Freebox Security system</description>
+
+ <channels>
+ <channel id="enable" typeId="keyfob-enable"/>
+ <channel id="battery" typeId="system.battery-level"/>
+ </channels>
+
+ <representation-property>id</representation-property>
+
+ <config-description-ref uri="thing-type:freeboxos:home-node"/>
+ </thing-type>
+
+ <thing-type id="shutter">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="api"/>
+ </supported-bridge-type-refs>
+
+ <label>Freebox Home Shutter</label>
+ <description>The Shutter configured in your Freebox Delta Server</description>
+
+ <channels>
+ <channel id="position-set" typeId="shutter"/>
+ </channels>
+
+ <representation-property>id</representation-property>
+
+ <config-description-ref uri="thing-type:freeboxos:home-node"/>
+ </thing-type>
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <channel-group-type id="connectivity">
+ <label>Network Connectivity</label>
+ <channels>
+ <channel id="reachable" typeId="reachable"/>
+ <channel id="last-seen" typeId="timestamp">
+ <label>Last Activity</label>
+ </channel>
+ <channel id="ip-address" typeId="ip-address">
+ <label>IP Address</label>
+ <description>IPv4 Address of the host</description>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <thing-type id="host">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="api"/>
+ </supported-bridge-type-refs>
+
+ <label>Network Device</label>
+ <description>Provides network device reachability</description>
+
+ <channel-groups>
+ <channel-group typeId="connectivity" id="connectivity"/>
+ </channel-groups>
+
+ <representation-property>macAddress</representation-property>
+
+ <config-description-ref uri="thing-type:freeboxos:host"/>
+ </thing-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <channel-group-type id="accepted">
+ <label>Accepted Call</label>
+ <description>The last accepted phone call</description>
+ <channels>
+ <channel id="number" typeId="number">
+ <label>Calling Number</label>
+ <description>Caller phone number</description>
+ </channel>
+ <channel id="duration" typeId="duration">
+ <label>Incoming Call Duration</label>
+ </channel>
+ <channel id="timestamp" typeId="timestamp">
+ <label>Incoming Call Timestamp</label>
+ </channel>
+ <channel id="name" typeId="name">
+ <label>Accepted Caller</label>
+ <description>Caller name</description>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="missed">
+ <label>Missed Call</label>
+ <description>The last missed phone call</description>
+ <channels>
+ <channel id="number" typeId="number">
+ <label>Missed Calling Number</label>
+ <description>Caller phone number</description>
+ </channel>
+ <channel id="timestamp" typeId="timestamp">
+ <label>Missed Call Timestamp</label>
+ </channel>
+ <channel id="name" typeId="name">
+ <label>Missed Caller</label>
+ <description>Caller name</description>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="incoming">
+ <label>Incoming Call</label>
+ <description>Currently presented phone call</description>
+ <channels>
+ <channel id="number" typeId="number">
+ <label>Calling Number</label>
+ <description>Caller phone number</description>
+ </channel>
+ <channel id="timestamp" typeId="timestamp">
+ <label>Call Timestamp</label>
+ </channel>
+ <channel id="name" typeId="name">
+ <label>Incoming Caller</label>
+ <description>Caller name</description>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="outgoing">
+ <label>Outgoing Call</label>
+ <description>The last outgoing phone call</description>
+ <channels>
+ <channel id="number" typeId="number">
+ <label>Called Number</label>
+ <description>Called phone number</description>
+ </channel>
+ <channel id="duration" typeId="duration">
+ <label>Outgoing Call Duration</label>
+ </channel>
+ <channel id="timestamp" typeId="timestamp">
+ <label>Outgoing Call Timestamp</label>
+ </channel>
+ <channel id="name" typeId="name">
+ <label>Called Name</label>
+ <description>Name, if known, of the called person</description>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <thing-type id="fxs">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="api"/>
+ </supported-bridge-type-refs>
+
+ <label>Landline</label>
+ <description>Provides various informations regarding the landline state</description>
+
+ <channels>
+ <channel id="telephony-service" typeId="telephony-service"/>
+ <channel id="onhook" typeId="onhook"/>
+ <channel id="ringing" typeId="ringing"/>
+ <channel id="hardware-status" typeId="hardware-status"/>
+ </channels>
+
+ <representation-property>id</representation-property>
+
+ <config-description-ref uri="thing-type:freeboxos:phone"/>
+ </thing-type>
+
+ <thing-type id="dect">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="api"/>
+ </supported-bridge-type-refs>
+
+ <label>DECT</label>
+ <description>Provides various informations regarding the DECT state and configuration</description>
+
+ <channels>
+ <channel id="telephony-service" typeId="telephony-service"/>
+ <channel id="dect-active" typeId="dect-active"/>
+ <channel id="alternate-ring" typeId="alternate-ring"/>
+ <channel id="ringing" typeId="ringing"/>
+ <channel id="hardware-status" typeId="hardware-status"/>
+ <channel id="gain-rx" typeId="gain">
+ <label>Gain RX</label>
+ </channel>
+ <channel id="gain-tx" typeId="gain">
+ <label>Gain TX</label>
+ </channel>
+ </channels>
+
+ <representation-property>id</representation-property>
+
+ <config-description-ref uri="thing-type:freeboxos:phone"/>
+ </thing-type>
+
+ <thing-type id="call">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="api"/>
+ </supported-bridge-type-refs>
+
+ <label>Calls</label>
+ <description>Provides various informations regarding the phone calls</description>
+
+ <channel-groups>
+ <channel-group typeId="incoming" id="incoming"/>
+ <channel-group typeId="accepted" id="accepted"/>
+ <channel-group typeId="missed" id="missed"/>
+ <channel-group typeId="outgoing" id="outgoing"/>
+ </channel-groups>
+
+ <config-description-ref uri="thing-type:freeboxos:call"/>
+ </thing-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <channel-group-type id="player-actions">
+ <label>Player Actions</label>
+ <channels>
+ <channel id="key-code" typeId="key-code"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="player-status">
+ <label>Player Status</label>
+ <channels>
+ <channel id="player-status" typeId="player-status"/>
+ <channel id="package" typeId="package"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="player-sysinfo">
+ <label>System Informations</label>
+ <channels>
+ <channel id="uptime" typeId="uptime"/>
+ <channel id="box-event" typeId="box-event"/>
+ </channels>
+ </channel-group-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <thing-type id="player">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="api"/>
+ </supported-bridge-type-refs>
+
+ <label>Freebox Player</label>
+ <description>The player is the device connected to your TV</description>
+
+ <channel-groups>
+ <channel-group typeId="player-actions" id="player-actions"/>
+ <channel-group typeId="sysinfo" id="sysinfo"/>
+ <channel-group typeId="connectivity" id="connectivity"/>
+ </channel-groups>
+
+ <representation-property>macAddress</representation-property>
+
+ <config-description-ref uri="thing-type:freeboxos:player"/>
+ </thing-type>
+
+ <thing-type id="active-player">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="api"/>
+ </supported-bridge-type-refs>
+
+ <label>Freebox Player</label>
+ <description>The player is the device connected to your TV with API capabilities</description>
+
+ <channel-groups>
+ <channel-group typeId="player-actions" id="player-actions"/>
+ <channel-group typeId="player-status" id="player-status"/>
+ <channel-group typeId="sysinfo" id="sysinfo"/>
+ <channel-group typeId="connectivity" id="connectivity"/>
+ </channel-groups>
+
+ <representation-property>macAddress</representation-property>
+
+ <config-description-ref uri="thing-type:freeboxos:player"/>
+ </thing-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <channel-group-type id="repeater-misc">
+ <label>Repeater Settings</label>
+ <channels>
+ <channel id="uptime" typeId="uptime"/>
+ <channel id="led" typeId="led"/>
+ <channel id="connection-status" typeId="connection-status"/>
+ <channel id="host-count" typeId="host-count"/>
+ <channel id="box-event" typeId="box-event">
+ <label>Repeater Event</label>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <thing-type id="repeater">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="api"/>
+ </supported-bridge-type-refs>
+
+ <label>Wifi Repeater</label>
+ <description>Provides informations and control over a Wifi Repeater</description>
+
+ <channel-groups>
+ <channel-group typeId="connectivity" id="connectivity"/>
+ <channel-group typeId="repeater-misc" id="repeater-misc"/>
+ </channel-groups>
+
+ <representation-property>macAddress</representation-property>
+
+ <config-description-ref uri="thing-type:freeboxos:repeater"/>
+ </thing-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <channel-group-type id="sensors">
+ <label>System Sensors</label>
+ </channel-group-type>
+
+ <channel-group-type id="fans">
+ <label>Fans</label>
+ </channel-group-type>
+
+ <channel-group-type id="sysinfo">
+ <label>System Informations</label>
+ <channels>
+ <channel id="uptime" typeId="uptime"/>
+ <channel id="ip-address" typeId="ip-address">
+ <label>Internal IP</label>
+ <description>Internal IPv4 Address of the host</description>
+ </channel>
+ <channel id="box-event" typeId="box-event"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="actions">
+ <label>Server Settings</label>
+ <channels>
+ <channel id="wifi-status" typeId="wifi-status"/>
+ <channel id="upnpav-status" typeId="upnpav-status"/>
+ <channel id="airmedia-status" typeId="airmedia-status"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="file-sharing">
+ <label>File Sharing</label>
+ <channels>
+ <channel id="ftp-status" typeId="ftp-status"/>
+ <channel id="samba-file-status" typeId="samba-file-status"/>
+ <channel id="samba-printer-status" typeId="samba-printer-status"/>
+ <channel id="afp-file-status" typeId="afp-file-status"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="display">
+ <label>Front Display Panel</label>
+ <channels>
+ <channel id="lcd-brightness" typeId="lcd-brightness"/>
+ <channel id="lcd-orientation" typeId="lcd-orientation"/>
+ <channel id="lcd-forced" typeId="lcd-forced"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="connection-status">
+ <label>Connection Status Details</label>
+ <channels>
+ <channel id="line-status" typeId="line-status"/>
+ <channel id="line-type" typeId="line-type"/>
+ <channel id="line-media" typeId="line-media"/>
+ <channel id="ip-address" typeId="ip-address">
+ <label>Public IPv4</label>
+ <description>Public IPv4 Address of the Freebox (this field is only available when connection state is up)</description>
+ </channel>
+ <channel id="ipv6-address" typeId="ip-address">
+ <label>Public IPv6</label>
+ <description>Public IPv6 Address of the Freebox (this field is only available when connection state is up)</description>
+ </channel>
+ <channel id="bandwidth-up" typeId="transfer-rate">
+ <label>Bandwidth Up</label>
+ <description>Raw value of the upload bandwidth currently used</description>
+ </channel>
+ <channel id="bandwidth-down" typeId="transfer-rate">
+ <label>Bandwidth Down</label>
+ <description>Raw value of the download bandwidth currently used</description>
+ </channel>
+ <channel id="bandwidth-usage-up" typeId="bandwidth-usage">
+ <label>Bandwidth Usage Up</label>
+ <description>Portion of the upload bandwidth currently used</description>
+ </channel>
+ <channel id="bandwidth-usage-down" typeId="bandwidth-usage">
+ <label>Bandwidth Usage Down</label>
+ <description>Portion of the download bandwidth currently used</description>
+ </channel>
+ <channel id="rate-up" typeId="transfer-rate">
+ <label>Upload Rate</label>
+ <description>Current upload rate</description>
+ </channel>
+ <channel id="rate-down" typeId="transfer-rate">
+ <label>Download Rate</label>
+ <description>Current download rate</description>
+ </channel>
+ <channel id="bytes-up" typeId="transfer-bytes">
+ <label>Uploaded</label>
+ <description>Total data uploaded since last restart</description>
+ </channel>
+ <channel id="bytes-down" typeId="transfer-bytes">
+ <label>Downloaded</label>
+ <description>Total data downloaded since last restart</description>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <thing-type id="revolution">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="api"/>
+ </supported-bridge-type-refs>
+
+ <label>Freebox Revolution</label>
+ <description>Provides various informations regarding the status of the Freebox Revolution Server</description>
+
+ <channel-groups>
+ <channel-group typeId="display" id="display"/>
+ <channel-group typeId="sensors" id="sensors"/>
+ <channel-group typeId="fans" id="fans"/>
+ <channel-group typeId="file-sharing" id="file-sharing"/>
+ <channel-group typeId="sysinfo" id="sysinfo"/>
+ <channel-group typeId="actions" id="actions"/>
+ <channel-group typeId="connection-status" id="connection-status"/>
+ </channel-groups>
+
+ <config-description-ref uri="thing-type:freeboxos:server"/>
+ </thing-type>
+
+ <thing-type id="delta">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="api"/>
+ </supported-bridge-type-refs>
+
+ <label>Freebox Delta</label>
+ <description>Provides various informations regarding the status of the Freebox Delta Server</description>
+
+ <channel-groups>
+ <channel-group typeId="sensors" id="sensors"/>
+ <channel-group typeId="fans" id="fans"/>
+ <channel-group typeId="file-sharing" id="file-sharing"/>
+ <channel-group typeId="sysinfo" id="sysinfo"/>
+ <channel-group typeId="actions" id="actions"/>
+ <channel-group typeId="connection-status" id="connection-status"/>
+ </channel-groups>
+
+ <config-description-ref uri="thing-type:freeboxos:server"/>
+ </thing-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <channel-group-type id="vmstatus">
+ <label>VM Status</label>
+ <channels>
+ <channel id="status" typeId="status"/>
+ </channels>
+ </channel-group-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <thing-type id="vm">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="api"/>
+ </supported-bridge-type-refs>
+
+ <label>Virtual Machine</label>
+ <description>Provides informations and control over virtual machine hosted on the server</description>
+
+ <channel-groups>
+ <channel-group typeId="connectivity" id="connectivity"/>
+ <channel-group typeId="vmstatus" id="vmstatus"/>
+ </channel-groups>
+
+ <representation-property>macAddress</representation-property>
+
+ <config-description-ref uri="thing-type:freeboxos:vm"/>
+ </thing-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <channel-group-type id="wifi">
+ <label>Wifi Related Information</label>
+ <channels>
+ <channel id="rssi" typeId="rssi"/>
+ <channel id="wifi-quality" typeId="system.signal-strength"/>
+ <channel id="ssid" typeId="ssid"/>
+ <channel id="wifi-host" typeId="wifi-host"/>
+ <channel id="rate-up" typeId="transfer-rate-bps">
+ <label>Tx Rate</label>
+ <description>Current TX Rate</description>
+ </channel>
+ <channel id="rate-down" typeId="transfer-rate-bps">
+ <label>Rx Rate</label>
+ <description>Current RX rate</description>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <thing-type id="wifihost">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="api"/>
+ </supported-bridge-type-refs>
+
+ <label>Wifi Device</label>
+ <description>Provides Wifi device reachability</description>
+
+ <channel-groups>
+ <channel-group typeId="connectivity" id="connectivity"/>
+ <channel-group typeId="wifi" id="wifi"/>
+ </channel-groups>
+
+ <representation-property>macAddress</representation-property>
+
+ <config-description-ref uri="thing-type:freeboxos:host"/>
+ </thing-type>
+
+</thing:thing-descriptions>
--- /dev/null
+-----BEGIN CERTIFICATE-----\r
+MIICWTCCAd+gAwIBAgIJAMaRcLnIgyukMAoGCCqGSM49BAMCMGExCzAJBgNVBAYT\r
+AkZSMQ8wDQYDVQQIDAZGcmFuY2UxDjAMBgNVBAcMBVBhcmlzMRMwEQYDVQQKDApG\r
+cmVlYm94IFNBMRwwGgYDVQQDDBNGcmVlYm94IEVDQyBSb290IENBMB4XDTE1MDkw\r
+MTE4MDIwN1oXDTM1MDgyNzE4MDIwN1owYTELMAkGA1UEBhMCRlIxDzANBgNVBAgM\r
+BkZyYW5jZTEOMAwGA1UEBwwFUGFyaXMxEzARBgNVBAoMCkZyZWVib3ggU0ExHDAa\r
+BgNVBAMME0ZyZWVib3ggRUNDIFJvb3QgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNi\r
+AASCjD6ZKn5ko6cU5Vxh8GA1KqRi6p2GQzndxHtuUmwY8RvBbhZ0GIL7bQ4f08ae\r
+JOv0ycWjEW0fyOnAw6AYdsN6y1eNvH2DVfoXQyGoCSvXQNAUxla+sJuLGICRYiZz\r
+mnijYzBhMB0GA1UdDgQWBBTIB3c2GlbV6EIh2ErEMJvFxMz/QTAfBgNVHSMEGDAW\r
+gBTIB3c2GlbV6EIh2ErEMJvFxMz/QTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB\r
+/wQEAwIBhjAKBggqhkjOPQQDAgNoADBlAjA8tzEMRVX8vrFuOGDhvZr7OSJjbBr8\r
+gl2I70LeVNGEXZsAThUkqj5Rg9bV8xw3aSMCMQCDjB5CgsLH8EdZmiksdBRRKM2r\r
+vxo6c0dSSNrr7dDN+m2/dRvgoIpGL2GauOGqDFY=\r
+-----END CERTIFICATE-----\r
--- /dev/null
+-----BEGIN CERTIFICATE-----
+MIICOjCCAcCgAwIBAgIUI0Tu7zsrBJACQIZgLMJobtbdNn4wCgYIKoZIzj0EAwIw
+TDELMAkGA1UEBhMCSVQxDjAMBgNVBAgMBUl0YWx5MQ4wDAYDVQQKDAVJbGlhZDEd
+MBsGA1UEAwwUSWxpYWRib3ggRUNDIFJvb3QgQ0EwHhcNMjAxMTI3MDkzODEzWhcN
+NDAxMTIyMDkzODEzWjBMMQswCQYDVQQGEwJJVDEOMAwGA1UECAwFSXRhbHkxDjAM
+BgNVBAoMBUlsaWFkMR0wGwYDVQQDDBRJbGlhZGJveCBFQ0MgUm9vdCBDQTB2MBAG
+ByqGSM49AgEGBSuBBAAiA2IABMryJyb2loHNAioY8IztN5MI3UgbVHVP/vZwcnre
+ZvJOyDvE4HJgIti5qmfswlnMzpNbwf/MkT+7HAU8jJoTorRm1wtAnQ9cWD3Ebv79
+RPwtjjy3Bza3SgdVxmd6fWPUKaNjMGEwHQYDVR0OBBYEFDUij/4lpoJ+kOXRyrcM
+jf2RPzOqMB8GA1UdIwQYMBaAFDUij/4lpoJ+kOXRyrcMjf2RPzOqMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQC6eUV1
+pFh4UpJOTc1JToztN4ttnQR6rIzxMZ6mNCe+nhjkohWp24pr7BpUYSbEizYCMAQ6
+LCiBKV2j7QQGy7N1aBmdur17ZepYzR1YV0eI+Kd978aZggsmhjXENQYVTmm/XA==
+-----END CERTIFICATE-----
<module>org.openhab.binding.folding</module>
<module>org.openhab.binding.foobot</module>
<module>org.openhab.binding.freebox</module>
+ <module>org.openhab.binding.freeboxos</module>
<module>org.openhab.binding.fronius</module>
<module>org.openhab.binding.fsinternetradio</module>
<module>org.openhab.binding.ftpupload</module>