diff --git a/CODEOWNERS b/CODEOWNERS
index a6d6e160d38..903e746634a 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -111,6 +111,7 @@
/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
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index 2a48a1987d3..61c86ec949d 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -551,6 +551,11 @@
org.openhab.binding.freebox
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.freeboxos
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.fronius
diff --git a/bundles/org.openhab.binding.freeboxos/NOTICE b/bundles/org.openhab.binding.freeboxos/NOTICE
new file mode 100644
index 00000000000..89286014860
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/NOTICE
@@ -0,0 +1,20 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
+
+== Third-party Content
+
+IPAddress: Java library for handling IP addresses and subnets, both IPv4 and IPv6
+* License: Apache License 2.0
+* Project: https://github.com/seancfoley/IPAddress
+* Source: https://github.com/seancfoley/IPAddress
diff --git a/bundles/org.openhab.binding.freeboxos/README.md b/bundles/org.openhab.binding.freeboxos/README.md
new file mode 100644
index 00000000000..5c4c8ace990
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/README.md
@@ -0,0 +1,210 @@
+# 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 |
diff --git a/bundles/org.openhab.binding.freeboxos/pom.xml b/bundles/org.openhab.binding.freeboxos/pom.xml
new file mode 100644
index 00000000000..f28ee769b12
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/pom.xml
@@ -0,0 +1,30 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 4.0.0-SNAPSHOT
+
+
+ org.openhab.binding.freeboxos
+
+ openHAB Add-ons :: Bundles :: FreeboxOS Binding
+
+
+
+ com.google.code.gson
+ gson
+ 2.10.1
+
+
+ com.github.seancfoley
+ ipaddress
+ 5.4.0
+
+
+
+
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/feature/feature.xml b/bundles/org.openhab.binding.freeboxos/src/main/feature/feature.xml
new file mode 100644
index 00000000000..bb55dd198ab
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/feature/feature.xml
@@ -0,0 +1,10 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ openhab-transport-mdns
+ mvn:org.openhab.addons.bundles/org.openhab.binding.freeboxos/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/FreeboxOsBindingConstants.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/FreeboxOsBindingConstants.java
new file mode 100644
index 00000000000..88c71f283de
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/FreeboxOsBindingConstants.java
@@ -0,0 +1,176 @@
+/**
+ * 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 BRIDGE_TYPE_UIDS = Set.of(BRIDGE_TYPE_API);
+ public static final Set 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 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 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 TRUE_COMMANDS = Set.of(OnOffType.ON, UpDownType.UP, OpenClosedType.OPEN);
+ public static final Set> ON_OFF_CLASSES = Set.of(OnOffType.class, UpDownType.class, OpenClosedType.class);
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/FreeboxOsHandlerFactory.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/FreeboxOsHandlerFactory.java
new file mode 100644
index 00000000000..f835aa29572
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/FreeboxOsHandlerFactory.java
@@ -0,0 +1,166 @@
+/**
+ * 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 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 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;
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/ActivePlayerActions.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/ActivePlayerActions.java
new file mode 100644
index 00000000000..1023aff9985
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/ActivePlayerActions.java
@@ -0,0 +1,48 @@
+/**
+ * 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();
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/CallActions.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/CallActions.java
new file mode 100644
index 00000000000..0f8d9b432a1
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/CallActions.java
@@ -0,0 +1,58 @@
+/**
+ * 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");
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/FreeplugActions.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/FreeplugActions.java
new file mode 100644
index 00000000000..b24af4caea0
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/FreeplugActions.java
@@ -0,0 +1,58 @@
+/**
+ * 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");
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/HostActions.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/HostActions.java
new file mode 100644
index 00000000000..b076063d0cf
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/HostActions.java
@@ -0,0 +1,58 @@
+/**
+ * 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");
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/PlayerActions.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/PlayerActions.java
new file mode 100644
index 00000000000..b1e7d619102
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/PlayerActions.java
@@ -0,0 +1,92 @@
+/**
+ * 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");
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/RepeaterActions.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/RepeaterActions.java
new file mode 100644
index 00000000000..ac33373fa07
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/RepeaterActions.java
@@ -0,0 +1,58 @@
+/**
+ * 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");
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/ServerActions.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/ServerActions.java
new file mode 100644
index 00000000000..4c5aea8ade9
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/ServerActions.java
@@ -0,0 +1,58 @@
+/**
+ * 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");
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/ApiHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/ApiHandler.java
new file mode 100644
index 00000000000..df5509cf687
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/ApiHandler.java
@@ -0,0 +1,155 @@
+/**
+ * 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) (json, type, jsonDeserializationContext) -> {
+ long timestamp = json.getAsJsonPrimitive().getAsLong();
+ Instant i = Instant.ofEpochSecond(timestamp);
+ return ZonedDateTime.ofInstant(i, timeZoneProvider.getTimeZone());
+ })
+ .registerTypeAdapter(MACAddress.class,
+ (JsonDeserializer) (json, type,
+ jsonDeserializationContext) -> new MACAddressString(json.getAsString()).getAddress())
+ .registerTypeAdapter(IPAddress.class,
+ (JsonDeserializer) (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 executeUri(URI uri, HttpMethod method, Class 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 deserialize(Class 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);
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxException.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxException.java
new file mode 100644
index 00000000000..5b04f63a160
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxException.java
@@ -0,0 +1,44 @@
+/**
+ * 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;
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxOsIconProvider.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxOsIconProvider.java
new file mode 100644
index 00000000000..2895adde5b4
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxOsIconProvider.java
@@ -0,0 +1,97 @@
+/**
+ * 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 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;
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxTlsCertificateProvider.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxTlsCertificateProvider.java
new file mode 100644
index 00000000000..47111248e99
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxTlsCertificateProvider.java
@@ -0,0 +1,47 @@
+/**
+ * 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));
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/IliadboxTlsCertificateProvider.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/IliadboxTlsCertificateProvider.java
new file mode 100644
index 00000000000..c6e25496588
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/IliadboxTlsCertificateProvider.java
@@ -0,0 +1,47 @@
+/**
+ * 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));
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/PermissionException.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/PermissionException.java
new file mode 100644
index 00000000000..d18c0c52e60
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/PermissionException.java
@@ -0,0 +1,37 @@
+/**
+ * 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;
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/Response.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/Response.java
new file mode 100644
index 00000000000..1b95cf408c9
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/Response.java
@@ -0,0 +1,125 @@
+/**
+ * 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 {
+ 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 result = List.of();
+ private boolean success;
+
+ // In some cases I did not understand deserialization can still produce null result
+ @SuppressWarnings("null")
+ public List getResult() {
+ List 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;
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/ForegroundAppDeserializer.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/ForegroundAppDeserializer.java
new file mode 100644
index 00000000000..74c8305cd65
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/ForegroundAppDeserializer.java
@@ -0,0 +1,57 @@
+/**
+ * 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 {
+
+ @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);
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/ListDeserializer.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/ListDeserializer.java
new file mode 100644
index 00000000000..0e403d495a5
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/ListDeserializer.java
@@ -0,0 +1,73 @@
+/**
+ * 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> {
+
+ @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;
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/StrictEnumTypeAdapterFactory.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/StrictEnumTypeAdapterFactory.java
new file mode 100644
index 00000000000..3c881fcb5e6
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/StrictEnumTypeAdapterFactory.java
@@ -0,0 +1,70 @@
+/**
+ * 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 TypeAdapter create(@NonNullByDefault({}) Gson gson,
+ @NonNullByDefault({}) TypeToken type) {
+ @SuppressWarnings("unchecked")
+ Class rawType = (Class) type.getRawType();
+ return rawType.isEnum() ? newStrictEnumAdapter(gson.getDelegateAdapter(this, type)) : null;
+ }
+
+ private TypeAdapter newStrictEnumAdapter(TypeAdapter delegateAdapter) {
+ return new TypeAdapter() {
+ @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);
+ }
+ };
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/APManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/APManager.java
new file mode 100644
index 00000000000..233046da0a8
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/APManager.java
@@ -0,0 +1,131 @@
+/**
+ * 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 {
+ 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 {
+ }
+
+ protected class APResponse extends Response {
+ }
+
+ public APManager(FreeboxOsSession session, UriBuilder uriBuilder) throws FreeboxException {
+ super(session, LoginManager.Permission.NONE, APResponse.class, uriBuilder.path(PATH));
+ }
+
+ private List getApStations(int apId) throws FreeboxException {
+ return get(ApHostsResponse.class, Integer.toString(apId), STATIONS_PATH);
+ }
+
+ public List getStations() throws FreeboxException {
+ List hosts = new ArrayList<>();
+ for (WifiAp ap : getDevices()) {
+ hosts.addAll(getApStations(ap.id));
+ }
+ return hosts;
+ }
+
+ public Optional getStation(MACAddress mac) throws FreeboxException {
+ return getStations().stream().filter(host -> host.mac().equals(mac)).findFirst();
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/AfpManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/AfpManager.java
new file mode 100644
index 00000000000..ed077efada4
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/AfpManager.java
@@ -0,0 +1,65 @@
+/**
+ * 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 {
+ private static final String AFP_PATH = "afp";
+
+ protected static class ConfigResponse extends Response {
+ }
+
+ 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;
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/AirMediaManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/AirMediaManager.java
new file mode 100644
index 00000000000..8b8ddc25852
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/AirMediaManager.java
@@ -0,0 +1,47 @@
+/**
+ * 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 {
+ private static final String PATH = "airmedia";
+
+ protected static record Config(boolean enabled) {
+ }
+
+ protected static class ConfigResponse extends Response {
+ }
+
+ 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();
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/CallManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/CallManager.java
new file mode 100644
index 00000000000..54444ba48fa
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/CallManager.java
@@ -0,0 +1,79 @@
+/**
+ * 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 {
+ }
+
+ 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 getCallEntries() throws FreeboxException {
+ List 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);
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ConfigurableRest.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ConfigurableRest.java
new file mode 100644
index 00000000000..7d57f93a394
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ConfigurableRest.java
@@ -0,0 +1,49 @@
+/**
+ * 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> extends RestManager {
+ protected static final String CONFIG_PATH = "config";
+
+ private final Class responseClazz;
+ private final @Nullable String configPath;
+
+ protected ConfigurableRest(FreeboxOsSession session, LoginManager.Permission required, Class 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);
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ConnectionManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ConnectionManager.java
new file mode 100644
index 00000000000..c500847a641
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ConnectionManager.java
@@ -0,0 +1,76 @@
+/**
+ * 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 {
+ private static final String PATH = "connection";
+
+ protected static class StatusResponse extends Response {
+ }
+
+ 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 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);
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FreeboxOsSession.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FreeboxOsSession.java
new file mode 100644
index 00000000000..d67b3edc7e9
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FreeboxOsSession.java
@@ -0,0 +1,192 @@
+/**
+ * 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, 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 > List execute(URI uri, HttpMethod method, Class 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 > List execute(URI uri, HttpMethod method, Class clazz,
+ @Nullable Object aPayload) throws FreeboxException {
+ return execute(uri, method, clazz, getSessionToken() != null, 3, aPayload);
+ }
+
+ @SuppressWarnings("unchecked")
+ public synchronized T getManager(Class clazz) throws FreeboxException {
+ RestManager manager = restManagers.get(clazz);
+ if (manager == null) {
+ try {
+ Constructor 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 addManager(Class 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;
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FreeplugManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FreeplugManager.java
new file mode 100644
index 00000000000..0444f878303
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FreeplugManager.java
@@ -0,0 +1,82 @@
+/**
+ * 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 {
+ }
+
+ 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 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 getPlugs() throws FreeboxException {
+ return get(Networks.class).stream().map(Network::members).flatMap(List::stream).toList();
+ }
+
+ public Optional 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);
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FtpManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FtpManager.java
new file mode 100644
index 00000000000..6be5801e895
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FtpManager.java
@@ -0,0 +1,53 @@
+/**
+ * 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 {
+ private static final String PATH = "ftp";
+
+ protected static class ConfigResponse extends Response {
+ }
+
+ 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();
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/HomeManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/HomeManager.java
new file mode 100644
index 00000000000..6fd9187016b
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/HomeManager.java
@@ -0,0 +1,173 @@
+/**
+ * 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 {
+ }
+
+ private static class HomeNodesResponse extends Response {
+ }
+
+ 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 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 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 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 showEndpoints, Map props, NodeType type) {
+ }
+
+ public HomeManager(FreeboxOsSession session) throws FreeboxException {
+ super(session, LoginManager.Permission.HOME, session.getUriBuilder().path(PATH));
+ }
+
+ public List 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 @Nullable EndpointState getEndpointsState(int nodeId, int stateSignalId) throws FreeboxException {
+ return getSingle(EndpointStateResponse.class, ENDPOINTS_PATH, String.valueOf(nodeId),
+ String.valueOf(stateSignalId));
+ }
+
+ public boolean putCommand(int nodeId, int stateSignalId, T value) throws FreeboxException {
+ put(new EndpointValue(value), ENDPOINTS_PATH, String.valueOf(nodeId), String.valueOf(stateSignalId));
+ return true;
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LanBrowserManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LanBrowserManager.java
new file mode 100644
index 00000000000..694d964f22d
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LanBrowserManager.java
@@ -0,0 +1,228 @@
+/**
+ * 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 {
+ 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 {
+ }
+
+ protected static class InterfacesResponse extends Response {
+ }
+
+ 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 names, List l3connectivities,
+ @Nullable LanAccessPoint accessPoint) {
+
+ public @Nullable LanAccessPoint accessPoint() {
+ return accessPoint;
+ }
+
+ public String vendorName() {
+ String localVendor = vendorName;
+ return localVendor != null ? localVendor : "Unknown";
+ }
+
+ public Optional getPrimaryName() {
+ return Optional.ofNullable(primaryName);
+ }
+
+ public Optional 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 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 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 getInterfaces() throws FreeboxException {
+ if (interfaces.isEmpty()) {
+ interfaces.addAll(getDevices());
+ }
+ return interfaces;
+ }
+
+ public synchronized List getHosts() throws FreeboxException {
+ List hosts = new ArrayList<>();
+
+ for (Interface intf : getInterfaces()) {
+ hosts.addAll(getInterfaceHosts(intf.name()));
+ }
+ return hosts;
+ }
+
+ public Optional 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 target = getHost(mac);
+ if (target.isPresent()) {
+ post(new WakeOnLineData(mac.toColonDelimitedString(), password), GenericResponse.class, WOL_ACTION,
+ target.get().intf.name);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LanManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LanManager.java
new file mode 100644
index 00000000000..e628cb2f2f8
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LanManager.java
@@ -0,0 +1,48 @@
+/**
+ * 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 {
+ private static final String PATH = "lan";
+
+ protected static class Config extends Response {
+ }
+
+ 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()));
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LcdManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LcdManager.java
new file mode 100644
index 00000000000..b19678f5458
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LcdManager.java
@@ -0,0 +1,64 @@
+/**
+ * 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 {
+ private static final String PATH = "lcd";
+
+ protected static class ConfigResponse extends Response {
+ }
+
+ 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 function) throws FreeboxException {
+ try {
+ setBrightness(function.call());
+ } catch (Exception e) {
+ throw new FreeboxException(e, "Error setting brightness");
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ListableRest.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ListableRest.java
new file mode 100644
index 00000000000..f88b30d89dd
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ListableRest.java
@@ -0,0 +1,48 @@
+/**
+ * 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> extends RestManager {
+ private final Class deviceResponseClass;
+
+ protected @Nullable String listSubPath = null;
+
+ public ListableRest(FreeboxOsSession session, LoginManager.Permission required, Class respClass, UriBuilder uri)
+ throws FreeboxException {
+ super(session, required, uri);
+ this.deviceResponseClass = respClass;
+ }
+
+ public List getDevices() throws FreeboxException {
+ return listSubPath == null ? get(deviceResponseClass) : get(deviceResponseClass, listSubPath);
+ }
+
+ public T getDevice(int deviceId) throws FreeboxException {
+ return getSingle(deviceResponseClass, Integer.toString(deviceId));
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LoginManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LoginManager.java
new file mode 100644
index 00000000000..41e38e235ad
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LoginManager.java
@@ -0,0 +1,156 @@
+/**
+ * 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 {
+ }
+
+ private static record Authorization(String appToken, String trackId) {
+ }
+
+ private static class AuthResponse extends Response {
+ }
+
+ 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 permissions,
+ @Nullable String sessionToken) {
+ protected boolean hasPermission(LoginManager.Permission checked) {
+ return Boolean.TRUE.equals(permissions.get(checked));
+ }
+ }
+
+ private static class SessionResponse extends Response {
+ }
+
+ 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 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");
+ };
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/MediaReceiverManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/MediaReceiverManager.java
new file mode 100644
index 00000000000..da7849b9e3c
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/MediaReceiverManager.java
@@ -0,0 +1,82 @@
+/**
+ * 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 {
+ private static final String SUB_PATH = "receivers";
+
+ public static record Receiver(boolean passwordProtected, //
+ Map capabilities, //
+ String name // This name is the UPnP name of the host
+ ) {
+ }
+
+ protected static class ReceiverResponse extends Response {
+ }
+
+ 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);
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/NetShareManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/NetShareManager.java
new file mode 100644
index 00000000000..617eac47df0
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/NetShareManager.java
@@ -0,0 +1,32 @@
+/**
+ * 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()));
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/PhoneManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/PhoneManager.java
new file mode 100644
index 00000000000..dc797a177d3
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/PhoneManager.java
@@ -0,0 +1,118 @@
+/**
+ * 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 {
+ 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 {
+ }
+
+ protected class StatusResponse extends Response {
+ }
+
+ 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 getPhoneStatuses() throws FreeboxException {
+ return get(StatusResponse.class, "");
+ }
+
+ public Optional 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 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 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;
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/PlayerManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/PlayerManager.java
new file mode 100644
index 00000000000..7ef7907ecde
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/PlayerManager.java
@@ -0,0 +1,246 @@
+/**
+ * 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 {
+ private static final String STATUS_PATH = "status";
+
+ protected static class PlayerResponse extends Response {
+ }
+
+ 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 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 {
+ }
+
+ 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 {
+ }
+
+ 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 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 subtitleList,
+ @SerializedName("videoIndex") int videoIndex, @SerializedName("videoList") List 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 services,
+ @SerializedName("videoIsVisible") boolean videoIsVisible) {
+ }
+
+ public static record TvContext(@Nullable Channel channel, @Nullable PlayerDetails player) {
+ }
+
+ private final Map 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);
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/RepeaterManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/RepeaterManager.java
new file mode 100644
index 00000000000..d76b4a646a8
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/RepeaterManager.java
@@ -0,0 +1,109 @@
+/**
+ * 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 {
+
+ protected static class RepeaterResponse extends Response {
+ }
+
+ protected static class RepeaterLedResponse extends Response {
+ }
+
+ 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 getRepeaterHosts(int id) throws FreeboxException {
+ return get(HostsResponse.class, Integer.toString(id), THING_HOST);
+ }
+
+ public synchronized List getHosts() throws FreeboxException {
+ List hosts = new ArrayList<>();
+ for (Repeater rep : getDevices()) {
+ if (Connection.CONNECTED.equals(rep.connection)) {
+ hosts.addAll(getRepeaterHosts(rep.id));
+ }
+ }
+ return hosts;
+ }
+
+ public Optional 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 led(int id, boolean enable) throws FreeboxException {
+ RepeaterLed result = put(RepeaterLedResponse.class, new RepeaterLed(id, enable), Integer.toString(id));
+ return Optional.ofNullable(result);
+ }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/RestManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/RestManager.java
new file mode 100644
index 00000000000..bb1a57c279d
--- /dev/null
+++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/RestManager.java
@@ -0,0 +1,97 @@
+/**
+ * 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