[Freeboxos] New binding alternative to Freebox binding (#12342)

* SAT warnings handling

Signed-off-by: clinique <gael@lhopital.org>

* Correcting potential NPE

Signed-off-by: clinique <gael@lhopital.org>

* Correcting a NPE on error

Signed-off-by: clinique <gael@lhopital.org>

* Active player request falls to incorrect API version

Signed-off-by: clinique <gael@lhopital.org>

* Reintroducing missing capability to send keys to player.
Solving an NPE

Signed-off-by: clinique <gael@lhopital.org>

* Handling DUTY CYCLE more gracefully

Signed-off-by: clinique <gael@lhopital.org>

* Enhancing DUTY CYCLE

Signed-off-by: clinique <gael@lhopital.org>

* Moving to SNAPSHOT 3.4

Signed-off-by: clinique <gael@lhopital.org>

* Adress inconsistencies in binding name

Signed-off-by: clinique <gael@lhopital.org>

* Discover Freebox Delta Home equipments(basic_shutter)

* Clean previous test code

* Fix "Unexpected command"

* Fix thing comm error

* README for basic shutter

* Fix MR discusions and solve maven check errors and warnings

* Fix MR discusions

* Fix README.md

* Enhancing logging to indentify source of erratic warn

Signed-off-by: clinique <gael@lhopital.org>

* Deny polling a device data when its API is needed and it is OFFLINE

Signed-off-by: clinique <gael@lhopital.org>

* Taking #11833 in accound

Signed-off-by: clinique <gael@lhopital.org>

* Switching to Snapshot 4.0.0
Correcting apiDomain was not used as expected
Code cleansing.

Signed-off-by: clinique <gael@lhopital.org>

* Implementing SHUTTER Home Node

Signed-off-by: clinique <gael@lhopital.org>

* Saving work before instroduction of ArrayListDeserializer

Signed-off-by: clinique <gael@lhopital.org>

* Enhanced deserialization to simplify code

Signed-off-by: clinique <gael@lhopital.org>

* Switching to Java 17 records

Signed-off-by: clinique <gael@lhopital.org>

* Switching to addons.xml, headers updated

Signed-off-by: clinique <gael@lhopital.org>

* Correcting two errors.

Signed-off-by: clinique <gael@lhopital.org>

* Enhance usage of global variables

Signed-off-by: clinique <gael@lhopital.org>

* Some code enhancement for base classes

Signed-off-by: clinique <gael@lhopital.org>

* solving SAT issues

Signed-off-by: clinique <gael@lhopital.org>

* Adding IliadBox compatibility

Signed-off-by: clinique <gael@lhopital.org>

* Commiting work

Signed-off-by: clinique <gael@lhopital.org>

* Saving work

Signed-off-by: clinique <gael@lhopital.org>

* Rebooting Home Node part

Signed-off-by: clinique <gael@lhopital.org>

* Spotless apply

Signed-off-by: clinique <gael@lhopital.org>

* Adding i18n

Signed-off-by: clinique <gael@lhopital.org>

* Decreasing websocket logging level

Signed-off-by: clinique <gael@lhopital.org>

* SAT warnings handling

Signed-off-by: clinique <gael@lhopital.org>

* Correcting potential NPE

Signed-off-by: clinique <gael@lhopital.org>

* Correcting a NPE on error

Signed-off-by: clinique <gael@lhopital.org>

* Active player request falls to incorrect API version

Signed-off-by: clinique <gael@lhopital.org>

* Reintroducing missing capability to send keys to player.
Solving an NPE

Signed-off-by: clinique <gael@lhopital.org>

* Handling DUTY CYCLE more gracefully

Signed-off-by: clinique <gael@lhopital.org>

* Enhancing DUTY CYCLE

Signed-off-by: clinique <gael@lhopital.org>

* Moving to SNAPSHOT 3.4

Signed-off-by: clinique <gael@lhopital.org>

* Adress inconsistencies in binding name

Signed-off-by: clinique <gael@lhopital.org>

* Discover Freebox Delta Home equipments(basic_shutter)

* Clean previous test code

* Fix "Unexpected command"

* Fix thing comm error

* README for basic shutter

* Fix MR discusions and solve maven check errors and warnings

* Fix MR discusions

* Fix README.md

* Enhancing logging to indentify source of erratic warn

Signed-off-by: clinique <gael@lhopital.org>

* Deny polling a device data when its API is needed and it is OFFLINE

Signed-off-by: clinique <gael@lhopital.org>

* Taking #11833 in accound

Signed-off-by: clinique <gael@lhopital.org>

* Switching to Snapshot 4.0.0
Correcting apiDomain was not used as expected
Code cleansing.

Signed-off-by: clinique <gael@lhopital.org>

* Implementing SHUTTER Home Node

Signed-off-by: clinique <gael@lhopital.org>

* Saving work before instroduction of ArrayListDeserializer

Signed-off-by: clinique <gael@lhopital.org>

* Enhanced deserialization to simplify code

Signed-off-by: clinique <gael@lhopital.org>

* Switching to Java 17 records

Signed-off-by: clinique <gael@lhopital.org>

* Switching to addons.xml, headers updated

Signed-off-by: clinique <gael@lhopital.org>

* Correcting two errors.

Signed-off-by: clinique <gael@lhopital.org>

* Enhance usage of global variables

Signed-off-by: clinique <gael@lhopital.org>

* Some code enhancement for base classes

Signed-off-by: clinique <gael@lhopital.org>

* solving SAT issues

Signed-off-by: clinique <gael@lhopital.org>

* Adding IliadBox compatibility

Signed-off-by: clinique <gael@lhopital.org>

* Commiting work

Signed-off-by: clinique <gael@lhopital.org>

* Saving work

Signed-off-by: clinique <gael@lhopital.org>

* Rebooting Home Node part

Signed-off-by: clinique <gael@lhopital.org>

* Spotless apply

Signed-off-by: clinique <gael@lhopital.org>

* Enhancing SAT report

Signed-off-by: clinique <gael@lhopital.org>

* I think that mvn spotless:apply has a problem with records - trying once again

Signed-off-by: clinique <gael@lhopital.org>

* Avoid requesting detailed information for a shutdown repeater.

Signed-off-by: clinique <gael@lhopital.org>

* Switched fan speed to RPM unit

Signed-off-by: clinique <gael@lhopital.org>

* Correcting SAT

Signed-off-by: clinique <gael@lhopital.org>

* Correcting SAT

Signed-off-by: clinique <gael@lhopital.org>

* Divergence between eclipse and mvn spotless:apply

Signed-off-by: clinique <gael@lhopital.org>

* YASAT

Signed-off-by: clinique <gael@lhopital.org>

* Corrections following fwolter code review

Signed-off-by: clinique <gael@lhopital.org>

* Pleasing SAT

Signed-off-by: clinique <gael@lhopital.org>

* Second fwolter code review

Signed-off-by: clinique <gael@lhopital.org>

* Porting modifications introduced in PR #15121

Signed-off-by: clinique <gael@lhopital.org>

* Removing redundant null checks.

Signed-off-by: clinique <gael@lhopital.org>

* Rebased.

Signed-off-by: clinique <gael@lhopital.org>

* Trying to remove the last sleep.

Signed-off-by: clinique <gael@lhopital.org>

* Reporting modifications of PR #15121

Signed-off-by: clinique <gael@lhopital.org>

* Reverting to working and cleaner granting process

Signed-off-by: clinique <gael@lhopital.org>

* Removing last Thread:Sleep

Signed-off-by: clinique <gael@lhopital.org>

* spotless:apply

Signed-off-by: clinique <gael@lhopital.org>

---------

Signed-off-by: clinique <gael@lhopital.org>
Co-authored-by: ben.12 <benmor_12@yahoo.fr>
This commit is contained in:
Gaël L'hopital 2023-07-05 20:13:29 +02:00 committed by GitHub
parent 123982dcc6
commit 751c8a74c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
118 changed files with 9648 additions and 0 deletions

View File

@ -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

View File

@ -551,6 +551,11 @@
<artifactId>org.openhab.binding.freebox</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.freeboxos</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.fronius</artifactId>

View File

@ -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

View File

@ -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 |

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>4.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.freeboxos</artifactId>
<name>openHAB Add-ons :: Bundles :: FreeboxOS Binding</name>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>com.github.seancfoley</groupId>
<artifactId>ipaddress</artifactId>
<version>5.4.0</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.freeboxos-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-freeboxos" description="Freebox OS Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-mdns</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.freeboxos/${project.version}</bundle>
</feature>
</features>

View File

@ -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<ThingTypeUID> BRIDGE_TYPE_UIDS = Set.of(BRIDGE_TYPE_API);
public static final Set<ThingTypeUID> THINGS_TYPES_UIDS = Set.of(THING_TYPE_FXS, THING_TYPE_DECT, THING_TYPE_CALL,
THING_TYPE_HOST, THING_TYPE_VM, THING_TYPE_PLAYER, THING_TYPE_ACTIVE_PLAYER, THING_TYPE_DELTA,
THING_TYPE_REVOLUTION, THING_TYPE_REPEATER, THING_TYPE_WIFI_HOST, THING_TYPE_FREEPLUG);
public static final Set<ThingTypeUID> HOME_TYPES_UIDS = Set.of(Category.BASIC_SHUTTER.getThingTypeUID(),
Category.SHUTTER.getThingTypeUID(), Category.KFB.getThingTypeUID(), Category.CAMERA.getThingTypeUID(),
Category.ALARM.getThingTypeUID());
protected static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
.of(BRIDGE_TYPE_UIDS, THINGS_TYPES_UIDS, HOME_TYPES_UIDS).flatMap(Set::stream).collect(Collectors.toSet());
// Thing properties
// public static final String LAST_CALL_TIMESTAMP = "lastCallTimestamp";
public static final String ROLE = "role";
public static final String NET_ID = "netId";
public static final String ETHERNET_SPEED = "ethernetSpeed";
public static final String LOCAL = "local";
public static final String FULL_DUPLEX = "fullDuplex";
// List of all Group Channel ids
public static final String GROUP_SENSORS = "sensors";
public static final String GROUP_FANS = "fans";
public static final String CONNECTION_STATUS = "connection-status";
public static final String SYS_INFO = "sysinfo";
public static final String ACTIONS = "actions";
public static final String FILE_SHARING = "file-sharing";
public static final String CONNECTIVITY = "connectivity";
public static final String DISPLAY = "display";
public static final String VM_STATUS = "vmstatus";
public static final String GROUP_WIFI = "wifi";
public static final String REPEATER_MISC = "repeater-misc";
// List of all Channel ids
public static final String RSSI = "rssi";
public static final String SSID = "ssid";
public static final String WIFI_QUALITY = "wifi-quality";
public static final String WIFI_HOST = "wifi-host";
public static final String UPTIME = "uptime";
public static final String BOX_EVENT = "box-event";
public static final String LCD_BRIGHTNESS = "lcd-brightness";
public static final String LCD_ORIENTATION = "lcd-orientation";
public static final String LCD_FORCED = "lcd-forced";
public static final String WIFI_STATUS = "wifi-status";
public static final String IP_ADDRESS = "ip-address";
public static final String IPV6_ADDRESS = "ipv6-address";
public static final String LINE_STATUS = "line-status";
public static final String LINE_TYPE = "line-type";
public static final String LINE_MEDIA = "line-media";
public static final String PLAYER_STATUS = "player-status";
public static final String PACKAGE = "package";
public static final String RATE = "rate";
public static final String BYTES_UP = "bytes-up";
public static final String BYTES_DOWN = "bytes-down";
public static final String BW = "bandwidth";
public static final String PCT_BW = "bandwidth-usage";
public static final String ONHOOK = "onhook";
public static final String RINGING = "ringing";
public static final String HARDWARE_STATUS = "hardware-status";
public static final String TELEPHONY_SERVICE = "telephony-service";
public static final String GAIN_RX = "gain-rx";
public static final String GAIN_TX = "gain-tx";
public static final String FTP_STATUS = "ftp-status";
public static final String SAMBA_FILE_STATUS = "samba-file-status";
public static final String SAMBA_PRINTER_STATUS = "samba-printer-status";
public static final String AFP_FILE_STATUS = "afp-file-status";
public static final String REACHABLE = "reachable";
public static final String LAST_SEEN = "last-seen";
public static final String ALTERNATE_RING = "lcd-forced";
public static final String DECT_ACTIVE = "dect-active";
public static final String UPNPAV_STATUS = "upnpav-status";
// Call channels for groups Accepted, Missed and Outgoing
public static final String NUMBER = "number";
public static final String DURATION = "duration";
public static final String TIMESTAMP = "timestamp";
public static final String NAME = "name";
// Freebox player channels
public static final String AIRMEDIA_STATUS = "airmedia-status";
public static final String KEY_CODE = "key-code";
// Virtual machine channels
public static final String STATUS = "status";
// Repeater channels
public static final String LED = "led";
public static final String HOST_COUNT = "host-count";
// Home channels
public static final String KEYFOB_ENABLE = "enable";
public static final String NODE_BATTERY = "battery";
public static final String SHUTTER_POSITION = "position-set";
public static final String SHUTTER_STOP = "stop";
public static final String BASIC_SHUTTER_STATE = "state";
public static final String BASIC_SHUTTER_UP = "up";
public static final String BASIC_SHUTTER_DOWN = "down";
// public static final String BASIC_SHUTTER_CMD = "basic-shutter";
public static final String ALARM_PIN = "pin";
public static final String ALARM_SOUND = "sound";
public static final String ALARM_VOLUME = "volume";
public static final String ALARM_TIMEOUT1 = "timeout1";
public static final String ALARM_TIMEOUT2 = "timeout2";
public static final String ALARM_TIMEOUT3 = "timeout3";
public static final Set<Command> TRUE_COMMANDS = Set.of(OnOffType.ON, UpDownType.UP, OpenClosedType.OPEN);
public static final Set<Class<?>> ON_OFF_CLASSES = Set.of(OnOffType.class, UpDownType.class, OpenClosedType.class);
}

View File

@ -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<String, Object> config) {
super.activate(componentContext);
this.audioHTTPServer = audioHTTPServer;
this.httpClient = httpClientFactory.getCommonHttpClient();
this.networkAddressService = networkAddressService;
this.apiHandler = new ApiHandler(httpClient, timeZoneProvider);
configChanged(config);
}
@Modified
public void configChanged(Map<String, Object> config) {
String timeout = (String) config.getOrDefault(TIMEOUT, "8");
apiHandler.setTimeout(TimeUnit.SECONDS.toMillis(Long.parseLong(timeout)));
callbackURL = (String) config.getOrDefault(CALLBACK_URL, "");
int port = HttpServiceUtil.getHttpServicePort(bundleContext);
if (callbackURL.isEmpty() && port != -1) {
String openHabIp = Objects.requireNonNull(networkAddressService.getPrimaryIpv4HostAddress());
// we do not use SSL as it can cause certificate validation issues.
callbackURL = "http://%s:%d".formatted(openHabIp, port);
}
if (callbackURL.isEmpty()) {
logger.warn("Unable to build a correct call back URL to stream media contents");
return;
}
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (BRIDGE_TYPE_API.equals(thingTypeUID)) {
return new FreeboxOsHandler((Bridge) thing, new FreeboxOsSession(apiHandler), callbackURL, bundleContext,
audioHTTPServer);
} else if (THING_TYPE_FREEPLUG.equals(thingTypeUID)) {
return new FreeplugHandler(thing);
} else if (THING_TYPE_FXS.equals(thingTypeUID)) {
return new FxsHandler(thing);
} else if (THING_TYPE_DECT.equals(thingTypeUID)) {
return new DectHandler(thing);
} else if (THING_TYPE_CALL.equals(thingTypeUID)) {
return new CallHandler(thing);
} else if (THING_TYPE_REVOLUTION.equals(thingTypeUID)) {
return new RevolutionHandler(thing);
} else if (THING_TYPE_DELTA.equals(thingTypeUID)) {
return new ServerHandler(thing);
} else if (THING_TYPE_HOST.equals(thingTypeUID)) {
return new HostHandler(thing);
} else if (THING_TYPE_WIFI_HOST.equals(thingTypeUID)) {
return new WifiStationHandler(thing);
} else if (THING_TYPE_REPEATER.equals(thingTypeUID)) {
return new RepeaterHandler(thing);
} else if (THING_TYPE_VM.equals(thingTypeUID)) {
return new VmHandler(thing);
} else if (THING_TYPE_ACTIVE_PLAYER.equals(thingTypeUID)) {
return new ActivePlayerHandler(thing);
} else if (THING_TYPE_PLAYER.equals(thingTypeUID)) {
return new PlayerHandler(thing);
} else if (Category.BASIC_SHUTTER.getThingTypeUID().equals(thingTypeUID)) {
return new BasicShutterHandler(thing);
} else if (Category.SHUTTER.getThingTypeUID().equals(thingTypeUID)) {
return new ShutterHandler(thing);
} else if (Category.ALARM.getThingTypeUID().equals(thingTypeUID)) {
return new AlarmHandler(thing);
} else if (Category.KFB.getThingTypeUID().equals(thingTypeUID)) {
return new KeyfobHandler(thing);
} else if (Category.CAMERA.getThingTypeUID().equals(thingTypeUID)) {
return new CameraHandler(thing);
}
return null;
}
}

View File

@ -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();
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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<ZonedDateTime>) (json, type, jsonDeserializationContext) -> {
long timestamp = json.getAsJsonPrimitive().getAsLong();
Instant i = Instant.ofEpochSecond(timestamp);
return ZonedDateTime.ofInstant(i, timeZoneProvider.getTimeZone());
})
.registerTypeAdapter(MACAddress.class,
(JsonDeserializer<MACAddress>) (json, type,
jsonDeserializationContext) -> new MACAddressString(json.getAsString()).getAddress())
.registerTypeAdapter(IPAddress.class,
(JsonDeserializer<IPAddress>) (json, type,
jsonDeserializationContext) -> new IPAddressString(json.getAsString()).getAddress())
.registerTypeAdapter(ForegroundApp.class, new ForegroundAppDeserializer())
.registerTypeAdapter(List.class, new ListDeserializer()).serializeNulls()
.registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory()).create();
}
public synchronized <T> T executeUri(URI uri, HttpMethod method, Class<T> clazz, @Nullable String sessionToken,
@Nullable Object payload) throws FreeboxException, InterruptedException {
logger.debug("executeUrl {}: {} ", method, uri);
Request request = httpClient.newRequest(uri).method(method).timeout(timeoutInMs, TimeUnit.MILLISECONDS)
.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE);
if (sessionToken != null) {
request.header(AUTH_HEADER, sessionToken);
}
if (payload != null) {
request.content(new StringContentProvider(serialize(payload), DEFAULT_CHARSET), null);
}
try {
ContentResponse response = request.send();
Code statusCode = HttpStatus.getCode(response.getStatus());
if (statusCode != Code.OK && statusCode != Code.FORBIDDEN) {
throw new FreeboxException(statusCode.getMessage());
}
String content = new String(response.getContent(), DEFAULT_CHARSET);
T result = deserialize(clazz, content);
logger.trace("executeUrl {} - {} returned {}", method, uri, content);
if (statusCode == Code.OK) {
return result;
} else if (statusCode == Code.FORBIDDEN) {
logger.debug("Fobidden, serviceReponse was {}, ", content);
if (result instanceof Response<?> errorResponse) {
throw new FreeboxException(errorResponse.getErrorCode(), errorResponse.getMsg());
}
}
throw new FreeboxException("Error '%s' requesting: %s", statusCode.getMessage(), uri.toString());
} catch (TimeoutException | ExecutionException e) {
throw new FreeboxException(e, "Exception while calling %s", request.getURI());
}
}
public <T> T deserialize(Class<T> clazz, String json) {
@Nullable
T result = gson.fromJson(json, clazz);
if (result != null) {
return result;
}
throw new IllegalArgumentException("Null result deserializing '%s', please file a bug report.".formatted(json));
}
public String serialize(Object payload) {
return gson.toJson(payload);
}
public HttpClient getHttpClient() {
return httpClient;
}
public void setTimeout(long millis) {
timeoutInMs = millis;
logger.debug("Timeout set to {} ms", timeoutInMs);
}
}

View File

@ -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;
}
}

View File

@ -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<IconSet> getIconSets(@Nullable Locale locale) {
return Set.of();
}
@Override
protected Integer getPriority() {
return 4;
}
@Override
protected @Nullable InputStream getResource(String iconSetId, String resourceName) {
URI uri = uriBuilder.clone().path(resourceName).build();
Request request = httpClient.newRequest(uri).method(HttpMethod.GET);
try {
ContentResponse response = request.send();
if (HttpStatus.getCode(response.getStatus()) == Code.OK) {
return new ByteArrayInputStream(response.getContent());
}
} catch (InterruptedException | TimeoutException | ExecutionException e) {
logger.warn("Error getting icon {}: {}", resourceName, e.getMessage());
}
return null;
}
@Override
protected boolean hasResource(String iconSetId, String resourceName) {
return resourceName.contains(".png") && getResource(iconSetId, resourceName) != null;
}
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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<ResultType> {
public static enum ErrorCode {
AUTH_REQUIRED,
BAD_LOGIN,
TOO_SHORT,
IN_DICTIONNARY,
BAD_XKCD,
NOT_ENOUGH_DIFFERENT_CHARS,
INVALID_TOKEN,
PENDING_TOKEN,
INSUFFICIENT_RIGHTS,
DENIED_FROM_EXTERNAL_IP,
INVALID_REQUEST,
RATELIMITED,
NEW_APPS_DENIED,
APPS_AUTHORIZATION_DENIED,
APPS_AUTHORIZATION_TIMEOUT,
PASSWORD_RESET_DENIED,
APPS_DENIED,
INTERNAL_ERROR,
SERVICE_DOWN,
DISK_FULL,
OP_FAILED,
DISK_BUSY,
ARRAY_START_FAILED,
ARRAY_STOP_FAILED,
ARRAY_NOT_FOUND,
INVAL,
NODEV,
NOENT,
NETDOWN,
BUSY,
INVALID_PORT,
INSECURE_PASSWORD,
INVALID_PROVIDER,
INVALID_NEXT_HOP,
INVALID_API_VERSION,
INVAL_WPS_MACFILTER,
INVAL_WPS_NEEDS_CCMP,
INVALID_ID,
PATH_NOT_FOUND,
ACCESS_DENIED,
DESTINATION_CONFLICT,
CANCELLED,
TASK_NOT_FOUND,
HTTP,
INVALID_URL,
INVALID_OPERATION,
INVALID_FILE,
CTX_FILE_ERROR,
HIBERNATING,
TOO_MANY_TASKS,
EXISTS,
EXIST,
CONNECTION_REFUSED,
NO_FREEBOX,
ALREADY_AUTHORIZED,
ECRC,
ERR_001,
ERR_002,
ERR_003,
ERR_004,
ERR_005,
ERR_009,
ERR_010,
ERR_030,
ERR_031,
NONE,
UNKNOWN;
}
private ErrorCode errorCode = ErrorCode.NONE;
private LoginManager.Permission missingRight = LoginManager.Permission.NONE;
private String msg = "";
private List<ResultType> result = List.of();
private boolean success;
// In some cases I did not understand deserialization can still produce null result
@SuppressWarnings("null")
public List<ResultType> getResult() {
List<ResultType> localResult = result;
return localResult != null ? localResult : List.of();
}
public boolean isSuccess() {
return success;
}
public LoginManager.Permission getMissingRight() {
return missingRight;
}
public ErrorCode getErrorCode() {
return errorCode;
}
public String getMsg() {
return msg;
}
}

View File

@ -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<ForegroundApp> {
@Override
public @NonNull ForegroundApp deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
Object obj;
String thePackage = json.getAsJsonObject().get("package").getAsString();
JsonElement jsonElement2 = json.getAsJsonObject().get("context");
if (jsonElement2 == null) {
obj = null;
} else if ("fr.freebox.tv".equals(thePackage)) {
obj = context.deserialize(jsonElement2, TvContext.class);
} else {
obj = context.deserialize(jsonElement2, PlayerContext.class);
}
int packageId = json.getAsJsonObject().get("package_id").getAsInt();
String curlUrl = json.getAsJsonObject().get("cur_url").getAsString();
Objects.requireNonNull(thePackage);
return new ForegroundApp(packageId, curlUrl, obj, thePackage);
}
}

View File

@ -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<List<?>> {
@Override
public @NonNull List<?> deserialize(@Nullable JsonElement json, @Nullable Type clazz,
@Nullable JsonDeserializationContext context) throws JsonParseException {
if (json != null && clazz != null && context != null) {
JsonArray jsonArray = toJsonArray(json);
ArrayList<?> result = new ArrayList<>(jsonArray != null ? jsonArray.size() : 0);
if (jsonArray != null) {
Type[] typeArguments = ((ParameterizedType) clazz).getActualTypeArguments();
if (typeArguments.length > 0) {
Type objectType = typeArguments[0];
for (int i = 0; i < jsonArray.size(); i++) {
result.add(context.deserialize(jsonArray.get(i), objectType));
}
return result;
}
}
}
return List.of();
}
private @Nullable JsonArray toJsonArray(JsonElement json) {
if (json instanceof JsonArray) {
return json.getAsJsonArray();
} else if (json instanceof JsonObject) {
JsonArray jsonArray = new JsonArray();
if (json.getAsJsonObject().size() > 0) {
jsonArray.add(json);
}
return jsonArray;
}
return null;
}
}

View File

@ -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 <T> TypeAdapter<T> create(@NonNullByDefault({}) Gson gson,
@NonNullByDefault({}) TypeToken<T> type) {
@SuppressWarnings("unchecked")
Class<T> rawType = (Class<T>) type.getRawType();
return rawType.isEnum() ? newStrictEnumAdapter(gson.getDelegateAdapter(this, type)) : null;
}
private <T> TypeAdapter<T> newStrictEnumAdapter(TypeAdapter<T> delegateAdapter) {
return new TypeAdapter<T>() {
@Override
public void write(JsonWriter out, @Nullable T value) throws IOException {
delegateAdapter.write(out, value);
}
@Override
public @NonNull T read(JsonReader in) throws IOException {
String searched = in.nextString().toUpperCase().replace("/", "_").replace("-", "_");
JsonReader delegateReader = new JsonReader(new StringReader('"' + searched + '"'));
@Nullable
T value = delegateAdapter.read(delegateReader);
delegateReader.close();
if (value == null) {
UNKNOWN.reset();
value = delegateAdapter.read(new JsonReader(UNKNOWN));
}
return Objects.requireNonNull(value);
}
};
}
}

View File

@ -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<APManager.WifiAp, APManager.APResponse> {
private static final String PATH = "ap";
private static final String STATIONS_PATH = "stations";
protected static record WifiInformation(String ssid, String band, int signal) { // Valid RSSI goes from -120 to 0
}
public static record LanAccessPoint(String mac, String type, String uid, @Nullable String connectivityType,
long rxBytes, // received bytes (from station to Freebox)
long txBytes, // transmitted bytes (from Freebox to station)
long txRate, // reception data rate (in bytes/s)
long rxRate, // transmission data rate (in bytes/s)
WifiInformation wifiInformation) {
public int getSignal() {
return wifiInformation.signal();
}
public @Nullable String getSsid() {
return wifiInformation().ssid();
}
}
private static enum State {
ASSOCIATED,
AUTHENTICATED,
UNKNOWN;
}
public static record Station(String id, MACAddress mac, String bssid, @Nullable String hostname, LanHost host,
State state, int inactive, int connDuration, //
long rxBytes, // received bytes (from station to Freebox)
long txBytes, // transmitted bytes (from Freebox to station)
long txRate, // reception data rate (in bytes/s)
long rxRate, // transmission data rate (in bytes/s)
int signal) { // signal attenuation (in dB)
public @Nullable String getSsid() {
LanAccessPoint accessPoint = host.accessPoint();
return accessPoint != null ? accessPoint.getSsid() : null;
}
public @Nullable ZonedDateTime getLastSeen() {
return host.getLastSeen();
}
}
protected static record ApStatus(ApState state, int channelWidth, int primaryChannel, int secondaryChannel,
int dfsCacRemainingTime, boolean dfsDisabled) {
private static enum ApState {
SCANNING, // Ap is probing wifi channels
NO_PARAM, // Ap is not configured
BAD_PARAM, // Ap has an invalid configuration
DISABLED, // Ap is permanently disabled
DISABLED_PLANNING, // Ap is currently disabled according to planning
NO_ACTIVE_BSS, // Ap has no active BSS
STARTING, // Ap is starting
ACS, // Ap is selecting the best available channel
HT_SCAN, // Ap is scanning for other access point
DFS, // Ap is performing dynamic frequency selection
ACTIVE, // Ap is active
FAILED, // Ap has failed to start
UNKNOWN;
}
}
protected static record WifiAp(int id, String name, ApStatus status) {
}
private class ApHostsResponse extends Response<Station> {
}
protected class APResponse extends Response<WifiAp> {
}
public APManager(FreeboxOsSession session, UriBuilder uriBuilder) throws FreeboxException {
super(session, LoginManager.Permission.NONE, APResponse.class, uriBuilder.path(PATH));
}
private List<Station> getApStations(int apId) throws FreeboxException {
return get(ApHostsResponse.class, Integer.toString(apId), STATIONS_PATH);
}
public List<Station> getStations() throws FreeboxException {
List<Station> hosts = new ArrayList<>();
for (WifiAp ap : getDevices()) {
hosts.addAll(getApStations(ap.id));
}
return hosts;
}
public Optional<Station> getStation(MACAddress mac) throws FreeboxException {
return getStations().stream().filter(host -> host.mac().equals(mac)).findFirst();
}
}

View File

@ -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<AfpManager.Afp, AfpManager.ConfigResponse> {
private static final String AFP_PATH = "afp";
protected static class ConfigResponse extends Response<Afp> {
}
protected static record Afp(boolean enabled, boolean guestAllow, ServerType serverType, @Nullable String loginName,
@Nullable String loginPassword) {
private static enum ServerType {
POWERBOOK,
POWERMAC,
MACMINI,
IMAC,
MACBOOK,
MACBOOKPRO,
MACBOOKAIR,
MACPRO,
APPLETV,
AIRPORT,
XSERVE,
UNKNOWN;
}
}
public AfpManager(FreeboxOsSession session, UriBuilder uriBuilder) throws FreeboxException {
super(session, LoginManager.Permission.NONE, ConfigResponse.class, uriBuilder.path(AFP_PATH), null);
}
public boolean getStatus() throws FreeboxException {
return getConfig().enabled;
}
public boolean setStatus(boolean enabled) throws FreeboxException {
Afp config = getConfig();
Afp newConfig = new Afp(enabled, config.guestAllow, config.serverType, config.loginName, config.loginPassword);
return setConfig(newConfig).enabled;
}
}

View File

@ -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<AirMediaManager.Config, AirMediaManager.ConfigResponse> {
private static final String PATH = "airmedia";
protected static record Config(boolean enabled) {
}
protected static class ConfigResponse extends Response<Config> {
}
public AirMediaManager(FreeboxOsSession session) throws FreeboxException {
super(session, LoginManager.Permission.NONE, ConfigResponse.class, session.getUriBuilder().path(PATH),
CONFIG_PATH);
session.addManager(MediaReceiverManager.class, new MediaReceiverManager(session, getUriBuilder()));
}
public boolean getStatus() throws FreeboxException {
return getConfig().enabled();
}
public boolean setStatus(boolean enabled) throws FreeboxException {
return setConfig(new Config(enabled)).enabled();
}
}

View File

@ -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<Call> {
}
public static enum Type {
ACCEPTED,
MISSED,
OUTGOING,
INCOMING,
UNKNOWN;
}
public static record Call(Type type, //
ZonedDateTime datetime, // Call creation timestamp.
String number, // Calling or called number
int duration, // Call duration in seconds.
String name) {
public @Nullable String name() {
return name.equals(number) ? null : name;
}
}
public CallManager(FreeboxOsSession session) throws FreeboxException {
super(session, LoginManager.Permission.CALLS, session.getUriBuilder().path(THING_CALL));
}
// Retrieves a sorted list of all call entries
public List<Call> getCallEntries() throws FreeboxException {
List<Call> callList = new ArrayList<>(
get(Calls.class, LOG_SUB_PATH).stream().sorted(Comparator.comparing(Call::datetime)).toList());
Call last = callList.get(callList.size() - 1);
// The INCOMING type call can only be set on the last call if its duration is 0;
if (last.type == Type.MISSED && last.duration == 0) {
callList.remove(callList.size() - 1);
callList.add(new Call(Type.INCOMING, last.datetime, last.number, 0, last.name));
}
return callList;
}
public void emptyQueue() throws FreeboxException {
post(LOG_SUB_PATH, DELETE_ACTION);
}
}

View File

@ -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<T, Y extends Response<T>> extends RestManager {
protected static final String CONFIG_PATH = "config";
private final Class<Y> responseClazz;
private final @Nullable String configPath;
protected ConfigurableRest(FreeboxOsSession session, LoginManager.Permission required, Class<Y> responseClazz,
UriBuilder uri, @Nullable String configPath) throws FreeboxException {
super(session, required, uri);
this.responseClazz = responseClazz;
this.configPath = configPath;
}
public T getConfig() throws FreeboxException {
return configPath != null ? getSingle(responseClazz, configPath) : getSingle(responseClazz);
}
protected T setConfig(T config) throws FreeboxException {
return configPath != null ? put(responseClazz, config, configPath) : put(responseClazz, config);
}
}

View File

@ -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<ConnectionManager.Status, ConnectionManager.StatusResponse> {
private static final String PATH = "connection";
protected static class StatusResponse extends Response<Status> {
}
private static enum State {
GOING_UP,
UP,
GOING_DOWN,
DOWN,
UNKNOWN;
}
private static enum Type {
ETHERNET,
RFC2684,
PPPOATM,
UNKNOWN;
}
private static enum Media {
FTTH,
ETHERNET,
XDSL,
BACKUP_4G,
UNKNOWN;
}
public static record Status(State state, Type type, Media media, @Nullable List<Integer> ipv4PortRange,
@Nullable IPAddress ipv4, // This can be null if state is not up
@Nullable IPAddress ipv6, // This can be null if state is not up
long rateUp, // current upload rate in byte/s
long rateDown, // current download rate in byte/s
long bandwidthUp, // available upload bandwidth in bit/s
long bandwidthDown, // available download bandwidth in bit/s
long bytesUp, // total uploaded bytes since last connection
long bytesDown // total downloaded bytes since last connection
) {
}
public ConnectionManager(FreeboxOsSession session) throws FreeboxException {
super(session, LoginManager.Permission.NONE, StatusResponse.class, session.getUriBuilder().path(PATH), null);
}
}

View File

@ -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<Class<? extends RestManager>, RestManager> restManagers = new HashMap<>();
private final ApiHandler apiHandler;
private @NonNullByDefault({}) UriBuilder uriBuilder;
private @Nullable Session session;
private String appToken = "";
public static enum BoxModel {
FBXGW_R1_FULL, // Freebox Server (v6) revision 1
FBXGW_R2_FULL, // Freebox Server (v6) revision 2
FBXGW_R1_MINI, // Freebox Mini revision 1
FBXGW_R2_MINI, // Freebox Mini revision 2
FBXGW_R1_ONE, // Freebox One revision 1
FBXGW_R2_ONE, // Freebox One revision 2
FBXGW7_R1_FULL, // Freebox v7 revision 1
UNKNOWN;
}
public static record ApiVersion(String apiBaseUrl, @Nullable String apiDomain, String apiVersion, BoxModel boxModel,
@Nullable String boxModelName, String deviceName, String deviceType, boolean httpsAvailable, int httpsPort,
String uid) {
/**
* @return a string like eg: '/api/v8'
*/
private String baseUrl() {
return "%sv%s".formatted(apiBaseUrl, apiVersion.split("\\.")[0]);
}
}
public FreeboxOsSession(ApiHandler apiHandler) {
this.apiHandler = apiHandler;
}
public void initialize(FreeboxOsConfiguration config) throws FreeboxException, InterruptedException {
ApiVersion version = apiHandler.executeUri(config.getUriBuilder(API_VERSION_PATH).build(), HttpMethod.GET,
ApiVersion.class, null, null);
this.uriBuilder = config.getUriBuilder(version.baseUrl());
getManager(LoginManager.class);
getManager(NetShareManager.class);
getManager(LanManager.class);
getManager(WifiManager.class);
getManager(FreeplugManager.class);
getManager(AirMediaManager.class);
}
public void openSession(String appToken) throws FreeboxException {
Session newSession = getManager(LoginManager.class).openSession(appToken);
getManager(WebSocketManager.class).openSession(newSession.sessionToken());
session = newSession;
this.appToken = appToken;
}
public String grant() throws FreeboxException {
return getManager(LoginManager.class).checkGrantStatus();
}
public void closeSession() {
Session currentSession = session;
if (currentSession != null) {
try {
getManager(WebSocketManager.class).closeSession();
getManager(LoginManager.class).closeSession();
session = null;
} catch (FreeboxException e) {
logger.warn("Error closing session: {}", e.getMessage());
}
}
appToken = "";
restManagers.clear();
}
private synchronized <F, T extends Response<F>> List<F> execute(URI uri, HttpMethod method, Class<T> clazz,
boolean retryAuth, int retryCount, @Nullable Object aPayload) throws FreeboxException {
try {
T response = apiHandler.executeUri(uri, method, clazz, getSessionToken(), aPayload);
if (response.getErrorCode() == ErrorCode.INTERNAL_ERROR && retryCount > 0) {
return execute(uri, method, clazz, false, retryCount - 1, aPayload);
} else if (retryAuth && response.getErrorCode() == ErrorCode.AUTH_REQUIRED) {
openSession(appToken);
return execute(uri, method, clazz, false, retryCount, aPayload);
}
if (!response.isSuccess()) {
throw new FreeboxException("Api request failed: %s", response.getMsg());
}
return response.getResult();
} catch (FreeboxException e) {
if (ErrorCode.AUTH_REQUIRED.equals(e.getErrorCode())) {
openSession(appToken);
return execute(uri, method, clazz, false, retryCount, aPayload);
}
throw e;
} catch (InterruptedException ignored) {
return List.of();
}
}
public <F, T extends Response<F>> List<F> execute(URI uri, HttpMethod method, Class<T> clazz,
@Nullable Object aPayload) throws FreeboxException {
return execute(uri, method, clazz, getSessionToken() != null, 3, aPayload);
}
@SuppressWarnings("unchecked")
public synchronized <T extends RestManager> T getManager(Class<T> clazz) throws FreeboxException {
RestManager manager = restManagers.get(clazz);
if (manager == null) {
try {
Constructor<T> managerConstructor = clazz.getConstructor(FreeboxOsSession.class);
manager = addManager(clazz, managerConstructor.newInstance(this));
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof PermissionException) {
throw (PermissionException) cause;
}
throw new FreeboxException(e, "Unable to call RestManager constructor for %s", clazz.getName());
} catch (ReflectiveOperationException e) {
throw new FreeboxException(e, "Unable to call RestManager constructor for %s", clazz.getName());
}
}
return (T) manager;
}
public <T extends RestManager> T addManager(Class<T> clazz, T manager) {
restManagers.put(clazz, manager);
return manager;
}
boolean hasPermission(LoginManager.Permission required) {
Session currentSession = session;
return currentSession != null ? currentSession.hasPermission(required) : false;
}
private @Nullable String getSessionToken() {
Session currentSession = session;
return currentSession != null ? currentSession.sessionToken() : null;
}
public UriBuilder getUriBuilder() {
return uriBuilder.clone();
}
public ApiHandler getApiHandler() {
return apiHandler;
}
}

View File

@ -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<Network> {
}
public static enum NetRole {
STA, // Freeplug station
PCO, // Freeplug proxy coordinator
CCO, // Central Coordinator
UNKNOWN;
}
private enum Status {
UP,
DOWN,
UNKNOWN
}
public static record Freeplug(MACAddress id, String netId, // Id of the network holding the plug
boolean local, // if true the Freeplug is connected directly to the Freebox
NetRole netRole, // Freeplug network role
String model, Status ethPortStatus, //
boolean ethFullDuplex, // ethernet link is full duplex
boolean hasNetwork, // is connected to the network
int ethSpeed, // ethernet port speed
int inactive, // seconds since last activity
int rxRate, // rx rate (from the freeplugs to the cco freeplug) (in Mb/s) -1 if not available
int txRate) { // tx rate (from the cco freeplug to the freeplugs) (in Mb/s) -1 if not available
}
private static record Network(MACAddress id, List<Freeplug> members) {
}
public FreeplugManager(FreeboxOsSession session) throws FreeboxException {
super(session, LoginManager.Permission.NONE, session.getUriBuilder().path(THING_FREEPLUG));
}
// Most of the users will host only one CPL network on their server, so we hide the network level in the manager
public List<Freeplug> getPlugs() throws FreeboxException {
return get(Networks.class).stream().map(Network::members).flatMap(List::stream).toList();
}
public Optional<Freeplug> getPlug(MACAddress mac) throws FreeboxException {
return getPlugs().stream().filter(plug -> plug.id.equals(mac)).findFirst();
}
public void reboot(MACAddress mac) throws FreeboxException {
post(mac.toColonDelimitedString(), RESET_ACTION);
}
}

View File

@ -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<FtpManager.Config, FtpManager.ConfigResponse> {
private static final String PATH = "ftp";
protected static class ConfigResponse extends Response<Config> {
}
protected static record Config(boolean enabled, boolean allowAnonymous, boolean allowAnonymousWrite,
boolean allowRemoteAccess, boolean weakPassword, int portCtrl, int portData, String remoteDomain) {
}
public FtpManager(FreeboxOsSession session) throws FreeboxException {
super(session, LoginManager.Permission.NONE, ConfigResponse.class, session.getUriBuilder().path(PATH),
CONFIG_PATH);
}
public boolean getStatus() throws FreeboxException {
return getConfig().enabled();
}
public boolean setStatus(boolean enabled) throws FreeboxException {
Config oldConfig = getConfig();
Config newConfig = new Config(enabled, oldConfig.allowAnonymous, oldConfig.allowAnonymousWrite,
oldConfig.allowRemoteAccess, oldConfig.weakPassword, oldConfig.portCtrl, oldConfig.portData,
oldConfig.remoteDomain);
return setConfig(newConfig).enabled();
}
}

View File

@ -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<EndpointState> {
}
private static class HomeNodesResponse extends Response<HomeNode> {
}
private static enum AccessType {
R,
W,
RW,
UNKNOWN;
}
private static enum DisplayType {
TEXT,
ICON,
BUTTON,
SLIDER,
TOGGLE,
COLOR,
WARNING,
UNKNOWN;
}
private static record EndpointValue<T>(T value) {
}
private static record EndpointUi(AccessType access, DisplayType display, String iconUrl, @Nullable String unit) {
}
private static enum ValueType {
BOOL,
INT,
FLOAT,
VOID,
STRING,
UNKNOWN;
}
public static record EndpointState(@Nullable String value, ValueType valueType, long refresh) {
public boolean asBoolean() {
String local = value;
return local != null ? Boolean.valueOf(local) : false;
}
public int asInt() {
String local = value;
return local != null ? Integer.valueOf(local) : Integer.MIN_VALUE;
}
public @Nullable String value() {
return value;
}
}
public static enum EpType {
SIGNAL,
SLOT,
UNKNOWN;
public String asConfId() {
return name().toLowerCase();
}
}
private static record LogEntry(long timestamp, int value) {
}
public static record Endpoint(int id, String name, String label, EpType epType, Visibility visibility, int refresh,
ValueType valueType, EndpointUi ui, @Nullable String category, Object value, List<LogEntry> history) {
private static enum Visibility {
INTERNAL,
NORMAL,
DASHBOARD,
UNKNOWN;
}
}
private static enum Status {
UNREACHABLE,
DISABLED,
ACTIVE,
UNPAIRED,
UNKNOWN;
}
public static enum Category {
BASIC_SHUTTER,
SHUTTER,
ALARM,
KFB,
CAMERA,
UNKNOWN;
private final ThingTypeUID thingTypeUID;
Category() {
thingTypeUID = new ThingTypeUID(BINDING_ID, name().toLowerCase());
}
public ThingTypeUID getThingTypeUID() {
return thingTypeUID;
}
}
public static record NodeType(@SerializedName("abstract") boolean _abstract, List<Endpoint> endpoints,
boolean generic, String icon, String inherit, String label, String name, boolean physical) {
}
public static record HomeNode(int id, @Nullable String name, @Nullable String label, Category category,
Status status, List<Endpoint> showEndpoints, Map<String, String> props, NodeType type) {
}
public HomeManager(FreeboxOsSession session) throws FreeboxException {
super(session, LoginManager.Permission.HOME, session.getUriBuilder().path(PATH));
}
public List<HomeNode> getHomeNodes() throws FreeboxException {
return get(HomeNodesResponse.class, NODES_PATH);
}
public HomeNode getHomeNode(int nodeId) throws FreeboxException {
return getSingle(HomeNodesResponse.class, NODES_PATH, Integer.toString(nodeId));
}
public <T> @Nullable EndpointState getEndpointsState(int nodeId, int stateSignalId) throws FreeboxException {
return getSingle(EndpointStateResponse.class, ENDPOINTS_PATH, String.valueOf(nodeId),
String.valueOf(stateSignalId));
}
public <T> boolean putCommand(int nodeId, int stateSignalId, T value) throws FreeboxException {
put(new EndpointValue<T>(value), ENDPOINTS_PATH, String.valueOf(nodeId), String.valueOf(stateSignalId));
return true;
}
}

View File

@ -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<LanBrowserManager.Interface, InterfacesResponse> {
private static final IPAddress NULL_IP = new IPAddressString("0.0.0.0").getAddress();
private static final String PATH = "browser";
private static final String INTERFACES = "interfaces";
private static final String WOL_ACTION = "wol";
protected static class HostsResponse extends Response<LanHost> {
}
protected static class InterfacesResponse extends Response<Interface> {
}
public static enum Source {
DHCP,
NETBIOS,
MDNS,
MDNS_SRV,
UPNP,
WSD,
UNKNOWN;
}
public record HostName(@Nullable String name, Source source) {
}
protected static record Interface(String name, int hostCount) {
}
private static record WakeOnLineData(String mac, String password) {
}
private static enum Type {
MAC_ADDRESS,
UNKNOWN;
}
private static record L2Ident(MACAddress id, Type type) {
}
private static record L3Connectivity(String addr, Af af, boolean active, boolean reachable,
ZonedDateTime lastActivity, ZonedDateTime lastTimeReachable, String model) {
private static enum Af {
IPV4,
IPV6,
UNKNOWN;
}
public IPAddress getIPAddress() {
if (af != Af.UNKNOWN) {
return new IPAddressString(addr).getAddress();
}
return NULL_IP;
}
}
public static record HostIntf(LanHost host, Interface intf) {
}
private static enum HostType {
WORKSTATION,
LAPTOP,
SMARTPHONE,
TABLET,
PRINTER,
VG_CONSOLE,
TELEVISION,
NAS,
IP_CAMERA,
IP_PHONE,
FREEBOX_PLAYER,
FREEBOX_HD,
FREEBOX_CRYSTAL,
FREEBOX_MINI,
FREEBOX_DELTA,
FREEBOX_ONE,
FREEBOX_WIFI,
FREEBOX_POP,
NETWORKING_DEVICE,
MULTIMEDIA_DEVICE,
CAR,
OTHER,
UNKNOWN;
}
public static record LanHost(String id, @Nullable String primaryName, HostType hostType, boolean primaryNameManual,
L2Ident l2ident, @Nullable String vendorName, boolean persistent, boolean reachable,
@Nullable ZonedDateTime lastTimeReachable, boolean active, @Nullable ZonedDateTime lastActivity,
@Nullable ZonedDateTime firstActivity, List<HostName> names, List<L3Connectivity> l3connectivities,
@Nullable LanAccessPoint accessPoint) {
public @Nullable LanAccessPoint accessPoint() {
return accessPoint;
}
public String vendorName() {
String localVendor = vendorName;
return localVendor != null ? localVendor : "Unknown";
}
public Optional<String> getPrimaryName() {
return Optional.ofNullable(primaryName);
}
public Optional<String> getUPnPName() {
return names.stream().filter(name -> name.source == Source.UPNP).findFirst().map(name -> name.name);
}
public MACAddress getMac() {
if (Type.MAC_ADDRESS.equals(l2ident.type)) {
return l2ident.id;
}
throw new IllegalArgumentException("This host does not seem to have a Mac Address. Weird.");
}
public @Nullable IPAddress getIpv4() {
return l3connectivities.stream().filter(L3Connectivity::reachable).map(L3Connectivity::getIPAddress)
.filter(ip -> !ip.equals(NULL_IP) && ip.isIPv4()).findFirst().orElse(null);
}
public @Nullable ZonedDateTime getLastSeen() {
ZonedDateTime localLastActivity = lastActivity;
if (lastTimeReachable == null && localLastActivity == null) {
return null;
}
if (lastTimeReachable == null) {
return lastActivity;
}
if (localLastActivity == null) {
return lastTimeReachable;
} else {
return localLastActivity.isAfter(lastTimeReachable) ? lastActivity : lastTimeReachable;
}
}
}
private final List<Interface> interfaces = new ArrayList<>();
public LanBrowserManager(FreeboxOsSession session, UriBuilder uriBuilder) throws FreeboxException {
super(session, LoginManager.Permission.NONE, InterfacesResponse.class, uriBuilder.path(PATH));
listSubPath = INTERFACES;
}
private List<LanHost> getInterfaceHosts(String lanInterface) throws FreeboxException {
return get(HostsResponse.class, lanInterface);
}
private @Nullable LanHost getHost(String lanInterface, String hostId) throws FreeboxException {
return getSingle(HostsResponse.class, lanInterface, hostId);
}
// As the list of interfaces on the box may not change, we cache the result
private List<Interface> getInterfaces() throws FreeboxException {
if (interfaces.isEmpty()) {
interfaces.addAll(getDevices());
}
return interfaces;
}
public synchronized List<LanHost> getHosts() throws FreeboxException {
List<LanHost> hosts = new ArrayList<>();
for (Interface intf : getInterfaces()) {
hosts.addAll(getInterfaceHosts(intf.name()));
}
return hosts;
}
public Optional<HostIntf> getHost(MACAddress searched) throws FreeboxException {
for (Interface intf : getInterfaces()) {
LanHost host = getHost(intf.name(), "ether-" + searched.toColonDelimitedString());
if (host != null) {
return Optional.of(new HostIntf(host, intf));
}
}
return Optional.empty();
}
public boolean wakeOnLan(MACAddress mac, String password) throws FreeboxException {
Optional<HostIntf> target = getHost(mac);
if (target.isPresent()) {
post(new WakeOnLineData(mac.toColonDelimitedString(), password), GenericResponse.class, WOL_ACTION,
target.get().intf.name);
return true;
}
return false;
}
}

View File

@ -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<LanManager.LanConfig, LanManager.Config> {
private static final String PATH = "lan";
protected static class Config extends Response<LanConfig> {
}
private static enum Mode {
ROUTER,
BRIDGE,
UNKNOWN;
}
public static record LanConfig(IPAddress ip, String name, String nameDns, String nameMdns, String nameNetbios,
Mode mode) {
}
public LanManager(FreeboxOsSession session) throws FreeboxException {
super(session, LoginManager.Permission.NONE, Config.class, session.getUriBuilder().path(PATH), CONFIG_PATH);
session.addManager(LanBrowserManager.class, new LanBrowserManager(session, getUriBuilder()));
}
}

View File

@ -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<LcdManager.Config, LcdManager.ConfigResponse> {
private static final String PATH = "lcd";
protected static class ConfigResponse extends Response<Config> {
}
public static record Config(int brightness, int orientation, boolean orientationForced) {
}
public LcdManager(FreeboxOsSession session) throws FreeboxException {
super(session, LoginManager.Permission.NONE, ConfigResponse.class, session.getUriBuilder().path(PATH),
CONFIG_PATH);
}
private void setBrightness(int brightness) throws FreeboxException {
Config oldConfig = getConfig();
setConfig(new Config(brightness, oldConfig.orientation, oldConfig.orientationForced));
}
public void setOrientation(int orientation) throws FreeboxException {
Config oldConfig = getConfig();
setConfig(new Config(oldConfig.brightness, orientation, oldConfig.orientationForced));
}
public void setOrientationForced(boolean forced) throws FreeboxException {
Config oldConfig = getConfig();
setConfig(new Config(oldConfig.brightness, oldConfig.orientation, forced));
}
public void setBrightness(Callable<Integer> function) throws FreeboxException {
try {
setBrightness(function.call());
} catch (Exception e) {
throw new FreeboxException(e, "Error setting brightness");
}
}
}

View File

@ -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<T, Z extends Response<T>> extends RestManager {
private final Class<Z> deviceResponseClass;
protected @Nullable String listSubPath = null;
public ListableRest(FreeboxOsSession session, LoginManager.Permission required, Class<Z> respClass, UriBuilder uri)
throws FreeboxException {
super(session, required, uri);
this.deviceResponseClass = respClass;
}
public List<T> getDevices() throws FreeboxException {
return listSubPath == null ? get(deviceResponseClass) : get(deviceResponseClass, listSubPath);
}
public T getDevice(int deviceId) throws FreeboxException {
return getSingle(deviceResponseClass, Integer.toString(deviceId));
}
}

View File

@ -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<AuthorizationStatus> {
}
private static record Authorization(String appToken, String trackId) {
}
private static class AuthResponse extends Response<Authorization> {
}
public static enum Permission {
PARENTAL,
CONTACTS,
EXPLORER,
TV,
WDO,
DOWNLOADER,
PROFILE,
CAMERA,
SETTINGS,
CALLS,
HOME,
PVR,
VM,
PLAYER,
NONE,
UNKNOWN;
}
public static record Session(Map<LoginManager.Permission, @Nullable Boolean> permissions,
@Nullable String sessionToken) {
protected boolean hasPermission(LoginManager.Permission checked) {
return Boolean.TRUE.equals(permissions.get(checked));
}
}
private static class SessionResponse extends Response<Session> {
}
private static record AuthorizeData(String appId, String appName, String appVersion, String deviceName) {
AuthorizeData(String appId, Bundle bundle) {
this(appId, bundle.getHeaders().get("Bundle-Name"), bundle.getVersion().toString(),
bundle.getHeaders().get("Bundle-Vendor"));
}
}
private static record OpenSessionData(String appId, String password) {
}
private final Mac mac;
private Optional<Authorization> authorize = Optional.empty();
public LoginManager(FreeboxOsSession session) throws FreeboxException {
super(session, LoginManager.Permission.NONE, session.getUriBuilder().path(PATH));
try {
this.mac = Mac.getInstance(ALGORITHM);
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException(e);
}
}
public Session openSession(String appToken) throws FreeboxException {
AuthorizationStatus authorization = getSingle(AuthStatus.class);
try {
// Initialize mac with the signing key
mac.init(new SecretKeySpec(appToken.getBytes(), mac.getAlgorithm()));
// Compute the hmac on input data bytes
byte[] rawHmac = mac.doFinal(authorization.challenge().getBytes());
// Convert raw bytes to Hex
String password = printHexBinary(rawHmac).toLowerCase();
return post(new OpenSessionData(APP_ID, password), SessionResponse.class, SESSION);
} catch (InvalidKeyException e) {
throw new IllegalArgumentException(e);
}
}
public void closeSession() throws FreeboxException {
post(LOGOUT);
}
public String checkGrantStatus() throws FreeboxException {
if (authorize.isEmpty()) {
authorize = Optional.of(post(new AuthorizeData(APP_ID, BUNDLE), AuthResponse.class, AUTHORIZE_ACTION));
}
return switch (getSingle(AuthStatus.class, AUTHORIZE_ACTION, authorize.get().trackId).status()) {
case PENDING -> "";
case GRANTED -> {
String appToken = authorize.get().appToken;
authorize = Optional.empty();
yield appToken;
}
case TIMEOUT -> throw new FreeboxException("Unable to grant session, delay expired");
case DENIED -> throw new FreeboxException("Unable to grant session, access was denied");
case UNKNOWN -> throw new FreeboxException("Unable to grant session");
};
}
}

View File

@ -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<Receiver, MediaReceiverManager.ReceiverResponse> {
private static final String SUB_PATH = "receivers";
public static record Receiver(boolean passwordProtected, //
Map<MediaType, Boolean> capabilities, //
String name // This name is the UPnP name of the host
) {
}
protected static class ReceiverResponse extends Response<Receiver> {
}
public static enum Action {
START,
STOP,
UNKNOWN;
}
public static enum MediaType {
VIDEO,
PHOTO,
AUDIO,
SCREEN,
UNKNOWN;
}
private static record Request(String password, Action action, MediaType mediaType, @Nullable String media,
int position) {
}
public MediaReceiverManager(FreeboxOsSession session, UriBuilder uriBuilder) throws FreeboxException {
super(session, LoginManager.Permission.NONE, ReceiverResponse.class, uriBuilder.path(SUB_PATH));
}
public @Nullable Receiver getReceiver(String receiverName) throws FreeboxException {
return getDevices().stream().filter(rcv -> receiverName.equals(rcv.name())).findFirst().orElse(null);
}
public void sendToReceiver(String receiver, String password, Action action, MediaType type)
throws FreeboxException {
sendToReceiver(receiver, new Request(password, action, type, null, 0));
}
public void sendToReceiver(String receiver, String password, Action action, MediaType type, String url)
throws FreeboxException {
sendToReceiver(receiver, new Request(password, action, type, url, 0));
}
private void sendToReceiver(String receiver, Request payload) throws FreeboxException {
post(payload, GenericResponse.class, receiver);
}
}

View File

@ -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()));
}
}

View File

@ -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<PhoneManager.Config, PhoneManager.ConfigResponse> {
private static final String DECT_PAGE_ACTION = "dect_page_%s";
private static final String FXS_RING_ACTION = "fxs_ring_%s";
private static final String PATH = "phone";
protected class ConfigResponse extends Response<Config> {
}
protected class StatusResponse extends Response<Status> {
}
private static enum NetworkStatus {
WORKING,
UNKNOWN;
}
public static record Config(NetworkStatus network, boolean dectEcoMode, String dectPin, int dectRingPattern,
boolean dectRegistration, boolean dectNemoMode, boolean dectEnabled, boolean dectRingOnOff) {
}
public enum Type {
FXS,
DECT,
UNKNOWN;
}
public static record Status(int id, boolean isRinging, boolean onHook, boolean hardwareDefect, Type type,
@Nullable String vendor, int gainRx, int gainTx) {
public String vendor() {
String localVendor = vendor;
return localVendor != null ? localVendor : "Unknown";
}
}
public PhoneManager(FreeboxOsSession session) throws FreeboxException {
super(session, LoginManager.Permission.CALLS, ConfigResponse.class, session.getUriBuilder().path(PATH),
CONFIG_PATH);
}
public List<Status> getPhoneStatuses() throws FreeboxException {
return get(StatusResponse.class, "");
}
public Optional<Status> getStatus(int id) throws FreeboxException {
return Optional.ofNullable(getSingle(StatusResponse.class, Integer.toString(id)));
}
public void ringFxs(boolean startIt) throws FreeboxException {
post(FXS_RING_ACTION.formatted(startIt ? "start" : "stop"));
}
public void ringDect(boolean startIt) throws FreeboxException {
post(DECT_PAGE_ACTION.formatted(startIt ? "start" : "stop"));
}
public void setGainRx(int clientId, int gain) throws FreeboxException {
Optional<Status> result = getStatus(clientId);
if (result.isPresent()) {
Status status = result.get();
Status newStatus = new Status(status.id, status.isRinging, status.onHook, status.hardwareDefect,
status.type, status.vendor, gain, status.gainTx);
put(StatusResponse.class, newStatus, Integer.toString(clientId));
}
}
public void setGainTx(int clientId, int gain) throws FreeboxException {
Optional<Status> result = getStatus(clientId);
if (result.isPresent()) {
Status status = result.get();
Status newStatus = new Status(status.id, status.isRinging, status.onHook, status.hardwareDefect,
status.type, status.vendor, status.gainRx, gain);
put(StatusResponse.class, newStatus, Integer.toString(clientId));
}
}
public void alternateRing(boolean status) throws FreeboxException {
Config config = getConfig();
Config newConfig = new Config(config.network, config.dectEcoMode, config.dectPin, config.dectRingPattern,
config.dectRegistration, config.dectNemoMode, config.dectEnabled, status);
put(ConfigResponse.class, newConfig, CONFIG_PATH);
}
public boolean setStatus(boolean enabled) throws FreeboxException {
Config config = getConfig();
Config newConfig = new Config(config.network, config.dectEcoMode, config.dectPin, config.dectRingPattern,
config.dectRegistration, config.dectNemoMode, enabled, config.dectRingOnOff);
return setConfig(newConfig).dectEnabled;
}
}

View File

@ -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<PlayerManager.Player, PlayerManager.PlayerResponse> {
private static final String STATUS_PATH = "status";
protected static class PlayerResponse extends Response<Player> {
}
public static enum DeviceModel {
FBX7HD_DELTA, // Freebox Player Devialet
TBX8AM, // Player Pop
FBX6HD,
FBX6LC,
FBX6LCV2,
FBX7HD,
FBX7HD_ONE,
FBX8AM,
UNKNOWN;
}
public static record Player(MACAddress mac, StbType stbType, int id, ZonedDateTime lastTimeReachable,
boolean apiAvailable, String deviceName, DeviceModel deviceModel, boolean reachable, String uid,
@Nullable String apiVersion, List<String> lanGids) {
private static enum StbType {
STB_ANDROID,
STB_V6,
STB_V7,
STB_V8,
UNKNOWN;
}
/**
* @return a string like eg: '17/api/v8'
*/
private @Nullable String baseUrl() {
String api = apiVersion;
return api != null ? "%d/api/v%s/".formatted(id, api.split("\\.")[0]) : null;
}
}
private static class StatusResponse extends Response<Status> {
}
public static enum PowerState {
STANDBY,
RUNNING,
UNKNOWN;
}
public static record Status(PowerState powerState, StatusInformation player,
@Nullable ForegroundApp foregroundApp) {
public @Nullable ForegroundApp foregroundApp() {
return foregroundApp;
}
}
public static record ForegroundApp(int packageId, @Nullable String curlUrl, @Nullable Object context,
@SerializedName(value = "package") String _package) {
}
private static record StatusInformation(String name, ZonedDateTime lastActivity) {
}
private static class ConfigurationResponse extends Response<Configuration> {
}
public static record Configuration(String boardName, boolean configured, String firmwareVersion,
@Nullable ModelInfo modelInfo, String serial, String uptime, long uptimeVal) {
}
private enum MediaState {
READY,
UNKNOWN;
}
private static record AudioTrack(int bitrate, @SerializedName("channelCount") int channelCount,
@Nullable String codec, @SerializedName("codecId") @Nullable String codecId, @Nullable String language,
@SerializedName("metadataId") @Nullable String metadataId, int pid, int samplerate, long uid) {
}
private static enum Type {
NORMAL,
HEARINGIMPAIRED,
UNKNOWN;
}
protected static record Metadata(@Nullable String album,
@SerializedName("albumArtist") @Nullable String albumArtist, @Nullable String artist,
@Nullable String author, int bpm, @Nullable String comment, boolean compilation, @Nullable String composer,
@Nullable String container, @Nullable String copyright, long date,
@SerializedName("discId") @Nullable String discId, @SerializedName("discNumber") int discNumber,
@SerializedName("discTotal") int discTotal, @Nullable String genre,
@SerializedName("musicbrainzDiscId") @Nullable String musicbrainzDiscId, @Nullable String performer,
@Nullable String title, @SerializedName("trackNumber") int trackNumber,
@SerializedName("trackTotal") int trackTotal, @Nullable String url) {
protected static enum PlaybackState {
PLAY,
PAUSE,
UNKNOWN;
}
protected static record SubtitleTrack(@Nullable String codec, @Nullable String language, @Nullable String pid,
Type type, @Nullable String uid) {
}
protected static record VideoTrack(int bitrate, @Nullable String codec, int height, int pid, int uid,
int width) {
}
}
public static record PlayerContext(@Nullable PlayerDetails player) {
public static record PlayerDetails(@SerializedName("audioIndex") int audioIndex,
@SerializedName("audioList") List<AudioTrack> audioList, @SerializedName("curPos") long curPos,
int duration, @SerializedName("livePos") long livePos, @SerializedName("maxPos") long maxPos,
@SerializedName("mediaState") MediaState mediaState, @Nullable Metadata metadata,
@SerializedName("minPos") long minPos, @SerializedName("playbackState") PlaybackState playbackState,
long position, @Nullable String source, @SerializedName("subtitleIndex") int subtitleIndex,
@SerializedName("subtitleList") List<SubtitleTrack> subtitleList,
@SerializedName("videoIndex") int videoIndex, @SerializedName("videoList") List<VideoTrack> videoList) {
}
}
private static enum BouquetType {
ADSL,
UNKNOWN;
}
private static enum ChannelType {
REGULAR,
UNKNOWN;
}
private static record Service(long id, @Nullable String name,
@SerializedName("qualityLabel") @Nullable String qualityLabel,
@SerializedName("qualityName") @Nullable String qualityName, @SerializedName("sortInfo") int sortInfo,
@SerializedName("typeLabel") @Nullable String typeLabel,
@SerializedName("typeName") @Nullable String typeName, @Nullable String url) {
}
private static record Channel(@SerializedName("bouquetId") long bouquetId,
@SerializedName("bouquetName") @Nullable String bouquetName,
@SerializedName("bouquetType") BouquetType bouquetType,
@SerializedName("channelName") @Nullable String channelName,
@SerializedName("channelNumber") int channelNumber,
@SerializedName("channelSubNumber") int channelSubNumber,
@SerializedName("channelType") ChannelType channelType,
@SerializedName("channelUuid") @Nullable String channelUuid,
@SerializedName("currentServiceIndex") int currentServiceIndex,
@SerializedName("isTimeShifting") boolean isTimeShifting, List<Service> services,
@SerializedName("videoIsVisible") boolean videoIsVisible) {
}
public static record TvContext(@Nullable Channel channel, @Nullable PlayerDetails player) {
}
private final Map<Integer, String> subPaths = new HashMap<>();
public PlayerManager(FreeboxOsSession session) throws FreeboxException {
super(session, LoginManager.Permission.PLAYER, PlayerResponse.class,
session.getUriBuilder().path(THING_PLAYER));
getDevices().stream().filter(Player::apiAvailable).forEach(player -> {
String baseUrl = player.baseUrl();
if (baseUrl != null) {
subPaths.put(player.id, baseUrl);
}
});
}
public Status getPlayerStatus(int id) throws FreeboxException {
return getSingle(StatusResponse.class, subPaths.get(id), STATUS_PATH);
}
// The player API does not allow to directly request a given player like others api parts
@Override
public Player getDevice(int id) throws FreeboxException {
return getDevices().stream().filter(player -> player.id == id).findFirst().orElse(null);
}
public Configuration getConfig(int id) throws FreeboxException {
return getSingle(ConfigurationResponse.class, subPaths.get(id), SYSTEM_PATH);
}
public void sendKey(String ip, String code, String key, boolean longPress, int count) {
UriBuilder uriBuilder = UriBuilder.fromPath("pub").scheme("http").host(ip).path("remote_control");
uriBuilder.queryParam("code", code).queryParam("key", key);
if (longPress) {
uriBuilder.queryParam("long", true);
}
if (count > 1) {
uriBuilder.queryParam("repeat", count);
}
try {
session.execute(uriBuilder.build(), HttpMethod.GET, GenericResponse.class, null);
} catch (FreeboxException ignore) {
// This call does not return anything, we can safely ignore
}
}
public void reboot(int id) throws FreeboxException {
post(subPaths.get(id), SYSTEM_PATH, REBOOT_ACTION);
}
}

View File

@ -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<RepeaterManager.Repeater, RepeaterManager.RepeaterResponse> {
protected static class RepeaterResponse extends Response<Repeater> {
}
protected static class RepeaterLedResponse extends Response<RepeaterLed> {
}
public static record RepeaterLed(int id, boolean ledActivated) {
}
private static enum Connection {
CONNECTED,
DISCONNECTED,
UNKNOWN;
}
private static enum Status {
STARTING,
RUNNING,
REBOOTING,
UPDATING,
REBOOT_FAILURE,
UPDATE_FAILURE,
UNKNOWN;
}
public static enum Model {
FBXWMR, // Répéteur Wifi
UNKNOWN;
}
public static record Repeater(int id, boolean ledActivated, boolean enabled, MACAddress mainMac,
Connection connection, ZonedDateTime bootTime, Status status, String name, String sn, String apiVer,
ZonedDateTime lastSeen, Model model, String firmwareVersion) {
public long getUptimeVal() {
return Duration.between(bootTime, ZonedDateTime.now()).toSeconds();
}
}
public RepeaterManager(FreeboxOsSession session) throws FreeboxException {
super(session, LoginManager.Permission.NONE, RepeaterResponse.class,
session.getUriBuilder().path(THING_REPEATER));
}
public List<LanHost> getRepeaterHosts(int id) throws FreeboxException {
return get(HostsResponse.class, Integer.toString(id), THING_HOST);
}
public synchronized List<LanHost> getHosts() throws FreeboxException {
List<LanHost> hosts = new ArrayList<>();
for (Repeater rep : getDevices()) {
if (Connection.CONNECTED.equals(rep.connection)) {
hosts.addAll(getRepeaterHosts(rep.id));
}
}
return hosts;
}
public Optional<LanHost> getHost(MACAddress mac) throws FreeboxException {
return getHosts().stream().filter(host -> host.getMac().equals(mac)).findFirst();
}
public void reboot(int id) throws FreeboxException {
post(Integer.toString(id), REBOOT_ACTION);
}
public Optional<RepeaterLed> led(int id, boolean enable) throws FreeboxException {
RepeaterLed result = put(RepeaterLedResponse.class, new RepeaterLed(id, enable), Integer.toString(id));
return Optional.ofNullable(result);
}
}

View File

@ -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<Object> {
}
private final UriBuilder uriBuilder;
protected final FreeboxOsSession session;
public RestManager(FreeboxOsSession session, LoginManager.Permission required, UriBuilder uri)
throws FreeboxException {
this.uriBuilder = uri;
this.session = session;
if (required != LoginManager.Permission.NONE && !session.hasPermission(required)) {
throw new PermissionException(required, "Permission missing: %s", required.toString());
}
}
protected UriBuilder getUriBuilder() {
return uriBuilder.clone();
}
private URI buildUri(String... pathElements) {
UriBuilder localBuilder = getUriBuilder();
for (String path : pathElements) {
localBuilder.path(path);
}
return localBuilder.build();
}
// Returns the first and supposed only element from the list. Presence of this element is expected and mandatory
private <F> F controlSingleton(List<F> result) {
if (result.size() == 1) {
return result.get(0);
}
throw new IllegalArgumentException("Result is empty or not singleton");
}
protected <F, T extends Response<F>> List<F> get(Class<T> clazz, String... pathElements) throws FreeboxException {
return session.execute(buildUri(pathElements), GET, clazz, null);
}
protected <F, T extends Response<F>> F getSingle(Class<T> clazz, String... pathElements) throws FreeboxException {
return controlSingleton(get(clazz, pathElements));
}
protected <F, T extends Response<F>> F post(Object payload, Class<T> clazz, String... pathElements)
throws FreeboxException {
return controlSingleton(session.execute(buildUri(pathElements), POST, clazz, payload));
}
protected void post(String... pathElements) throws FreeboxException {
session.execute(buildUri(pathElements), POST, GenericResponse.class, null);
}
protected <F, T extends Response<F>> F put(Class<T> clazz, F payload, String... pathElements)
throws FreeboxException {
return controlSingleton(session.execute(buildUri(pathElements), PUT, clazz, payload));
}
protected <F, T extends Response<F>> void put(F payload, String... pathElements) throws FreeboxException {
session.execute(buildUri(pathElements), PUT, GenericResponse.class, payload);
}
}

View File

@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.api.rest;
import javax.ws.rs.core.UriBuilder;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.Response;
/**
* The {@link SambaManager} is the Java class used to handle api requests related to Samba shares
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class SambaManager extends ConfigurableRest<SambaManager.Samba, SambaManager.ConfigResponse> {
private static final String PATH = "samba";
protected static class ConfigResponse extends Response<Samba> {
}
public static record Samba(boolean fileShareEnabled, boolean printShareEnabled, boolean logonEnabled,
@Nullable String logonUser, @Nullable String logonPassword, @Nullable String workgroup,
boolean smbv2Enabled) {
}
public SambaManager(FreeboxOsSession session, UriBuilder uriBuilder) throws FreeboxException {
super(session, LoginManager.Permission.NONE, ConfigResponse.class, uriBuilder.path(PATH), null);
}
public boolean setFileShare(boolean enable) throws FreeboxException {
Samba config = getConfig();
Samba newConfig = new Samba(enable, config.printShareEnabled, config.logonEnabled, config.logonUser,
config.logonPassword, config.workgroup, config.smbv2Enabled);
return setConfig(newConfig).fileShareEnabled();
}
public boolean setPrintShare(boolean enable) throws FreeboxException {
Samba config = getConfig();
Samba newConfig = new Samba(config.fileShareEnabled, enable, config.logonEnabled, config.logonUser,
config.logonPassword, config.workgroup, config.smbv2Enabled);
return setConfig(newConfig).printShareEnabled();
}
}

View File

@ -0,0 +1,93 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.api.rest;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.Response;
import org.openhab.binding.freeboxos.internal.api.rest.FreeboxOsSession.BoxModel;
import inet.ipaddr.mac.MACAddress;
/**
* The {@link SystemManager} is the Java class used to handle api requests related to system
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class SystemManager extends ConfigurableRest<SystemManager.Config, SystemManager.ConfigurationResponse> {
protected static class ConfigurationResponse extends Response<Config> {
}
public static record Sensor(String id, String name, int value) {
public enum SensorKind {
FAN,
TEMP,
UNKNOWN;
}
public SensorKind getKind() {
String[] elements = id.split("_");
if (elements.length > 0) {
String kind = elements[0].replaceAll("\\d", "").toUpperCase();
try {
return SensorKind.valueOf(kind);
} catch (IllegalArgumentException ignore) { // returning UNKNOWN
}
}
return SensorKind.UNKNOWN;
}
}
private static record Expansion(int slot, boolean probeDone, boolean present, boolean supported, String bundle,
Type type) {
private static enum Type {
UNKNOWN, // unknown module
DSL_LTE, // xDSL + LTE
DSL_LTE_EXTERNAL_ANTENNAS, // xDSL + LTE with external antennas switch
FTTH_P2P, // FTTH P2P
FTTH_PON, // FTTH PON
SECURITY; // Security module
}
}
public static record ModelInfo(BoxModel name, String prettyName, boolean hasExpansions, boolean hasLanSfp,
boolean hasDect, boolean hasHomeAutomation, boolean hasFemtocellExp, boolean hasFixedFemtocell,
boolean hasVm) {
}
public static record Config(String firmwareVersion, MACAddress mac, String serial, String uptime, long uptimeVal,
String boardName, boolean boxAuthenticated, DiskStatus diskStatus, String userMainStorage,
List<Sensor> sensors, ModelInfo modelInfo, List<Sensor> fans, List<Expansion> expansions) {
private static enum DiskStatus {
NOT_DETECTED,
DISABLED,
INITIALIZING,
ERROR,
ACTIVE,
UNKNOWN;
}
}
public SystemManager(FreeboxOsSession session) throws FreeboxException {
super(session, LoginManager.Permission.NONE, ConfigurationResponse.class,
session.getUriBuilder().path(SYSTEM_PATH), null);
}
public void reboot() throws FreeboxException {
post(REBOOT_ACTION);
}
}

View File

@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.api.rest;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.Response;
/**
* The {@link UPnPAVManager} is the Java class used to handle api requests related to UPnP AV
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class UPnPAVManager extends ConfigurableRest<UPnPAVManager.Config, UPnPAVManager.ConfigResponse> {
private static final String PATH = "upnpav";
protected static class ConfigResponse extends Response<Config> {
}
protected static record Config(boolean enabled) {
}
public UPnPAVManager(FreeboxOsSession session) throws FreeboxException {
super(session, LoginManager.Permission.NONE, ConfigResponse.class, session.getUriBuilder().path(PATH),
CONFIG_PATH);
}
public boolean getStatus() throws FreeboxException {
return getConfig().enabled();
}
public boolean setStatus(boolean enabled) throws FreeboxException {
return setConfig(new Config(enabled)).enabled();
}
}

View File

@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.api.rest;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.THING_VM;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.Response;
import inet.ipaddr.mac.MACAddress;
/**
* The {@link VmManager} is the Java class used to handle api requests related to virtual machines
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class VmManager extends ListableRest<VmManager.VirtualMachine, VmManager.VirtualMachineResponse> {
protected class VirtualMachineResponse extends Response<VirtualMachine> {
}
public static enum Status {
STOPPED,
RUNNING,
UNKNOWN;
}
public static record VirtualMachine(int id, String name, MACAddress mac, Status status) {
}
public VmManager(FreeboxOsSession session) throws FreeboxException {
super(session, LoginManager.Permission.VM, VirtualMachineResponse.class,
session.getUriBuilder().path(THING_VM));
}
public void power(int vmId, boolean startIt) throws FreeboxException {
post(Integer.toString(vmId), startIt ? "start" : "powerbutton");
}
}

View File

@ -0,0 +1,201 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.api.rest;
import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketListener;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.openhab.binding.freeboxos.internal.api.ApiHandler;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.LanHost;
import org.openhab.binding.freeboxos.internal.api.rest.VmManager.VirtualMachine;
import org.openhab.binding.freeboxos.internal.handler.HostHandler;
import org.openhab.binding.freeboxos.internal.handler.VmHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement;
import inet.ipaddr.mac.MACAddress;
/**
* The {@link WebSocketManager} is the Java class register to the websocket server and handle notifications
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class WebSocketManager extends RestManager implements WebSocketListener {
private static final String HOST_UNREACHABLE = "lan_host_l3addr_unreachable";
private static final String HOST_REACHABLE = "lan_host_l3addr_reachable";
private static final String VM_CHANGED = "vm_state_changed";
private static final Register REGISTRATION = new Register("register",
List.of(VM_CHANGED, HOST_REACHABLE, HOST_UNREACHABLE));
private static final String WS_PATH = "ws/event";
private final Logger logger = LoggerFactory.getLogger(WebSocketManager.class);
private final Map<MACAddress, HostHandler> lanHosts = new HashMap<>();
private final Map<Integer, VmHandler> vms = new HashMap<>();
private final ApiHandler apiHandler;
private volatile @Nullable Session wsSession;
private record Register(String action, List<String> events) {
}
public WebSocketManager(FreeboxOsSession session) throws FreeboxException {
super(session, LoginManager.Permission.NONE, session.getUriBuilder().path(WS_PATH));
this.apiHandler = session.getApiHandler();
}
private static enum Action {
REGISTER,
NOTIFICATION,
UNKNOWN;
}
private static record WebSocketResponse(boolean success, Action action, String event, String source,
@Nullable JsonElement result) {
public String getEvent() {
return source + "_" + event;
}
}
public void openSession(@Nullable String sessionToken) throws FreeboxException {
WebSocketClient client = new WebSocketClient(apiHandler.getHttpClient());
URI uri = getUriBuilder().scheme(getUriBuilder().build().getScheme().contains("s") ? "wss" : "ws").build();
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setHeader(ApiHandler.AUTH_HEADER, sessionToken);
try {
client.start();
client.connect(this, uri, request);
} catch (Exception e) {
throw new FreeboxException(e, "Exception connecting websocket client");
}
}
public void closeSession() {
logger.debug("Awaiting closure from remote");
Session localSession = wsSession;
if (localSession != null) {
localSession.close();
}
}
@Override
public void onWebSocketConnect(@NonNullByDefault({}) Session wsSession) {
this.wsSession = wsSession;
logger.debug("Websocket connection establisehd");
try {
wsSession.getRemote().sendString(apiHandler.serialize(REGISTRATION));
} catch (IOException e) {
logger.warn("Error connecting to websocket: {}", e.getMessage());
}
}
@Override
public void onWebSocketText(@NonNullByDefault({}) String message) {
Session localSession = wsSession;
if (message.toLowerCase(Locale.US).contains("bye") && localSession != null) {
localSession.close(StatusCode.NORMAL, "Thanks");
return;
}
WebSocketResponse result = apiHandler.deserialize(WebSocketResponse.class, message);
if (result.success) {
switch (result.action) {
case REGISTER:
logger.debug("Event registration successfull");
break;
case NOTIFICATION:
handleNotification(result);
break;
default:
logger.warn("Unhandled notification received: {}", result.action);
}
}
}
private void handleNotification(WebSocketResponse result) {
JsonElement json = result.result;
if (json != null) {
switch (result.getEvent()) {
case VM_CHANGED:
VirtualMachine vm = apiHandler.deserialize(VirtualMachine.class, json.toString());
logger.debug("Received notification for VM {}", vm.id());
VmHandler vmHandler = vms.get(vm.id());
if (vmHandler != null) {
vmHandler.updateVmChannels(vm);
}
break;
case HOST_UNREACHABLE, HOST_REACHABLE:
LanHost host = apiHandler.deserialize(LanHost.class, json.toString());
MACAddress mac = host.getMac();
logger.debug("Received notification for LanHost {}", mac.toColonDelimitedString());
HostHandler hostHandler = lanHosts.get(mac);
if (hostHandler != null) {
hostHandler.updateConnectivityChannels(host);
}
break;
default:
logger.warn("Unhandled event received: {}", result.getEvent());
}
} else {
logger.warn("Empty json element in notification");
}
}
@Override
public void onWebSocketClose(int statusCode, @NonNullByDefault({}) String reason) {
logger.debug("Socket Closed: [{}] - reason {}", statusCode, reason);
this.wsSession = null;
}
@Override
public void onWebSocketError(@NonNullByDefault({}) Throwable cause) {
logger.warn("Error on websocket: {}", cause.getMessage());
}
@Override
public void onWebSocketBinary(byte @Nullable [] payload, int offset, int len) {
/* do nothing */
}
public void registerListener(MACAddress mac, HostHandler hostHandler) {
lanHosts.put(mac, hostHandler);
}
public void unregisterListener(MACAddress mac) {
lanHosts.remove(mac);
}
public void registerVm(int clientId, VmHandler vmHandler) {
vms.put(clientId, vmHandler);
}
public void unregisterVm(int clientId) {
vms.remove(clientId);
}
}

View File

@ -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 WifiManager} is the Java class used to handle api requests related to wifi
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class WifiManager extends ConfigurableRest<WifiManager.Config, WifiManager.ConfigResponse> {
private static final String PATH = "wifi";
protected static class ConfigResponse extends Response<Config> {
}
protected static record Config(boolean enabled) {
}
public WifiManager(FreeboxOsSession session) throws FreeboxException {
super(session, LoginManager.Permission.NONE, ConfigResponse.class, session.getUriBuilder().path(PATH),
CONFIG_PATH);
session.addManager(APManager.class, new APManager(session, getUriBuilder()));
}
public boolean getStatus() throws FreeboxException {
return getConfig().enabled();
}
public boolean setStatus(boolean enabled) throws FreeboxException {
return setConfig(new Config(enabled)).enabled();
}
}

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class ApiConsumerConfiguration {
public static final String REFRESH_INTERVAL = "refreshInterval";
public int refreshInterval = 30;
public String password = "";
public boolean acceptAllMp3 = true;
}

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link ClientConfiguration} is responsible for holding configuration informations for a controllable client of
* the API
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class ClientConfiguration extends HostConfiguration {
public static final String ID = "id";
public int id = 1;
}

View File

@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.config;
import javax.ws.rs.core.UriBuilder;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.FreeboxTlsCertificateProvider;
/**
* The {@link FreeboxOsConfiguration} is responsible for holding configuration informations needed to access the Freebox
* API
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class FreeboxOsConfiguration {
public static final String API_DOMAIN = "apiDomain";
public static final String APP_TOKEN = "appToken";
public static final String HTTPS_PORT = "httpsPort";
public static final String HTTPS_AVAILABLE = "httpsAvailable";
private String apiDomain = FreeboxTlsCertificateProvider.DEFAULT_NAME;
public String appToken = "";
public boolean discoverNetDevice;
private int httpsPort = 15682;
private boolean httpsAvailable;
private String getScheme() {
return httpsAvailable ? "https" : "http";
}
private int getPort() {
return httpsAvailable ? httpsPort : 80;
}
public UriBuilder getUriBuilder(String path) {
return UriBuilder.fromPath("/").scheme(getScheme()).port(getPort()).host(apiDomain).path(path).clone();
}
}

View File

@ -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.config;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.rest.FreeplugManager.Freeplug;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import inet.ipaddr.mac.MACAddress;
/**
* The {@link FreeplugConfigurationBuilder} is responsible for holding configuration informations associated to a
* Freeplug
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class FreeplugConfigurationBuilder {
private static final FreeplugConfigurationBuilder BUILDER_INSTANCE = new FreeplugConfigurationBuilder();
private final Logger logger = LoggerFactory.getLogger(FreeplugConfigurationBuilder.class);
private FreeplugConfigurationBuilder() {
}
public static FreeplugConfigurationBuilder getInstance() {
return BUILDER_INSTANCE;
}
public DiscoveryResultBuilder configure(ThingUID bridgeUID, Freeplug plug) {
MACAddress mac = plug.id();
String uid = mac.toHexString(false);
ThingUID thingUID = new ThingUID(THING_TYPE_FREEPLUG, bridgeUID, uid);
logger.debug("Adding new {} {} to inbox", THING_FREEPLUG, thingUID);
return DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
.withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS)
.withLabel("%s (%s) %s".formatted(THING_FREEPLUG, plug.netRole().name(), uid))
.withProperty(Thing.PROPERTY_MAC_ADDRESS, mac.toColonDelimitedString());
}
}

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
import inet.ipaddr.MACAddressString;
import inet.ipaddr.mac.MACAddress;
/**
* The {@link HostConfiguration} is responsible for holding
* configuration informations associated to a Freebox Network Device
* thing type
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class HostConfiguration extends ApiConsumerConfiguration {
private String macAddress = "";
public MACAddress getMac() {
return new MACAddressString(macAddress).getAddress();
}
}

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link LandlineConfiguration} is responsible for holding
* configuration informations associated to a Freebox Phone thing type
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class LandlineConfiguration extends ApiConsumerConfiguration {
public int id = 1;
LandlineConfiguration() {
refreshInterval = 2;
}
}

View File

@ -0,0 +1,50 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.config;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Category;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.HomeNode;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingUID;
/**
* The {@link NodeConfigurationBuilder} is responsible for holding configuration informations associated to a Freebox
* Home thing type
*
* @author ben12 - Initial contribution
*/
@NonNullByDefault
public class NodeConfigurationBuilder {
private static final NodeConfigurationBuilder BUILDER_INSTANCE = new NodeConfigurationBuilder();
private NodeConfigurationBuilder() {
}
public static NodeConfigurationBuilder getInstance() {
return BUILDER_INSTANCE;
}
public Optional<DiscoveryResultBuilder> configure(ThingUID bridgeUID, HomeNode node) {
if (node.category() == Category.UNKNOWN) {
return Optional.empty();
}
ThingUID thingUID = new ThingUID(node.category().getThingTypeUID(), bridgeUID, Integer.toString(node.id()));
DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(thingUID);
discoveryResultBuilder.withProperty(ClientConfiguration.ID, node.id()).withLabel(node.label())
.withRepresentationProperty(ClientConfiguration.ID).withBridge(bridgeUID);
return Optional.of(discoveryResultBuilder);
}
}

View File

@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.config;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager.Status;
import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager.Type;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PhoneConfigurationBuilder} is responsible for holding configuration informations associated the phone
* lines (DECT and FXS / landline)
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class PhoneConfigurationBuilder {
private static final PhoneConfigurationBuilder BUILDER_INSTANCE = new PhoneConfigurationBuilder();
private final Logger logger = LoggerFactory.getLogger(PhoneConfigurationBuilder.class);
private PhoneConfigurationBuilder() {
}
public static PhoneConfigurationBuilder getInstance() {
return BUILDER_INSTANCE;
}
public DiscoveryResultBuilder configure(ThingUID bridgeUID, Status config) {
ThingUID thingUID = new ThingUID(Type.DECT.equals(config.type()) ? THING_TYPE_DECT : THING_TYPE_FXS, bridgeUID,
Integer.toString(config.id()));
logger.debug("Adding new Freebox Phone {} to inbox", thingUID);
return DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
.withProperty(ClientConfiguration.ID, config.id()).withLabel(config.type().name())
.withRepresentationProperty(ClientConfiguration.ID);
}
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link PlayerConfiguration} is responsible for holding configuration informations needed to access/poll the
* freebox player
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class PlayerConfiguration extends ClientConfiguration {
public static final String REMOTE_CODE = "remoteCode";
public String remoteCode = "";
}

View File

@ -0,0 +1,99 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.console;
import static org.openhab.binding.freeboxos.internal.config.FreeboxOsConfiguration.APP_TOKEN;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants;
import org.openhab.binding.freeboxos.internal.handler.FreeboxOsHandler;
import org.openhab.core.io.console.Console;
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingRegistry;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link FreeboxOsCommandExtension} is responsible for handling console commands
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
@Component(service = ConsoleCommandExtension.class)
public class FreeboxOsCommandExtension extends AbstractConsoleCommandExtension {
private final ThingRegistry thingRegistry;
@Activate
public FreeboxOsCommandExtension(final @Reference ThingRegistry thingRegistry) {
super(FreeboxOsBindingConstants.BINDING_ID, "Interact with the Freebox OS binding.");
this.thingRegistry = thingRegistry;
}
@Override
public void execute(String[] args, Console console) {
if (args.length == 2) {
Thing thing = null;
try {
ThingUID thingUID = new ThingUID(args[0]);
thing = thingRegistry.get(thingUID);
} catch (IllegalArgumentException e) {
thing = null;
}
ThingHandler thingHandler = null;
FreeboxOsHandler handler = null;
if (thing != null) {
thingHandler = thing.getHandler();
if (thingHandler instanceof FreeboxOsHandler) {
handler = (FreeboxOsHandler) thingHandler;
}
}
if (thing == null) {
console.println("Bad thing id '" + args[0] + "'");
printUsage(console);
} else if (thingHandler == null) {
console.println("No handler initialized for the thing id '" + args[0] + "'");
printUsage(console);
} else if (handler == null) {
console.println("'" + args[0] + "' is not a freebox bridge id");
printUsage(console);
} else {
switch (args[1]) {
case APP_TOKEN:
String token = handler.getConfiguration().appToken;
console.println("Your application token is " + (token.isEmpty() ? "undefined" : token));
break;
default:
printUsage(console);
break;
}
}
} else {
printUsage(console);
}
}
@Override
public List<String> getUsages() {
return Arrays.asList(buildCommandUsage(String.format("<bridgeUID> %s show the application token", APP_TOKEN)));
}
}

View File

@ -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.discovery;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import static org.openhab.binding.freeboxos.internal.config.FreeboxOsConfiguration.*;
import java.util.Set;
import javax.jmdns.ServiceInfo;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ApiDiscoveryParticipant} is responsible for discovering the various servers flavors of bridges thing using
* mDNS discovery service
*
* @author Gaël L'hopital - Initial contribution
*/
@Component
@NonNullByDefault
public class ApiDiscoveryParticipant implements MDNSDiscoveryParticipant {
private static final String DOMAIN_PROPERTY = "api_domain";
private static final String PORT_PROPERTY = "https_port";
private static final String HTTPS_PROPERTY = "https_available";
private final Logger logger = LoggerFactory.getLogger(ApiDiscoveryParticipant.class);
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return BRIDGE_TYPE_UIDS;
}
@Override
public String getServiceType() {
return "_fbx-api._tcp.local.";
}
@Override
public @Nullable DiscoveryResult createResult(ServiceInfo service) {
logger.debug("createResult ServiceInfo: {}", service);
ThingUID thingUID = getThingUID(service);
return thingUID != null
? DiscoveryResultBuilder.create(thingUID).withLabel("Bridge Freebox OS")
.withRepresentationProperty(API_DOMAIN)
.withProperty(HTTPS_AVAILABLE, "1".equals(service.getPropertyString(HTTPS_PROPERTY)))
.withProperty(HTTPS_PORT, service.getPropertyString(PORT_PROPERTY))
.withProperty(API_DOMAIN, service.getPropertyString(DOMAIN_PROPERTY)).build()
: null;
}
@Override
public @Nullable ThingUID getThingUID(ServiceInfo service) {
String domain = service.getPropertyString(DOMAIN_PROPERTY);
if (domain != null) {
String[] elements = domain.split("\\.");
if (elements.length > 0) {
return new ThingUID(BRIDGE_TYPE_API, elements[0]);
}
}
return null;
}
}

View File

@ -0,0 +1,283 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.discovery;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.PermissionException;
import org.openhab.binding.freeboxos.internal.api.rest.APManager;
import org.openhab.binding.freeboxos.internal.api.rest.APManager.Station;
import org.openhab.binding.freeboxos.internal.api.rest.FreeplugManager;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager;
import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.LanHost;
import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager;
import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager.Status;
import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager;
import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Player;
import org.openhab.binding.freeboxos.internal.api.rest.RepeaterManager;
import org.openhab.binding.freeboxos.internal.api.rest.RepeaterManager.Repeater;
import org.openhab.binding.freeboxos.internal.api.rest.SystemManager;
import org.openhab.binding.freeboxos.internal.api.rest.SystemManager.Config;
import org.openhab.binding.freeboxos.internal.api.rest.VmManager;
import org.openhab.binding.freeboxos.internal.config.ClientConfiguration;
import org.openhab.binding.freeboxos.internal.config.FreeplugConfigurationBuilder;
import org.openhab.binding.freeboxos.internal.config.NodeConfigurationBuilder;
import org.openhab.binding.freeboxos.internal.config.PhoneConfigurationBuilder;
import org.openhab.binding.freeboxos.internal.handler.FreeboxOsHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import inet.ipaddr.mac.MACAddress;
/**
* The {@link FreeboxOsDiscoveryService} is responsible for discovering all things
* except the Freebox API thing itself
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class FreeboxOsDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
private static final int DISCOVERY_TIME_SECONDS = 10;
private static final int BACKGROUND_SCAN_REFRESH_MINUTES = 1;
private final Logger logger = LoggerFactory.getLogger(FreeboxOsDiscoveryService.class);
private Optional<ScheduledFuture<?>> backgroundFuture = Optional.empty();
private @Nullable FreeboxOsHandler bridgeHandler;
public FreeboxOsDiscoveryService() {
super(Stream.of(THINGS_TYPES_UIDS, HOME_TYPES_UIDS).flatMap(Set::stream).collect(Collectors.toSet()),
DISCOVERY_TIME_SECONDS);
}
@Override
public void deactivate() {
super.deactivate();
}
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof FreeboxOsHandler) {
bridgeHandler = (FreeboxOsHandler) handler;
activate(null);
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler;
}
@Override
protected void startBackgroundDiscovery() {
stopBackgroundDiscovery();
backgroundFuture = Optional.of(scheduler.scheduleWithFixedDelay(this::startScan,
BACKGROUND_SCAN_REFRESH_MINUTES, BACKGROUND_SCAN_REFRESH_MINUTES, TimeUnit.MINUTES));
}
@Override
protected void stopBackgroundDiscovery() {
backgroundFuture.ifPresent(future -> future.cancel(true));
backgroundFuture = Optional.empty();
}
@Override
protected void startScan() {
logger.debug("Starting Freebox discovery scan");
FreeboxOsHandler localHandler = bridgeHandler;
if (localHandler != null && localHandler.getThing().getStatus() == ThingStatus.ONLINE) {
try {
ThingUID bridgeUID = localHandler.getThing().getUID();
List<LanHost> lanHosts = localHandler.getManager(LanBrowserManager.class).getHosts().stream()
.filter(LanHost::reachable).collect(Collectors.toList());
discoverServer(localHandler.getManager(SystemManager.class), bridgeUID);
discoverPhone(localHandler.getManager(PhoneManager.class), bridgeUID);
discoverPlugs(localHandler.getManager(FreeplugManager.class), bridgeUID);
discoverRepeater(localHandler.getManager(RepeaterManager.class), bridgeUID, lanHosts);
discoverPlayer(localHandler.getManager(PlayerManager.class), bridgeUID, lanHosts);
discoverVM(localHandler.getManager(VmManager.class), bridgeUID, lanHosts);
discoverHome(localHandler.getManager(HomeManager.class), bridgeUID);
if (localHandler.getConfiguration().discoverNetDevice) {
discoverHosts(localHandler, bridgeUID, lanHosts);
}
} catch (FreeboxException e) {
logger.warn("Error while requesting data for things discovery: {}", e.getMessage());
}
}
}
private void discoverHome(HomeManager homeManager, ThingUID bridgeUID) throws FreeboxException {
NodeConfigurationBuilder builder = NodeConfigurationBuilder.getInstance();
try {
homeManager.getHomeNodes().forEach(
node -> builder.configure(bridgeUID, node).ifPresent(result -> thingDiscovered(result.build())));
} catch (PermissionException e) {
logger.warn("Missing permission to discover Home {}", e.getPermission());
}
}
private void discoverPlugs(FreeplugManager freeplugManager, ThingUID bridgeUID) {
FreeplugConfigurationBuilder builder = FreeplugConfigurationBuilder.getInstance();
try {
freeplugManager.getPlugs().forEach(plug -> thingDiscovered(builder.configure(bridgeUID, plug).build()));
} catch (FreeboxException e) {
logger.warn("Error discovering freeplugs {}", e.getMessage());
}
}
private void discoverPhone(PhoneManager phoneManager, ThingUID bridgeUID) throws FreeboxException {
PhoneConfigurationBuilder builder = PhoneConfigurationBuilder.getInstance();
List<Status> statuses = List.of();
try {
statuses = phoneManager.getPhoneStatuses();
statuses.forEach(phone -> thingDiscovered(builder.configure(bridgeUID, phone).build()));
} catch (FreeboxException e) {
logger.warn("Error discovering phones {}", e.getMessage());
}
if (!statuses.isEmpty()) {
ThingUID thingUID = new ThingUID(THING_TYPE_CALL, bridgeUID, "landline");
logger.debug("Adding new Call thing {} to inbox", thingUID);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
.withLabel("Freebox Calls").build();
thingDiscovered(discoveryResult);
}
}
private void discoverHosts(FreeboxOsHandler localHandler, ThingUID bridgeUID, List<LanHost> lanHosts)
throws FreeboxException {
try {
List<MACAddress> wifiMacs = new ArrayList<>();
wifiMacs.addAll(localHandler.getManager(APManager.class).getStations().stream().map(Station::mac)
.collect(Collectors.toList()));
wifiMacs.addAll(localHandler.getManager(RepeaterManager.class).getHosts().stream().map(LanHost::getMac)
.collect(Collectors.toList()));
lanHosts.forEach(lanHost -> {
MACAddress mac = lanHost.getMac();
String macString = mac.toColonDelimitedString();
ThingUID thingUID = new ThingUID(wifiMacs.contains(mac) ? THING_TYPE_WIFI_HOST : THING_TYPE_HOST,
bridgeUID, mac.toHexString(false));
logger.debug("Adding new Freebox Network Host {} to inbox", thingUID);
DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
.withLabel(lanHost.getPrimaryName().orElse("Network Device %s".formatted(macString)))
.withTTL(300).withProperty(Thing.PROPERTY_MAC_ADDRESS, macString)
.withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS);
thingDiscovered(builder.build());
});
} catch (PermissionException e) {
logger.warn("Missing permission to discover Hosts {}", e.getPermission());
}
}
private void discoverVM(VmManager vmManager, ThingUID bridgeUID, List<LanHost> lanHosts) throws FreeboxException {
try {
vmManager.getDevices().forEach(vm -> {
MACAddress mac = vm.mac();
lanHosts.removeIf(host -> host.getMac().equals(mac));
ThingUID thingUID = new ThingUID(THING_TYPE_VM, bridgeUID, mac.toHexString(false));
logger.debug("Adding new VM Device {} to inbox", thingUID);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
.withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS)
.withLabel("%s (VM)".formatted(vm.name())).withProperty(ClientConfiguration.ID, vm.id())
.withProperty(Thing.PROPERTY_MAC_ADDRESS, mac.toColonDelimitedString()).build();
thingDiscovered(discoveryResult);
});
} catch (PermissionException e) {
logger.warn("Missing permission to discover VM {}", e.getPermission());
}
}
private void discoverRepeater(RepeaterManager repeaterManager, ThingUID bridgeUID, List<LanHost> lanHosts)
throws FreeboxException {
try {
List<Repeater> repeaters = repeaterManager.getDevices();
repeaters.forEach(repeater -> {
MACAddress mac = repeater.mainMac();
lanHosts.removeIf(host -> host.getMac().equals(mac));
ThingUID thingUID = new ThingUID(THING_TYPE_REPEATER, bridgeUID, Integer.toString(repeater.id()));
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
.withLabel("Repeater %s".formatted(repeater.name()))
.withProperty(Thing.PROPERTY_MAC_ADDRESS, mac.toColonDelimitedString())
.withProperty(ClientConfiguration.ID, repeater.id())
.withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
thingDiscovered(discoveryResult);
});
} catch (PermissionException e) {
logger.warn("Missing permission to discover Repeater {}", e.getPermission());
}
}
private void discoverServer(SystemManager systemManager, ThingUID bridgeUID) throws FreeboxException {
try {
Config config = systemManager.getConfig();
ThingTypeUID targetType = config.boardName().startsWith("fbxgw7") ? THING_TYPE_DELTA
: THING_TYPE_REVOLUTION;
ThingUID thingUID = new ThingUID(targetType, bridgeUID, config.serial());
logger.debug("Adding new Freebox Server {} to inbox", thingUID);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
.withProperty(Thing.PROPERTY_MAC_ADDRESS, config.mac())
.withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).withLabel(config.modelInfo().prettyName())
.build();
thingDiscovered(discoveryResult);
} catch (PermissionException e) {
logger.warn("Missing permission to discover Server {}", e.getPermission());
}
}
private void discoverPlayer(PlayerManager playerManager, ThingUID bridgeUID, List<LanHost> lanHosts)
throws FreeboxException {
try {
for (Player player : playerManager.getDevices()) {
lanHosts.removeIf(host -> host.getMac().equals(player.mac()));
ThingUID thingUID = new ThingUID(player.apiAvailable() ? THING_TYPE_ACTIVE_PLAYER : THING_TYPE_PLAYER,
bridgeUID, Integer.toString(player.id()));
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
.withLabel(player.deviceName())
.withProperty(Thing.PROPERTY_MAC_ADDRESS, player.mac().toColonDelimitedString())
.withProperty(ClientConfiguration.ID, player.id())
.withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
thingDiscovered(discoveryResult);
}
} catch (PermissionException e) {
logger.warn("Missing permission to discover Player {}", e.getPermission());
}
}
}

View File

@ -0,0 +1,108 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.action.ActivePlayerActions;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager;
import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Configuration;
import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.ForegroundApp;
import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Player;
import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Status;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ActivePlayerHandler} is responsible for handling everything associated to Freebox Player with api
* capabilities.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class ActivePlayerHandler extends PlayerHandler implements FreeDeviceIntf {
private final Logger logger = LoggerFactory.getLogger(ActivePlayerHandler.class);
private final ChannelUID eventChannelUID;
private long uptime = -1;
public ActivePlayerHandler(Thing thing) {
super(thing);
eventChannelUID = new ChannelUID(getThing().getUID(), SYS_INFO, BOX_EVENT);
}
@Override
void initializeProperties(Map<String, String> properties) throws FreeboxException {
super.initializeProperties(properties);
Player player = getManager(PlayerManager.class).getDevice(getClientId());
if (player.reachable()) {
Configuration config = getManager(PlayerManager.class).getConfig(player.id());
properties.put(Thing.PROPERTY_SERIAL_NUMBER, config.serial());
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, config.firmwareVersion());
}
}
@Override
protected void internalPoll() throws FreeboxException {
super.internalPoll();
if (thing.getStatus().equals(ThingStatus.ONLINE)) {
Status status = getManager(PlayerManager.class).getPlayerStatus(getClientId());
updateChannelString(PLAYER_STATUS, PLAYER_STATUS, status.powerState().name());
ForegroundApp foreground = status.foregroundApp();
if (foreground != null) {
updateChannelString(PLAYER_STATUS, PACKAGE, foreground._package());
}
Configuration config = getManager(PlayerManager.class).getConfig(getClientId());
uptime = checkUptimeAndFirmware(config.uptimeVal(), uptime, config.firmwareVersion());
updateChannelQuantity(SYS_INFO, UPTIME, uptime, Units.SECOND);
}
}
public void reboot() {
processReboot(() -> {
try {
getManager(PlayerManager.class).reboot(getClientId());
} catch (FreeboxException e) {
logger.warn("Error rebooting: {}", e.getMessage());
}
});
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singletonList(ActivePlayerActions.class);
}
@Override
public ChannelUID getEventChannelUID() {
return eventChannelUID;
}
@Override
public void triggerChannel(ChannelUID channelUID, String event) {
super.triggerChannel(channelUID, event);
}
}

View File

@ -0,0 +1,160 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.core.audio.AudioFormat.*;
import java.io.IOException;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.MediaReceiverManager;
import org.openhab.binding.freeboxos.internal.api.rest.MediaReceiverManager.Action;
import org.openhab.binding.freeboxos.internal.api.rest.MediaReceiverManager.MediaType;
import org.openhab.core.audio.AudioFormat;
import org.openhab.core.audio.AudioHTTPServer;
import org.openhab.core.audio.AudioSinkAsync;
import org.openhab.core.audio.AudioStream;
import org.openhab.core.audio.StreamServed;
import org.openhab.core.audio.URLAudioStream;
import org.openhab.core.audio.UnsupportedAudioFormatException;
import org.openhab.core.audio.UnsupportedAudioStreamException;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.ThingStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AirMediaSink} is holding AudioSink capabilities for various
* things.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class AirMediaSink extends AudioSinkAsync {
private static final Set<Class<? extends AudioStream>> SUPPORTED_STREAMS = Set.of(AudioStream.class);
private static final Set<AudioFormat> BASIC_FORMATS = Set.of(WAV, OGG);
private static final Set<AudioFormat> ALL_MP3_FORMATS = Set.of(
new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, null, 96000, null),
new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, null, 112000, null),
new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, null, 128000, null),
new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, null, 160000, null),
new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, null, 192000, null),
new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, null, 224000, null),
new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, null, 256000, null),
new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, null, 320000, null));
private final Logger logger = LoggerFactory.getLogger(AirMediaSink.class);
private final ApiConsumerHandler thingHandler;
private final Set<AudioFormat> supportedFormats = new HashSet<>();
private final AudioHTTPServer audioHTTPServer;
private final String callbackUrl;
private final String playerName;
private final String password;
public AirMediaSink(ApiConsumerHandler thingHandler, AudioHTTPServer audioHTTPServer, String callbackUrl,
String playerName, String password, boolean acceptAllMp3) {
this.thingHandler = thingHandler;
this.audioHTTPServer = audioHTTPServer;
this.playerName = playerName;
this.callbackUrl = callbackUrl;
this.password = password;
supportedFormats.addAll(BASIC_FORMATS);
if (acceptAllMp3) {
supportedFormats.addAll(ALL_MP3_FORMATS);
} else { // Only accept MP3 bitrates >= 96 kbps
supportedFormats.add(MP3);
}
}
@Override
public Set<Class<? extends AudioStream>> getSupportedStreams() {
return SUPPORTED_STREAMS;
}
@Override
public PercentType getVolume() throws IOException {
logger.debug("getVolume received but AirMedia does not have the capability - returning 100%.");
return PercentType.HUNDRED;
}
@Override
public void setVolume(PercentType volume) throws IOException {
logger.debug("setVolume received but AirMedia does not have the capability - ignoring it.");
}
@Override
public String getId() {
return thingHandler.getThing().getUID().toString();
}
@Override
public @Nullable String getLabel(@Nullable Locale locale) {
return thingHandler.getThing().getLabel();
}
@Override
protected void processAsynchronously(@Nullable AudioStream audioStream)
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
if (thingHandler.getThing().getStatus() == ThingStatus.ONLINE) {
try {
MediaReceiverManager manager = thingHandler.getManager(MediaReceiverManager.class);
if (audioStream == null) {
manager.sendToReceiver(playerName, password, Action.STOP, MediaType.VIDEO);
return;
}
if (audioStream instanceof URLAudioStream urlAudioStream) {
// it is an external URL, we can access it directly
logger.debug("AirPlay audio sink: process url {}", urlAudioStream.getURL());
playMedia(manager, urlAudioStream.getURL());
return;
}
// we serve it on our own HTTP server
StreamServed streamServed;
try {
streamServed = audioHTTPServer.serve(audioStream, 5, true);
} catch (IOException e) {
try {
audioStream.close();
} catch (IOException ex) {
logger.debug("Exception while closing audioStream");
}
throw new UnsupportedAudioStreamException(
"AirPlay device was not able to handle the audio stream (cache on disk failed).",
audioStream.getClass(), e);
}
streamServed.playEnd().thenRun(() -> this.playbackFinished(audioStream));
logger.debug("AirPlay audio sink: process url {}", callbackUrl + streamServed.url());
playMedia(manager, callbackUrl + streamServed.url());
} catch (FreeboxException e) {
logger.warn("Audio stream playback failed: {}", e.getMessage());
}
}
}
private void playMedia(MediaReceiverManager manager, String url) throws FreeboxException {
manager.sendToReceiver(playerName, password, Action.STOP, MediaType.VIDEO);
manager.sendToReceiver(playerName, password, Action.START, MediaType.VIDEO, url);
}
@Override
public Set<AudioFormat> getSupportedFormats() {
return supportedFormats;
}
}

View File

@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* The {@link AlarmHandler} is responsible for handling everything associated to
* any Freebox Home Alarm thing type.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class AlarmHandler extends HomeNodeHandler {
public AlarmHandler(Thing thing) {
super(thing);
}
@Override
protected State getChannelState(HomeManager homeManager, String channelId, EndpointState state) {
String value = state.value();
if (value == null) {
return UnDefType.NULL;
}
return switch (channelId) {
case NODE_BATTERY -> DecimalType.valueOf(value);
case ALARM_PIN -> StringType.valueOf(value);
case ALARM_SOUND, ALARM_VOLUME -> QuantityType.valueOf(value + " %");
case ALARM_TIMEOUT1, ALARM_TIMEOUT2, ALARM_TIMEOUT3 -> QuantityType.valueOf(value + " s");
default -> UnDefType.NULL;
};
}
}

View File

@ -0,0 +1,352 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.Source;
import org.openhab.binding.freeboxos.internal.api.rest.MediaReceiverManager;
import org.openhab.binding.freeboxos.internal.api.rest.MediaReceiverManager.MediaType;
import org.openhab.binding.freeboxos.internal.api.rest.MediaReceiverManager.Receiver;
import org.openhab.binding.freeboxos.internal.api.rest.RestManager;
import org.openhab.binding.freeboxos.internal.config.ApiConsumerConfiguration;
import org.openhab.binding.freeboxos.internal.config.ClientConfiguration;
import org.openhab.core.audio.AudioSink;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.BridgeHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.osgi.framework.ServiceRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import inet.ipaddr.IPAddress;
import inet.ipaddr.MACAddressString;
import inet.ipaddr.mac.MACAddress;
/**
* The {@link ServerHandler} is a base abstract class for all devices made available by the FreeboxOs bridge
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
abstract class ApiConsumerHandler extends BaseThingHandler implements ApiConsumerIntf {
private final Logger logger = LoggerFactory.getLogger(ApiConsumerHandler.class);
private final Map<String, ScheduledFuture<?>> jobs = new HashMap<>();
private @Nullable ServiceRegistration<?> reg;
ApiConsumerHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
FreeboxOsHandler bridgeHandler = checkBridgeHandler();
if (bridgeHandler == null) {
return;
}
Map<String, String> properties = editProperties();
if (properties.isEmpty()) {
try {
initializeProperties(properties);
checkAirMediaCapabilities(properties);
updateProperties(properties);
} catch (FreeboxException e) {
logger.warn("Error getting thing {} properties: {}", thing.getUID(), e.getMessage());
}
}
boolean isAudioReceiver = Boolean.parseBoolean(properties.get(MediaType.AUDIO.name()));
if (isAudioReceiver) {
configureMediaSink(bridgeHandler, properties.getOrDefault(Source.UPNP.name(), ""));
}
startRefreshJob();
}
private void configureMediaSink(FreeboxOsHandler bridgeHandler, String upnpName) {
try {
Receiver receiver = getManager(MediaReceiverManager.class).getReceiver(upnpName);
if (receiver != null && reg == null) {
ApiConsumerConfiguration config = getConfig().as(ApiConsumerConfiguration.class);
String callbackURL = bridgeHandler.getCallbackURL();
if (!config.password.isEmpty() || !receiver.passwordProtected()) {
reg = bridgeHandler.getBundleContext().registerService(
AudioSink.class.getName(), new AirMediaSink(this, bridgeHandler.getAudioHTTPServer(),
callbackURL, receiver.name(), config.password, config.acceptAllMp3),
new Hashtable<>());
} else {
logger.info("A password needs to be configured to enable Air Media capability.");
}
}
} catch (FreeboxException e) {
logger.warn("Unable to retrieve Media Receivers: {}", e.getMessage());
}
}
public <T extends RestManager> T getManager(Class<T> clazz) throws FreeboxException {
FreeboxOsHandler handler = checkBridgeHandler();
if (handler != null) {
return handler.getManager(clazz);
}
throw new FreeboxException("Bridge handler not yet defined");
}
abstract void initializeProperties(Map<String, String> properties) throws FreeboxException;
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
if (checkBridgeHandler() != null) {
startRefreshJob();
} else {
stopJobs();
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType || getThing().getStatus() != ThingStatus.ONLINE) {
return;
}
try {
if (checkBridgeHandler() == null || !internalHandleCommand(channelUID.getIdWithoutGroup(), command)) {
logger.debug("Unexpected command {} on channel {}", command, channelUID.getId());
}
} catch (FreeboxException e) {
logger.warn("Error handling command: {}", e.getMessage());
}
}
private void checkAirMediaCapabilities(Map<String, String> properties) throws FreeboxException {
String upnpName = properties.getOrDefault(Source.UPNP.name(), "");
Receiver receiver = getManager(MediaReceiverManager.class).getReceiver(upnpName);
if (receiver != null) {
receiver.capabilities().entrySet()
.forEach(entry -> properties.put(entry.getKey().name(), entry.getValue().toString()));
}
}
private @Nullable FreeboxOsHandler checkBridgeHandler() {
Bridge bridge = getBridge();
if (bridge != null) {
BridgeHandler handler = bridge.getHandler();
if (handler instanceof FreeboxOsHandler) {
if (bridge.getStatus() == ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
return (FreeboxOsHandler) handler;
}
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_MISSING_ERROR);
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
}
return null;
}
@Override
public void dispose() {
stopJobs();
ServiceRegistration<?> localReg = reg;
if (localReg != null) {
localReg.unregister();
}
super.dispose();
}
private void startRefreshJob() {
removeJob("GlobalJob");
int refreshInterval = getConfigAs(ApiConsumerConfiguration.class).refreshInterval;
logger.debug("Scheduling state update every {} seconds for thing {}...", refreshInterval, getThing().getUID());
ThingStatusDetail detail = thing.getStatusInfo().getStatusDetail();
if (ThingStatusDetail.DUTY_CYCLE.equals(detail)) {
try {
internalPoll();
} catch (FreeboxException ignore) {
// An exception is normal if the box is rebooting then let's try again later...
addJob("Initialize", this::initialize, 10, TimeUnit.SECONDS);
return;
}
}
addJob("GlobalJob", () -> {
try {
internalPoll();
} catch (FreeboxException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}, 0, refreshInterval, TimeUnit.SECONDS);
}
private void removeJob(String name) {
ScheduledFuture<?> existing = jobs.get(name);
if (existing != null && !existing.isCancelled()) {
existing.cancel(true);
}
}
@Override
public void addJob(String name, Runnable command, long initialDelay, long delay, TimeUnit unit) {
removeJob(name);
jobs.put(name, scheduler.scheduleWithFixedDelay(command, initialDelay, delay, unit));
}
@Override
public void addJob(String name, Runnable command, long delay, TimeUnit unit) {
removeJob(name);
jobs.put(name, scheduler.schedule(command, delay, unit));
}
@Override
public void stopJobs() {
jobs.keySet().forEach(name -> removeJob(name));
jobs.clear();
}
protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
return false;
}
protected abstract void internalPoll() throws FreeboxException;
private void updateIfActive(String group, String channelId, State state) {
ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
if (isLinked(id)) {
updateState(id, state);
}
}
protected void updateIfActive(String channelId, State state) {
ChannelUID id = new ChannelUID(getThing().getUID(), channelId);
if (isLinked(id)) {
updateState(id, state);
}
}
protected void updateChannelDateTimeState(String channelId, @Nullable ZonedDateTime timestamp) {
updateIfActive(channelId, timestamp == null ? UnDefType.NULL : new DateTimeType(timestamp));
}
protected void updateChannelDateTimeState(String group, String channelId, @Nullable ZonedDateTime timestamp) {
updateIfActive(group, channelId, timestamp == null ? UnDefType.NULL : new DateTimeType(timestamp));
}
protected void updateChannelOnOff(String group, String channelId, boolean value) {
updateIfActive(group, channelId, OnOffType.from(value));
}
protected void updateChannelOnOff(String channelId, boolean value) {
updateIfActive(channelId, OnOffType.from(value));
}
protected void updateChannelString(String group, String channelId, @Nullable String value) {
updateIfActive(group, channelId, value != null ? new StringType(value) : UnDefType.NULL);
}
protected void updateChannelString(String group, String channelId, @Nullable IPAddress value) {
updateIfActive(group, channelId, value != null ? new StringType(value.toCanonicalString()) : UnDefType.NULL);
}
protected void updateChannelString(String channelId, @Nullable String value) {
updateIfActive(channelId, value != null ? new StringType(value) : UnDefType.NULL);
}
protected void updateChannelString(String channelId, Enum<?> value) {
updateIfActive(channelId, new StringType(value.name()));
}
protected void updateChannelString(String group, String channelId, Enum<?> value) {
updateIfActive(group, channelId, new StringType(value.name()));
}
protected void updateChannelQuantity(String group, String channelId, double d, Unit<?> unit) {
updateChannelQuantity(group, channelId, new QuantityType<>(d, unit));
}
protected void updateChannelQuantity(String channelId, @Nullable QuantityType<?> quantity) {
updateIfActive(channelId, quantity != null ? quantity : UnDefType.NULL);
}
protected void updateChannelQuantity(String group, String channelId, @Nullable QuantityType<?> quantity) {
updateIfActive(group, channelId, quantity != null ? quantity : UnDefType.NULL);
}
protected void updateChannelDecimal(String group, String channelId, @Nullable Integer value) {
updateIfActive(group, channelId, value != null ? new DecimalType(value) : UnDefType.NULL);
}
protected void updateChannelQuantity(String group, String channelId, QuantityType<?> qtty, Unit<?> unit) {
updateChannelQuantity(group, channelId, qtty.toUnit(unit));
}
@Override
public void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
super.updateStatus(status, statusDetail, description);
}
@Override
public Map<String, String> editProperties() {
return super.editProperties();
}
@Override
public void updateProperties(@Nullable Map<String, String> properties) {
super.updateProperties(properties);
}
@Override
public Configuration getConfig() {
return super.getConfig();
}
@Override
public int getClientId() {
return ((BigDecimal) getConfig().get(ClientConfiguration.ID)).intValue();
}
@Override
public MACAddress getMac() {
String mac = (String) getConfig().get(Thing.PROPERTY_MAC_ADDRESS);
return new MACAddressString(mac).getAddress();
}
}

View File

@ -0,0 +1,62 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import java.math.BigDecimal;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.freeboxos.internal.config.ClientConfiguration;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.ThingHandler;
import inet.ipaddr.MACAddressString;
import inet.ipaddr.mac.MACAddress;
/**
* The {@link ApiConsumerIntf} defines some common methods for various devices (server, player, repeater) not belonging
* to the same class hierarchy
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public interface ApiConsumerIntf extends ThingHandler {
Map<String, String> editProperties();
Configuration getConfig();
void updateProperties(@Nullable Map<String, String> properties);
void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description);
void stopJobs();
void addJob(String name, Runnable command, long initialDelay, long delay, TimeUnit unit);
void addJob(String name, Runnable command, long delay, TimeUnit unit);
default int getClientId() {
return ((BigDecimal) getConfig().get(ClientConfiguration.ID)).intValue();
}
default MACAddress getMac() {
String mac = (String) getConfig().get(Thing.PROPERTY_MAC_ADDRESS);
return new MACAddressString(mac).getAddress();
}
}

View File

@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Endpoint;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* The {@link BasicShutterHandler} is responsible for handling everything associated to
* any Freebox Home basic-shutter thing type.
*
* @author ben12 - Initial contribution
*/
@NonNullByDefault
public class BasicShutterHandler extends HomeNodeHandler {
private static final Set<String> SHUTTER_ENDPOINTS = Set.of(SHUTTER_STOP, BASIC_SHUTTER_UP, BASIC_SHUTTER_DOWN);
public BasicShutterHandler(Thing thing) {
super(thing);
}
@Override
protected void internalConfigureChannel(String channelId, Configuration conf, List<Endpoint> endpoints) {
endpoints.stream().filter(ep -> channelId.equals(BASIC_SHUTTER_STATE) && SHUTTER_ENDPOINTS.contains(ep.name()))
.forEach(endPoint -> conf.put(endPoint.name(), endPoint.id()));
}
@Override
protected State getChannelState(HomeManager homeManager, String channelId, EndpointState state) {
String value = state.value();
return value != null && channelId.equals(BASIC_SHUTTER_STATE)
? state.asBoolean() ? OpenClosedType.CLOSED : OpenClosedType.OPEN
: UnDefType.NULL;
}
@Override
protected boolean executeChannelCommand(HomeManager homeManager, String channelId, Command command,
Configuration config) throws FreeboxException {
Integer slot = getSlotId(config, command.toString().toLowerCase());
if (BASIC_SHUTTER_STATE.equals(channelId) && slot != null) {
return homeManager.putCommand(getClientId(), slot, true);
}
return false;
}
}

View File

@ -0,0 +1,108 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.action.CallActions;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.CallManager;
import org.openhab.binding.freeboxos.internal.api.rest.CallManager.Call;
import org.openhab.binding.freeboxos.internal.api.rest.CallManager.Type;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link CallHandler} is responsible for handling everything associated to the phone calls received on the box line
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class CallHandler extends ApiConsumerHandler {
private final Logger logger = LoggerFactory.getLogger(CallHandler.class);
private final Map<Type, ZonedDateTime> lastCalls = new HashMap<>(Type.values().length);
public CallHandler(Thing thing) {
super(thing);
}
@Override
void initializeProperties(Map<String, String> properties) throws FreeboxException {
// nothing to do here
}
@Override
protected void internalPoll() throws FreeboxException {
logger.debug("Polling phone calls ...");
lastCalls.clear();
List<Call> entries = getManager(CallManager.class).getCallEntries();
Arrays.stream(Type.values()).forEach(callType -> entries.stream().filter(call -> call.type().equals(callType))
.reduce((first, second) -> second).ifPresent(this::updateCallChannels));
// Clear incoming call if the youngest is not an incoming call
lastCalls.entrySet().stream().sorted(Map.Entry.comparingByValue()).reduce((first, second) -> second)
.map(entry -> entry.getKey()).filter(type -> !Type.INCOMING.equals(type)).ifPresent(type -> {
String groupName = Type.INCOMING.name().toLowerCase();
getThing().getChannelsOfGroup(groupName).stream().map(Channel::getUID).filter(uid -> isLinked(uid))
.forEach(uid -> updateState(uid, UnDefType.NULL));
});
updateStatus(ThingStatus.ONLINE);
}
private void updateCallChannels(Call call) {
Type lastType = call.type();
lastCalls.put(lastType, call.datetime());
String group = lastType.name().toLowerCase();
String phoneNumber = call.number();
updateChannelString(group, NUMBER, phoneNumber);
updateChannelString(group, NAME, call.name());
updateChannelDateTimeState(group, TIMESTAMP, call.datetime());
// Do not consider duration for Missed & incoming calls
if (lastType == Type.ACCEPTED || lastType == Type.OUTGOING) {
updateChannelQuantity(group, DURATION, call.duration(), Units.SECOND);
}
}
public void emptyQueue() {
try {
getManager(CallManager.class).emptyQueue();
} catch (FreeboxException e) {
logger.warn("Error clearing call logs: {}", e.getMessage());
}
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(CallActions.class);
}
}

View File

@ -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.handler;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
/**
* The {@link CameraHandler} is responsible for handling everything associated to
* any Freebox Home Camera thing type.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class CameraHandler extends ApiConsumerHandler {
public CameraHandler(Thing thing) {
super(thing);
}
@Override
void initializeProperties(Map<String, String> properties) throws FreeboxException {
}
@Override
protected void internalPoll() throws FreeboxException {
}
@Override
protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
return super.internalHandleCommand(channelId, command);
}
}

View File

@ -0,0 +1,83 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager;
import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager.Config;
import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager.Status;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
/**
* The {@link DectHandler} is responsible for handling DECT specifics of the Telephony API
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class DectHandler extends FxsHandler {
public DectHandler(Thing thing) {
super(thing);
}
@Override
protected void updateConfigChannels(Config config) {
super.updateConfigChannels(config);
updateChannelOnOff(DECT_ACTIVE, config.dectEnabled());
updateChannelOnOff(ALTERNATE_RING, config.dectRingOnOff());
}
@Override
protected void updateStatusChannels(Status status) {
super.updateStatusChannels(status);
updateIfActive(GAIN_RX, new PercentType(status.gainRx()));
updateIfActive(GAIN_TX, new PercentType(status.gainTx()));
}
@Override
protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
PhoneManager phoneManager = getManager(PhoneManager.class);
if (command instanceof OnOffType) {
boolean status = OnOffType.ON.equals(command);
if (RINGING.equals(channelId)) {
phoneManager.ringDect(status);
return true;
} else if (DECT_ACTIVE.equals(channelId)) {
phoneManager.setStatus(status);
return true;
} else if (ALTERNATE_RING.equals(channelId)) {
phoneManager.alternateRing(status);
return true;
}
}
if (command instanceof PercentType) {
PercentType percent = (PercentType) command;
if (GAIN_RX.equals(channelId)) {
phoneManager.setGainRx(getClientId(), percent.intValue());
updateIfActive(GAIN_RX, percent);
return true;
} else if (GAIN_TX.equals(channelId)) {
phoneManager.setGainTx(getClientId(), percent.intValue());
updateIfActive(GAIN_RX, percent);
return true;
}
}
return super.internalHandleCommand(channelId, command);
}
}

View File

@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
/**
* The {@link FreeDeviceIntf} defines some common methods for various devices (server, player, repeater) not belonging
* to the same class hierarchy
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public interface FreeDeviceIntf extends ApiConsumerIntf {
public ChannelUID getEventChannelUID();
public void triggerChannel(ChannelUID channelUID, String event);
default long checkUptimeAndFirmware(long newUptime, long oldUptime, String firmwareVersion) {
if (newUptime < oldUptime) {
triggerChannel(getEventChannelUID(), "restarted");
Map<String, String> properties = editProperties();
if (!firmwareVersion.equals(properties.get(Thing.PROPERTY_FIRMWARE_VERSION))) {
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmwareVersion);
updateProperties(properties);
triggerChannel(getEventChannelUID(), "firmware_updated");
}
}
return newUptime;
}
default void processReboot(Runnable actualReboot) {
triggerChannel(getEventChannelUID(), "reboot_requested");
actualReboot.run();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.DUTY_CYCLE, "System rebooting...");
stopJobs();
addJob("Initialize", this::initialize, 30, TimeUnit.SECONDS);
}
}

View File

@ -0,0 +1,151 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.FreeboxOsSession;
import org.openhab.binding.freeboxos.internal.api.rest.RestManager;
import org.openhab.binding.freeboxos.internal.config.FreeboxOsConfiguration;
import org.openhab.binding.freeboxos.internal.discovery.FreeboxOsDiscoveryService;
import org.openhab.core.audio.AudioHTTPServer;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link FreeboxOsHandler} handle common parts of Freebox bridges.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class FreeboxOsHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(FreeboxOsHandler.class);
private final FreeboxOsSession session;
private final String callbackURL;
private final BundleContext bundleContext;
private final AudioHTTPServer audioHTTPServer;
private Optional<Future<?>> openConnectionJob = Optional.empty();
private Optional<Future<?>> grantingJob = Optional.empty();
public FreeboxOsHandler(Bridge thing, FreeboxOsSession session, String callbackURL, BundleContext bundleContext,
AudioHTTPServer audioHTTPServer) {
super(thing);
this.session = session;
this.callbackURL = callbackURL;
this.bundleContext = bundleContext;
this.audioHTTPServer = audioHTTPServer;
}
@Override
public void initialize() {
freeConnectionJob();
FreeboxOsConfiguration config = getConfiguration();
openConnectionJob = Optional.of(scheduler.submit(() -> {
try {
session.initialize(config);
if (config.appToken.isBlank()) {
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"@text/info-conf-pending");
grantingJob = Optional.of(scheduler.schedule(this::processGranting, 2, TimeUnit.SECONDS));
return;
} else {
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE);
session.openSession(config.appToken);
}
updateStatus(ThingStatus.ONLINE);
} catch (FreeboxException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
} catch (InterruptedException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}));
}
private void processGranting() {
try {
String appToken = session.grant();
if (appToken.isBlank()) {
grantingJob = Optional.of(scheduler.schedule(this::processGranting, 2, TimeUnit.SECONDS));
} else {
Configuration thingConfig = editConfiguration();
thingConfig.put(FreeboxOsConfiguration.APP_TOKEN, appToken);
updateConfiguration(thingConfig);
logger.info("AppToken updated, ensure giving permissions in the Freebox management console");
initialize();
}
} catch (FreeboxException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
}
}
public <T extends RestManager> T getManager(Class<T> clazz) throws FreeboxException {
return session.getManager(clazz);
}
private void freeConnectionJob() {
openConnectionJob.ifPresent(job -> job.cancel(true));
openConnectionJob = Optional.empty();
grantingJob.ifPresent(job -> job.cancel(true));
grantingJob = Optional.empty();
}
@Override
public void dispose() {
freeConnectionJob();
session.closeSession();
super.dispose();
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(FreeboxOsDiscoveryService.class);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
public FreeboxOsConfiguration getConfiguration() {
return getConfigAs(FreeboxOsConfiguration.class);
}
public String getCallbackURL() {
return callbackURL;
}
public BundleContext getBundleContext() {
return bundleContext;
}
public AudioHTTPServer getAudioHTTPServer() {
return audioHTTPServer;
}
}

View File

@ -0,0 +1,102 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.action.FreeplugActions;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.FreeplugManager;
import org.openhab.binding.freeboxos.internal.api.rest.FreeplugManager.NetRole;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link FreeplugHandler} is responsible for handling everything associated to a CPL gateway managed by the freebox
* server
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class FreeplugHandler extends ApiConsumerHandler {
private final Logger logger = LoggerFactory.getLogger(FreeplugHandler.class);
public FreeplugHandler(Thing thing) {
super(thing);
}
@Override
void initializeProperties(Map<String, String> properties) throws FreeboxException {
getManager(FreeplugManager.class).getPlug(getMac()).ifPresent(plug -> {
NetRole role = plug.netRole();
properties.put(Thing.PROPERTY_MODEL_ID, plug.model());
properties.put(ROLE, role.name());
properties.put(NET_ID, plug.netId());
properties.put(ETHERNET_SPEED, String.format("%d Mb/s", plug.ethSpeed()));
properties.put(LOCAL, Boolean.valueOf(plug.local()).toString());
properties.put(FULL_DUPLEX, Boolean.valueOf(plug.ethFullDuplex()).toString());
if (role.equals(NetRole.CCO)) { // Coordinator does not provide rate up or down
List<Channel> channels = new ArrayList<>(getThing().getChannels());
channels.removeIf(channel -> channel.getUID().getId().contains("rate"));
updateThing(editThing().withChannels(channels).build());
}
});
}
@Override
protected void internalPoll() throws FreeboxException {
getManager(FreeplugManager.class).getPlug(getMac()).ifPresent(plug -> {
ZonedDateTime lastSeen = ZonedDateTime.now().minusSeconds(plug.inactive());
updateChannelDateTimeState(LAST_SEEN, lastSeen);
updateChannelString(LINE_STATUS, plug.ethPortStatus());
updateChannelOnOff(REACHABLE, plug.hasNetwork());
updateRateChannel(RATE + "-down", plug.rxRate());
updateRateChannel(RATE + "-up", plug.txRate());
});
}
private void updateRateChannel(String channel, int rate) {
QuantityType<?> qtty = rate != -1 ? new QuantityType<>(rate, Units.MEGABIT_PER_SECOND) : null;
updateChannelQuantity(channel, qtty);
}
public void reset() {
try {
getManager(FreeplugManager.class).reboot(getMac());
logger.debug("Freeplug {} succesfully restarted", getMac());
} catch (FreeboxException e) {
logger.warn("Error restarting freeplug: {}", e.getMessage());
}
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(FreeplugActions.class);
}
}

View File

@ -0,0 +1,80 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager;
import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager.Config;
import org.openhab.binding.freeboxos.internal.api.rest.PhoneManager.Status;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link FxsHandler} is responsible for handling everything associated to the landline associated with the
* Freebox Server.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class FxsHandler extends ApiConsumerHandler {
private final Logger logger = LoggerFactory.getLogger(FxsHandler.class);
public FxsHandler(Thing thing) {
super(thing);
}
@Override
void initializeProperties(Map<String, String> properties) throws FreeboxException {
getManager(PhoneManager.class).getStatus(getClientId())
.ifPresent(status -> properties.put(Thing.PROPERTY_VENDOR, status.vendor()));
}
@Override
protected void internalPoll() throws FreeboxException {
logger.debug("Polling landline status...");
Config config = getManager(PhoneManager.class).getConfig();
updateConfigChannels(config);
getManager(PhoneManager.class).getStatus(getClientId()).ifPresent(this::updateStatusChannels);
}
protected void updateConfigChannels(Config config) {
updateChannelString(TELEPHONY_SERVICE, config.network());
}
protected void updateStatusChannels(Status status) {
updateChannelOnOff(ONHOOK, status.onHook());
updateChannelOnOff(RINGING, status.isRinging());
updateChannelString(HARDWARE_STATUS, status.hardwareDefect() ? "KO" : "OK");
updateStatus(ThingStatus.ONLINE);
}
@Override
protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
if (RINGING.equals(channelId) && command instanceof OnOffType) {
getManager(PhoneManager.class).ringFxs(TRUE_COMMANDS.contains(command));
return true;
}
return super.internalHandleCommand(channelId, command);
}
}

View File

@ -0,0 +1,134 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import java.math.BigDecimal;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Endpoint;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EpType;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.HomeNode;
import org.openhab.binding.freeboxos.internal.config.ApiConsumerConfiguration;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link HomeNodeHandler} is the base class for handler of home node things.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public abstract class HomeNodeHandler extends ApiConsumerHandler {
private final Logger logger = LoggerFactory.getLogger(HomeNodeHandler.class);
public HomeNodeHandler(Thing thing) {
super(thing);
}
@Override
void initializeProperties(Map<String, String> properties) throws FreeboxException {
HomeNode node = getManager(HomeManager.class).getHomeNode(getClientId());
// Gets the lowest refresh time or else, we'll keep configuration default
node.showEndpoints().stream().filter(ep -> ep.epType() == EpType.SIGNAL).filter(ep -> ep.refresh() != 0)
.min(Comparator.comparing(Endpoint::refresh)).map(Endpoint::refresh).ifPresent(rate -> {
Configuration thingConfig = editConfiguration();
thingConfig.put(ApiConsumerConfiguration.REFRESH_INTERVAL, Integer.toString(rate / 1000));
updateConfiguration(thingConfig);
});
properties.putAll(node.props());
getThing().getChannels().forEach(channel -> {
Configuration conf = channel.getConfiguration();
node.type().endpoints().stream().filter(ep -> ep.name().equals(channel.getUID().getIdWithoutGroup()))
.forEach(endPoint -> conf.put(endPoint.epType().asConfId(), endPoint.id()));
internalConfigureChannel(channel.getUID().getIdWithoutGroup(), conf, node.type().endpoints());
});
}
protected void internalConfigureChannel(String channelId, Configuration conf, List<Endpoint> endpoints) {
}
@Override
protected void internalPoll() throws FreeboxException {
HomeManager homeManager = getManager(HomeManager.class);
getThing().getChannels().stream().filter(channel -> isLinked(channel.getUID())).forEach(channel -> {
State result = UnDefType.UNDEF;
Integer slotId = getSlotId(channel.getConfiguration(), EpType.SIGNAL.asConfId());
if (slotId instanceof Integer) {
try {
EndpointState state = homeManager.getEndpointsState(getClientId(), slotId);
if (state != null) {
result = getChannelState(homeManager, channel.getUID().getIdWithoutGroup(), state);
} else {
result = getChannelState(homeManager, channel.getUID().getIdWithoutGroup());
}
} catch (FreeboxException e) {
logger.warn("Error updating channel: {}", e.getMessage());
}
} else {
result = getChannelState(homeManager, channel.getUID().getIdWithoutGroup());
}
updateState(channel.getUID(), result);
});
}
@Override
protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
Channel channel = getThing().getChannel(channelId);
if (channel != null) {
Configuration config = channel.getConfiguration();
String channelWG = channel.getUID().getIdWithoutGroup();
Integer slotId = getSlotId(config, EpType.SLOT.asConfId());
HomeManager homeManager = getManager(HomeManager.class);
return slotId instanceof Integer ? executeSlotCommand(homeManager, channelWG, command, config, slotId)
: executeChannelCommand(homeManager, channelWG, command, config);
}
return super.internalHandleCommand(channelId, command);
}
protected @Nullable Integer getSlotId(Configuration configuration, String endPoint) {
Object slot = configuration.get(endPoint);
return slot instanceof BigDecimal slotId ? slotId.intValue() : null;
}
protected boolean executeChannelCommand(HomeManager homeManager, String channelId, Command command,
Configuration config) throws FreeboxException {
return false;
}
protected boolean executeSlotCommand(HomeManager homeManager, String channelId, Command command,
Configuration config, int slotId) throws FreeboxException {
return false;
}
protected State getChannelState(HomeManager homeManager, String channelWG) {
return UnDefType.UNDEF;
}
protected abstract State getChannelState(HomeManager homeManager, String channelId, EndpointState state);
}

View File

@ -0,0 +1,105 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.action.HostActions;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager;
import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.HostIntf;
import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.LanHost;
import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.Source;
import org.openhab.binding.freeboxos.internal.api.rest.WebSocketManager;
import org.openhab.binding.freeboxos.internal.config.ApiConsumerConfiguration;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link HostHandler} is responsible for all network equipments hosted on the network
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class HostHandler extends ApiConsumerHandler {
private final Logger logger = LoggerFactory.getLogger(HostHandler.class);
// We start in pull mode and switch to push after a first update
private boolean pushSubscribed = false;
public HostHandler(Thing thing) {
super(thing);
}
@Override
void initializeProperties(Map<String, String> properties) throws FreeboxException {
getManager(LanBrowserManager.class).getHost(getMac()).ifPresent(result -> {
LanHost host = result.host();
properties.put(Thing.PROPERTY_VENDOR, host.vendorName());
host.getUPnPName().ifPresent(upnpName -> properties.put(Source.UPNP.name(), upnpName));
});
}
@Override
public void dispose() {
try {
getManager(WebSocketManager.class).unregisterListener(getMac());
} catch (FreeboxException e) {
logger.warn("Error unregistering host from the websocket: {}", e.getMessage());
}
super.dispose();
}
@Override
protected void internalPoll() throws FreeboxException {
if (pushSubscribed) {
return;
}
HostIntf data = getManager(LanBrowserManager.class).getHost(getMac())
.orElseThrow(() -> new FreeboxException("Host data not found"));
updateConnectivityChannels(data.host());
logger.debug("Switching to push mode - refreshInterval will now be ignored for Connectivity data");
getManager(WebSocketManager.class).registerListener(data.host().getMac(), this);
pushSubscribed = true;
}
public void updateConnectivityChannels(LanHost host) {
updateChannelOnOff(CONNECTIVITY, REACHABLE, host.reachable());
updateChannelDateTimeState(CONNECTIVITY, LAST_SEEN, host.getLastSeen());
updateChannelString(CONNECTIVITY, IP_ADDRESS, host.getIpv4());
updateStatus(host.reachable() ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
}
public void wol() {
try {
getManager(LanBrowserManager.class).wakeOnLan(getMac(),
getConfigAs(ApiConsumerConfiguration.class).password);
} catch (FreeboxException e) {
logger.warn("Error waking up host: {}", e.getMessage());
}
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singletonList(HostActions.class);
}
}

View File

@ -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.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* The {@link KeyfobHandler} is responsible for handling everything associated to
* any Freebox Home keyfob thing type.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class KeyfobHandler extends HomeNodeHandler {
public KeyfobHandler(Thing thing) {
super(thing);
}
@Override
protected State getChannelState(HomeManager homeManager, String channelId, EndpointState state) {
String value = state.value();
if (value != null) {
switch (channelId) {
case KEYFOB_ENABLE:
return OnOffType.from(state.asBoolean());
case NODE_BATTERY:
return DecimalType.valueOf(value);
}
}
return UnDefType.NULL;
}
@Override
protected boolean executeSlotCommand(HomeManager homeManager, String channelId, Command command,
Configuration config, int intValue) throws FreeboxException {
if (KEYFOB_ENABLE.equals(channelId) && command instanceof OnOffType onOff) {
return getManager(HomeManager.class).putCommand(getClientId(), intValue, onOff);
}
return false;
}
}

View File

@ -0,0 +1,114 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.KEY_CODE;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.freeboxos.internal.action.PlayerActions;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.LanHost;
import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager;
import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Player;
import org.openhab.binding.freeboxos.internal.config.PlayerConfiguration;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import inet.ipaddr.IPAddress;
/**
* The {@link PlayerHandler} is responsible for handling everything associated to any Freebox Player thing type.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class PlayerHandler extends HostHandler {
private static final List<String> VALID_REMOTE_KEYS = Arrays.asList("red", "green", "blue", "yellow", "power",
"list", "tv", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "vol_inc", "vol_dec", "mute", "prgm_inc",
"prgm_dec", "prev", "bwd", "play", "rec", "fwd", "next", "up", "right", "down", "left", "back", "swap",
"info", "epg", "mail", "media", "help", "options", "pip", "ok", "home");
private final Logger logger = LoggerFactory.getLogger(PlayerHandler.class);
private @Nullable IPAddress ipAddress;
public PlayerHandler(Thing thing) {
super(thing);
}
@Override
void initializeProperties(Map<String, String> properties) throws FreeboxException {
super.initializeProperties(properties);
Player player = getManager(PlayerManager.class).getDevice(getClientId());
properties.put(Thing.PROPERTY_MODEL_ID, player.deviceModel().name());
}
@Override
protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
if (KEY_CODE.equals(channelId) && command instanceof StringType) {
sendKey(command.toString(), false, 1);
return true;
}
return super.internalHandleCommand(channelId, command);
}
@Override
public void updateConnectivityChannels(LanHost host) {
super.updateConnectivityChannels(host);
ipAddress = host.getIpv4();
}
public void sendKey(String key, boolean longPress, int count) {
String aKey = key.toLowerCase();
IPAddress ip = ipAddress;
if (ip == null) {
logger.warn("Player IP is unknown");
} else if (VALID_REMOTE_KEYS.contains(aKey)) {
String remoteCode = (String) getConfig().get(PlayerConfiguration.REMOTE_CODE);
if (remoteCode != null) {
try {
getManager(PlayerManager.class).sendKey(ip.toCanonicalString(), remoteCode, aKey, longPress, count);
} catch (FreeboxException e) {
logger.warn("Error sending key: {}", e.getMessage());
}
} else {
logger.warn("A remote code must be configured in the on the player thing.");
}
} else {
logger.warn("Key '{}' is not a valid key expression", key);
}
}
public void sendMultipleKeys(String keys) {
String[] keyChain = keys.split(",");
Arrays.stream(keyChain).forEach(key -> {
sendKey(key, false, 1);
});
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singletonList(PlayerActions.class);
}
}

View File

@ -0,0 +1,119 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.action.RepeaterActions;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.LanHost;
import org.openhab.binding.freeboxos.internal.api.rest.RepeaterManager;
import org.openhab.binding.freeboxos.internal.api.rest.RepeaterManager.Repeater;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link RepeaterHandler} is responsible for interface to a freebox
* pop repeater.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class RepeaterHandler extends HostHandler implements FreeDeviceIntf {
private final Logger logger = LoggerFactory.getLogger(RepeaterHandler.class);
private long uptime = -1;
private final ChannelUID eventChannelUID;
public RepeaterHandler(Thing thing) {
super(thing);
eventChannelUID = new ChannelUID(getThing().getUID(), REPEATER_MISC, BOX_EVENT);
}
@Override
void initializeProperties(Map<String, String> properties) throws FreeboxException {
super.initializeProperties(properties);
Repeater repeater = getManager(RepeaterManager.class).getDevice(getClientId());
properties.put(Thing.PROPERTY_SERIAL_NUMBER, repeater.sn());
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, repeater.firmwareVersion());
properties.put(Thing.PROPERTY_MODEL_ID, repeater.model().name());
}
@Override
protected void internalPoll() throws FreeboxException {
super.internalPoll();
if (!thing.getStatus().equals(ThingStatus.ONLINE)) {
return;
}
logger.debug("Polling Repeater status");
RepeaterManager repeaterManager = getManager(RepeaterManager.class);
Repeater repeater = repeaterManager.getDevice(getClientId());
updateChannelOnOff(REPEATER_MISC, LED, repeater.ledActivated());
updateChannelString(REPEATER_MISC, CONNECTION_STATUS, repeater.connection());
List<LanHost> hosts = repeaterManager.getRepeaterHosts(getClientId());
updateChannelDecimal(REPEATER_MISC, HOST_COUNT, hosts.size());
uptime = checkUptimeAndFirmware(repeater.getUptimeVal(), uptime, repeater.firmwareVersion());
updateChannelQuantity(REPEATER_MISC, UPTIME, uptime, Units.SECOND);
}
@Override
protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
if (ON_OFF_CLASSES.contains(command.getClass()) && LED.equals(channelId)) {
getManager(RepeaterManager.class).led(getClientId(), TRUE_COMMANDS.contains(command))
.ifPresent(repeater -> updateChannelOnOff(REPEATER_MISC, LED, repeater.ledActivated()));
}
return super.internalHandleCommand(channelId, command);
}
public void reboot() {
processReboot(() -> {
try {
getManager(RepeaterManager.class).reboot(getClientId());
} catch (FreeboxException e) {
logger.warn("Error rebooting: {}", e.getMessage());
}
});
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(RepeaterActions.class);
}
@Override
public ChannelUID getEventChannelUID() {
return eventChannelUID;
}
@Override
public void triggerChannel(ChannelUID channelUID, String event) {
super.triggerChannel(channelUID, event);
}
}

View File

@ -0,0 +1,104 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import static org.openhab.core.library.unit.Units.PERCENT;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.LcdManager;
import org.openhab.binding.freeboxos.internal.api.rest.LcdManager.Config;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link RevolutionHandler} is responsible for handling take care of revolution server specifics
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class RevolutionHandler extends ServerHandler {
private final Logger logger = LoggerFactory.getLogger(RevolutionHandler.class);
public RevolutionHandler(Thing thing) {
super(thing);
}
@Override
protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
LcdManager manager = getManager(LcdManager.class);
Config config = manager.getConfig();
switch (channelId) {
case LCD_BRIGHTNESS:
setBrightness(manager, config, command);
internalPoll();
return true;
case LCD_ORIENTATION:
setOrientation(manager, config, command);
internalPoll();
return true;
case LCD_FORCED:
setForced(manager, config, command);
internalPoll();
return true;
}
return super.internalHandleCommand(channelId, command);
}
@Override
protected void internalPoll() throws FreeboxException {
super.internalPoll();
Config config = getManager(LcdManager.class).getConfig();
updateChannelQuantity(DISPLAY, LCD_BRIGHTNESS, config.brightness(), PERCENT);
updateChannelDecimal(DISPLAY, LCD_ORIENTATION, config.orientation());
updateChannelOnOff(DISPLAY, LCD_FORCED, config.orientationForced());
}
private void setOrientation(LcdManager manager, Config config, Command command) throws FreeboxException {
if (command instanceof DecimalType) {
manager.setOrientation(((DecimalType) command).intValue());
} else {
logger.warn("Invalid command {} from channel {}", command, LCD_ORIENTATION);
}
}
private void setForced(LcdManager manager, Config config, Command command) throws FreeboxException {
if (ON_OFF_CLASSES.contains(command.getClass())) {
manager.setOrientationForced(TRUE_COMMANDS.contains(command));
} else {
logger.warn("Invalid command {} from channel {}", command, LCD_FORCED);
}
}
private void setBrightness(LcdManager manager, Config config, Command command) throws FreeboxException {
if (command instanceof IncreaseDecreaseType) {
manager.setBrightness(() -> config.brightness() + (command == IncreaseDecreaseType.INCREASE ? 1 : -1));
} else if (command instanceof OnOffType) {
manager.setBrightness(() -> command == OnOffType.ON ? 100 : 0);
} else if (command instanceof QuantityType) {
manager.setBrightness(() -> ((QuantityType<?>) command).intValue());
} else if (command instanceof DecimalType || command instanceof PercentType) {
manager.setBrightness(() -> ((DecimalType) command).intValue());
} else {
logger.warn("Invalid command {} from channel {}", command, LCD_BRIGHTNESS);
}
}
}

View File

@ -0,0 +1,215 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import static org.openhab.core.library.unit.Units.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.action.ServerActions;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.AfpManager;
import org.openhab.binding.freeboxos.internal.api.rest.AirMediaManager;
import org.openhab.binding.freeboxos.internal.api.rest.ConnectionManager;
import org.openhab.binding.freeboxos.internal.api.rest.ConnectionManager.Status;
import org.openhab.binding.freeboxos.internal.api.rest.FtpManager;
import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.Source;
import org.openhab.binding.freeboxos.internal.api.rest.LanManager;
import org.openhab.binding.freeboxos.internal.api.rest.LanManager.LanConfig;
import org.openhab.binding.freeboxos.internal.api.rest.SambaManager;
import org.openhab.binding.freeboxos.internal.api.rest.SambaManager.Samba;
import org.openhab.binding.freeboxos.internal.api.rest.SystemManager;
import org.openhab.binding.freeboxos.internal.api.rest.SystemManager.Config;
import org.openhab.binding.freeboxos.internal.api.rest.UPnPAVManager;
import org.openhab.binding.freeboxos.internal.api.rest.WifiManager;
import org.openhab.core.library.CoreItemFactory;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ServerHandler} handle common parts of Freebox bridges.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class ServerHandler extends ApiConsumerHandler implements FreeDeviceIntf {
private static final BigDecimal HUNDRED = BigDecimal.valueOf(100);
private final Logger logger = LoggerFactory.getLogger(ServerHandler.class);
private final ChannelUID eventChannelUID;
private long uptime = -1;
public ServerHandler(Thing thing) {
super(thing);
eventChannelUID = new ChannelUID(getThing().getUID(), SYS_INFO, BOX_EVENT);
}
@Override
void initializeProperties(Map<String, String> properties) throws FreeboxException {
LanConfig lanConfig = getManager(LanManager.class).getConfig();
Config config = getManager(SystemManager.class).getConfig();
properties.put(Thing.PROPERTY_SERIAL_NUMBER, config.serial());
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, config.firmwareVersion());
properties.put(Thing.PROPERTY_HARDWARE_VERSION, config.modelInfo().prettyName());
properties.put(Source.UPNP.name(), lanConfig.name());
List<Channel> channels = new ArrayList<>(getThing().getChannels());
config.sensors().forEach(sensor -> {
ChannelUID sensorId = new ChannelUID(thing.getUID(), GROUP_SENSORS, sensor.id());
channels.add(
ChannelBuilder.create(sensorId).withLabel(sensor.name()).withAcceptedItemType("Number:Temperature")
.withType(new ChannelTypeUID(BINDING_ID + ":temperature")).build());
});
config.fans().forEach(sensor -> {
ChannelUID sensorId = new ChannelUID(thing.getUID(), GROUP_FANS, sensor.id());
channels.add(ChannelBuilder.create(sensorId).withLabel(sensor.name())
.withAcceptedItemType(CoreItemFactory.NUMBER).withType(new ChannelTypeUID(BINDING_ID + ":fanspeed"))
.build());
});
updateThing(editThing().withChannels(channels).build());
}
@Override
protected void internalPoll() throws FreeboxException {
logger.debug("Polling server state...");
fetchConnectionStatus();
fetchSystemConfig();
updateChannelOnOff(ACTIONS, WIFI_STATUS, getManager(WifiManager.class).getStatus());
updateChannelOnOff(ACTIONS, AIRMEDIA_STATUS, getManager(AirMediaManager.class).getStatus());
updateChannelOnOff(ACTIONS, UPNPAV_STATUS, getManager(UPnPAVManager.class).getStatus());
Samba response = getManager(SambaManager.class).getConfig();
updateChannelOnOff(FILE_SHARING, SAMBA_FILE_STATUS, response.fileShareEnabled());
updateChannelOnOff(FILE_SHARING, SAMBA_PRINTER_STATUS, response.printShareEnabled());
updateChannelOnOff(FILE_SHARING, FTP_STATUS, getManager(FtpManager.class).getStatus());
updateChannelOnOff(FILE_SHARING, AFP_FILE_STATUS, getManager(AfpManager.class).getStatus());
}
private void fetchSystemConfig() throws FreeboxException {
Config config = getManager(SystemManager.class).getConfig();
config.sensors().forEach(s -> updateChannelQuantity(GROUP_SENSORS, s.id(), s.value(), SIUnits.CELSIUS));
config.fans().forEach(f -> updateChannelQuantity(GROUP_FANS, f.id(), f.value(), Units.RPM));
uptime = checkUptimeAndFirmware(config.uptimeVal(), uptime, config.firmwareVersion());
updateChannelQuantity(SYS_INFO, UPTIME, uptime, Units.SECOND);
LanConfig lanConfig = getManager(LanManager.class).getConfig();
updateChannelString(SYS_INFO, IP_ADDRESS, lanConfig.ip());
}
private void fetchConnectionStatus() throws FreeboxException {
Status status = getManager(ConnectionManager.class).getConfig();
updateChannelString(CONNECTION_STATUS, LINE_STATUS, status.state());
updateChannelString(CONNECTION_STATUS, LINE_TYPE, status.type());
updateChannelString(CONNECTION_STATUS, LINE_MEDIA, status.media());
updateChannelString(CONNECTION_STATUS, IP_ADDRESS, status.ipv4());
updateChannelString(CONNECTION_STATUS, IPV6_ADDRESS, status.ipv6());
updateRateBandwidth(status.rateUp(), status.bandwidthUp(), "up");
updateRateBandwidth(status.rateDown(), status.bandwidthDown(), "down");
updateChannelQuantity(CONNECTION_STATUS, BYTES_UP, new QuantityType<>(status.bytesUp(), OCTET), GIBIOCTET);
updateChannelQuantity(CONNECTION_STATUS, BYTES_DOWN, new QuantityType<>(status.bytesDown(), OCTET), GIBIOCTET);
}
private void updateRateBandwidth(long rate, long bandwidth, String orientation) {
QuantityType<?> rateUp = new QuantityType<>(rate * 8, Units.BIT_PER_SECOND);
QuantityType<?> bandwidthUp = new QuantityType<>(bandwidth, BIT_PER_SECOND);
updateChannelQuantity(CONNECTION_STATUS, RATE + "-" + orientation, rateUp, KILOBIT_PER_SECOND);
updateChannelQuantity(CONNECTION_STATUS, BW + "-" + orientation, bandwidthUp, KILOBIT_PER_SECOND);
updateChannelQuantity(CONNECTION_STATUS, PCT_BW + "-" + orientation,
!bandwidthUp.equals(QuantityType.ZERO) ? rateUp.multiply(HUNDRED).divide(bandwidthUp)
: QuantityType.ZERO,
Units.PERCENT);
}
@Override
protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
if (ON_OFF_CLASSES.contains(command.getClass())) {
boolean enable = TRUE_COMMANDS.contains(command);
switch (channelId) {
case WIFI_STATUS:
updateChannelOnOff(ACTIONS, WIFI_STATUS, getManager(WifiManager.class).setStatus(enable));
return true;
case FTP_STATUS:
updateChannelOnOff(FILE_SHARING, FTP_STATUS, getManager(FtpManager.class).setStatus(enable));
return true;
case SAMBA_FILE_STATUS:
updateChannelOnOff(FILE_SHARING, SAMBA_FILE_STATUS,
getManager(SambaManager.class).setFileShare(enable));
return true;
case SAMBA_PRINTER_STATUS:
updateChannelOnOff(FILE_SHARING, SAMBA_PRINTER_STATUS,
getManager(SambaManager.class).setPrintShare(enable));
return true;
case UPNPAV_STATUS:
updateChannelOnOff(ACTIONS, UPNPAV_STATUS, getManager(UPnPAVManager.class).setStatus(enable));
return true;
case AFP_FILE_STATUS:
updateChannelOnOff(FILE_SHARING, AFP_FILE_STATUS, getManager(AfpManager.class).setStatus(enable));
return true;
case AIRMEDIA_STATUS:
updateChannelOnOff(ACTIONS, AIRMEDIA_STATUS, getManager(AirMediaManager.class).setStatus(enable));
return true;
default:
break;
}
}
return super.internalHandleCommand(channelId, command);
}
public void reboot() {
processReboot(() -> {
try {
getManager(SystemManager.class).reboot();
} catch (FreeboxException e) {
logger.warn("Error rebooting: {}", e.getMessage());
}
});
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(ServerActions.class);
}
@Override
public ChannelUID getEventChannelUID() {
return eventChannelUID;
}
@Override
public void triggerChannel(ChannelUID channelUID, String event) {
super.triggerChannel(channelUID, event);
}
}

View File

@ -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.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Endpoint;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StopMoveType;
import org.openhab.core.library.types.UpDownType;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* The {@link ShutterHandler} is responsible for handling everything associated to any Freebox Home shutter.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class ShutterHandler extends HomeNodeHandler {
public ShutterHandler(Thing thing) {
super(thing);
}
@Override
protected void internalConfigureChannel(String channelId, Configuration conf, List<Endpoint> endpoints) {
endpoints.stream().filter(ep -> channelId.equals(SHUTTER_POSITION) && ep.name().equals(SHUTTER_STOP))
.forEach(endPoint -> conf.put(endPoint.name(), endPoint.id()));
}
@Override
protected State getChannelState(HomeManager homeManager, String channelId, EndpointState state) {
String value = state.value();
return value != null && channelId.equals(SHUTTER_POSITION) ? QuantityType.valueOf(value + " %")
: UnDefType.NULL;
}
@Override
protected boolean executeSlotCommand(HomeManager homeManager, String channelId, Command command,
Configuration config, int positionSlot) throws FreeboxException {
Integer stopSlot = getSlotId(config, SHUTTER_STOP);
if (SHUTTER_POSITION.equals(channelId) && stopSlot instanceof Integer) {
if (command instanceof UpDownType upDownCmd) {
return operateShutter(homeManager, stopSlot, positionSlot, upDownCmd == UpDownType.DOWN ? 100 : 0);
} else if (command instanceof StopMoveType stopMove && stopMove == StopMoveType.STOP) {
return operateShutter(homeManager, stopSlot, positionSlot, -1);
} else if (command instanceof Number numberCmd) {
return operateShutter(homeManager, stopSlot, positionSlot, numberCmd.intValue());
}
}
return false;
}
private boolean operateShutter(HomeManager homeManager, int stopSlot, int positionSlot, int target)
throws FreeboxException {
homeManager.putCommand(getClientId(), stopSlot, true);
if (target >= 0) {
homeManager.putCommand(getClientId(), positionSlot, target);
}
return true;
}
}

View File

@ -0,0 +1,86 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.VmManager;
import org.openhab.binding.freeboxos.internal.api.rest.VmManager.Status;
import org.openhab.binding.freeboxos.internal.api.rest.VmManager.VirtualMachine;
import org.openhab.binding.freeboxos.internal.api.rest.WebSocketManager;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link VmHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class VmHandler extends HostHandler {
private final Logger logger = LoggerFactory.getLogger(VmHandler.class);
// We start in pull mode and switch to push after a first update
private boolean pushSubscribed = false;
public VmHandler(Thing thing) {
super(thing);
}
@Override
public void dispose() {
try {
getManager(WebSocketManager.class).unregisterVm(getClientId());
} catch (FreeboxException e) {
logger.warn("Error unregistering VM from the websocket: {}", e.getMessage());
}
super.dispose();
}
@Override
protected void internalPoll() throws FreeboxException {
if (pushSubscribed) {
return;
}
super.internalPoll();
logger.debug("Polling Virtual machine status");
VirtualMachine vm = getManager(VmManager.class).getDevice(getClientId());
updateVmChannels(vm);
getManager(WebSocketManager.class).registerVm(vm.id(), this);
pushSubscribed = true;
}
public void updateVmChannels(VirtualMachine vm) {
boolean running = Status.RUNNING.equals(vm.status());
updateChannelOnOff(VM_STATUS, STATUS, running);
updateChannelOnOff(CONNECTIVITY, REACHABLE, running);
updateStatus(running ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
}
@Override
protected boolean internalHandleCommand(String channelId, Command command) throws FreeboxException {
if (STATUS.equals(channelId) && command instanceof OnOffType) {
getManager(VmManager.class).power(getClientId(), OnOffType.ON.equals(command));
return true;
}
return super.internalHandleCommand(channelId, command);
}
}

View File

@ -0,0 +1,93 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.APManager;
import org.openhab.binding.freeboxos.internal.api.rest.APManager.LanAccessPoint;
import org.openhab.binding.freeboxos.internal.api.rest.APManager.Station;
import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.LanHost;
import org.openhab.binding.freeboxos.internal.api.rest.RepeaterManager;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.UnDefType;
/**
* The {@link WifiStationHandler} is responsible for handling everything associated to
* any Freebox thing types except the bridge thing type.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class WifiStationHandler extends HostHandler {
private static final String SERVER_HOST = "Server";
public WifiStationHandler(Thing thing) {
super(thing);
}
@Override
protected void internalPoll() throws FreeboxException {
super.internalPoll();
// Search if the wifi-host is hosted on server access-points
Optional<Station> station = getManager(APManager.class).getStation(getMac());
if (station.isPresent()) {
Station data = station.get();
updateChannelDateTimeState(CONNECTIVITY, LAST_SEEN, data.getLastSeen());
updateChannelString(GROUP_WIFI, WIFI_HOST, SERVER_HOST);
updateWifiStationChannels(data.signal(), data.getSsid(), data.rxRate(), data.txRate());
return;
}
// Search if it is hosted by a repeater
Optional<LanHost> wifiHost = getManager(RepeaterManager.class).getHost(getMac());
if (wifiHost.isPresent()) {
updateChannelDateTimeState(CONNECTIVITY, LAST_SEEN, wifiHost.get().getLastSeen());
LanAccessPoint lanAp = wifiHost.get().accessPoint();
if (lanAp != null) {
updateChannelString(GROUP_WIFI, WIFI_HOST, "%s-%s".formatted(lanAp.type(), lanAp.uid()));
updateWifiStationChannels(lanAp.getSignal(), lanAp.getSsid(), lanAp.rxRate(), lanAp.txRate());
return;
}
}
// Not found a wifi repeater/host, so update all wifi channels to NULL
getThing().getChannelsOfGroup(GROUP_WIFI).stream().map(Channel::getUID).filter(uid -> isLinked(uid))
.forEach(uid -> updateState(uid, UnDefType.NULL));
}
private void updateWifiStationChannels(int rssi, @Nullable String ssid, long rxRate, long txRate) {
updateChannelString(GROUP_WIFI, SSID, ssid);
updateChannelQuantity(GROUP_WIFI, RSSI, rssi <= 0 ? new QuantityType<>(rssi, Units.DECIBEL_MILLIWATTS) : null);
updateChannelDecimal(GROUP_WIFI, WIFI_QUALITY, rssi <= 0 ? toQoS(rssi) : null);
updateRateChannel(RATE + "-down", rxRate);
updateRateChannel(RATE + "-up", txRate);
}
private void updateRateChannel(String channel, long rate) {
QuantityType<?> qtty = rate != -1 ? new QuantityType<>(rate * 8, Units.BIT_PER_SECOND) : null;
updateChannelQuantity(GROUP_WIFI, channel, qtty);
}
private int toQoS(int rssi) {
return rssi > -50 ? 4 : rssi > -60 ? 3 : rssi > -70 ? 2 : rssi > -85 ? 1 : 0;
}
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon:addon id="freeboxos" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
<type>binding</type>
<name>Freebox OS Binding</name>
<description>The Freebox OS binding integrates Free equipments in your home automation.</description>
<connection>local</connection>
<countries>fr,it</countries>
<config-description>
<parameter name="timeout" type="integer" required="false" min="1" unit="s">
<label>Timeout</label>
<description>The timeout for reading from the API in seconds.</description>
<default>8</default>
</parameter>
<parameter name="callbackUrl" type="text" required="false">
<label>Callback URL</label>
<description>URL to use for playing notification sounds hosted by openHAB, e.g. 'http://192.168.0.2:8080'</description>
</parameter>
</config-description>
</addon:addon>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="bridge-type:freeboxos:api">
<parameter name="apiDomain" type="text">
<label>Freebox Server Address</label>
<context>network-address</context>
<description>The domain to use in place of hardcoded Freebox ip</description>
<default>mafreebox.freebox.fr</default>
</parameter>
<parameter name="appToken" type="text" required="false">
<label>Application Token</label>
<context>password</context>
<description>Token generated by the Freebox server</description>
</parameter>
<parameter name="discoverNetDevice" type="boolean">
<label>Network Device Discovery</label>
<description>Enable the discovery of network device things</description>
<default>false</default>
</parameter>
<parameter name="httpsAvailable" type="boolean">
<label>HTTPS Available</label>
<description>Tells if https has been configured on the Freebox</description>
<advanced>true</advanced>
<default>false</default>
</parameter>
<parameter name="httpsPort" type="integer">
<label>HTTPS port</label>
<description>Port to use for remote https access to the Freebox Api</description>
<advanced>true</advanced>
<default>15682</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:freeboxos:home-node">
<parameter name="id" type="integer" required="true">
<label>ID</label>
<description>Id of the Home Node</description>
<default>1</default>
</parameter>
<parameter name="refreshInterval" type="integer" min="1" unit="s">
<label>Refresh Interval</label>
<description>The refresh interval in seconds which is used to poll the Node</description>
<default>30</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:freeboxos:host">
<parameter name="refreshInterval" type="integer" min="1" unit="s">
<label>Refresh Interval</label>
<description>The refresh interval in seconds which is used to poll given device</description>
<default>30</default>
</parameter>
<parameter name="macAddress" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
<label>MAC Address</label>
<description>The MAC address of the network device</description>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:freeboxos:phone">
<parameter name="refreshInterval" type="integer" min="1" unit="s">
<label>State Refresh Interval</label>
<description>The refresh interval in seconds which is used to poll for phone state.</description>
<default>30</default>
</parameter>
<parameter name="id" type="integer">
<label>ID</label>
<description>Id of the phone line</description>
<default>1</default>
</parameter>
</config-description>
<config-description uri="thing-type:freeboxos:call">
<parameter name="refreshInterval" type="integer" min="1" unit="s">
<label>State Refresh Interval</label>
<description>The refresh interval in seconds which is used to poll for phone state.</description>
<default>2</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:freeboxos:player">
<parameter name="macAddress" type="text" required="true" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})">
<label>MAC Address</label>
<description>The MAC address of the player device</description>
</parameter>
<parameter name="id" type="integer">
<label>ID</label>
<description>Id of the player</description>
<default>1</default>
</parameter>
<parameter name="port" type="integer">
<label>Player port</label>
<default>24322</default>
<advanced>true</advanced>
</parameter>
<parameter name="password" type="text" required="false">
<context>password</context>
<label>Password</label>
<description>AirPlay password</description>
<advanced>true</advanced>
</parameter>
<parameter name="remoteCode" type="text" required="false">
<label>Remote Code</label>
<description>Code associated to remote control</description>
<advanced>true</advanced>
</parameter>
<parameter name="acceptAllMp3" type="boolean" required="false">
<label>Accept All MP3</label>
<description>Accept any bitrate for MP3 audio or only bitrates greater than 64 kbps</description>
<default>true</default>
<advanced>true</advanced>
</parameter>
<parameter name="refreshInterval" type="integer" min="1" unit="s">
<label>Refresh Interval</label>
<description>The refresh interval in seconds which is used to poll the player</description>
<default>30</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:freeboxos:repeater">
<parameter name="refreshInterval" type="integer" min="1" unit="s">
<label>Refresh Interval</label>
<description>The refresh interval in seconds which is used to poll the repeater</description>
<default>30</default>
</parameter>
<parameter name="macAddress" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
<label>MAC Address</label>
<description>The MAC address of the network device</description>
</parameter>
<parameter name="id" type="integer" required="true">
<label>ID</label>
<description>Id of the repeater</description>
<default>1</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:freeboxos:server">
<parameter name="refreshInterval" type="integer" min="1" unit="s">
<label>Refresh Interval</label>
<description>The refresh interval in seconds which is used to poll given Freebox Server</description>
<default>30</default>
</parameter>
<parameter name="macAddress" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
<label>MAC Address</label>
<description>The MAC address of the network device</description>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:freeboxos:vm">
<parameter name="refreshInterval" type="integer" min="1" unit="s">
<label>Refresh Interval</label>
<description>The refresh interval in seconds which is used to poll given virtual machine</description>
<default>30</default>
</parameter>
<parameter name="macAddress" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
<label>MAC Address</label>
<description>The MAC address of the network device</description>
</parameter>
<parameter name="id" type="integer" required="true">
<label>ID</label>
<description>Id of the Virtual Machine</description>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -0,0 +1,354 @@
# add-on
addon.freeboxos.name = Freebox OS Binding
addon.freeboxos.description = The Freebox OS binding integrates Free equipments in your home automation.
# add-on config
addon.config.freeboxos.callbackUrl.label = Callback URL
addon.config.freeboxos.callbackUrl.description = URL to use for playing notification sounds hosted by openHAB, e.g. 'http://192.168.0.2:8080'
addon.config.freeboxos.timeout.label = Timeout
addon.config.freeboxos.timeout.description = The timeout for reading from the API in seconds.
# thing types
thing-type.freeboxos.active-player.label = Freebox Player
thing-type.freeboxos.active-player.description = The player is the device connected to your TV with API capabilities
thing-type.freeboxos.alarm.label = Freebox Alarm
thing-type.freeboxos.alarm.description = The Alarm system configured in your Freebox Delta Server
thing-type.freeboxos.alarm.channel.sound.label = Bips Volume
thing-type.freeboxos.alarm.channel.timeout1.label = Alarm Activation Duration
thing-type.freeboxos.alarm.channel.timeout2.label = Safe Zone Alert Timeout
thing-type.freeboxos.alarm.channel.timeout3.label = Alert Duration
thing-type.freeboxos.alarm.channel.volume.label = Alarm Volume
thing-type.freeboxos.api.label = Freebox OS Api
thing-type.freeboxos.api.description = Bridge between hosts and the API rest service
thing-type.freeboxos.basic-shutter.label = Freebox Home Basic Shutter
thing-type.freeboxos.basic-shutter.description = The Basic Shutter (UP,DOWN,STOP) configured in your Freebox Delta Server
thing-type.freeboxos.call.label = Calls
thing-type.freeboxos.call.description = Provides various informations regarding the phone calls
thing-type.freeboxos.dect.label = DECT
thing-type.freeboxos.dect.description = Provides various informations regarding the DECT state and configuration
thing-type.freeboxos.dect.channel.gain-rx.label = Gain RX
thing-type.freeboxos.dect.channel.gain-tx.label = Gain TX
thing-type.freeboxos.delta.label = Freebox Delta
thing-type.freeboxos.delta.description = Provides various informations regarding the status of the Freebox Delta Server
thing-type.freeboxos.freeplug.label = Freeplug
thing-type.freeboxos.freeplug.description = Ethernet / CPL gateway
thing-type.freeboxos.freeplug.channel.last-seen.label = Last Activity
thing-type.freeboxos.freeplug.channel.rate-down.label = Rx Rate
thing-type.freeboxos.freeplug.channel.rate-down.description = Current RX rate
thing-type.freeboxos.freeplug.channel.rate-up.label = Tx Rate
thing-type.freeboxos.freeplug.channel.rate-up.description = Current TX Rate
thing-type.freeboxos.fxs.label = Landline
thing-type.freeboxos.fxs.description = Provides various informations regarding the landline state
thing-type.freeboxos.host.label = Network Device
thing-type.freeboxos.host.description = Provides network device reachability
thing-type.freeboxos.kfb.label = Freebox Keyfob
thing-type.freeboxos.kfb.description = A keyfob configured for your Freebox Security system
thing-type.freeboxos.player.label = Freebox Player
thing-type.freeboxos.player.description = The player is the device connected to your TV
thing-type.freeboxos.repeater.label = Wifi Repeater
thing-type.freeboxos.repeater.description = Provides informations and control over a Wifi Repeater
thing-type.freeboxos.revolution.label = Freebox Revolution
thing-type.freeboxos.revolution.description = Provides various informations regarding the status of the Freebox Revolution Server
thing-type.freeboxos.shutter.label = Freebox Home Shutter
thing-type.freeboxos.shutter.description = The Shutter configured in your Freebox Delta Server
thing-type.freeboxos.vm.label = Virtual Machine
thing-type.freeboxos.vm.description = Provides informations and control over virtual machine hosted on the server
thing-type.freeboxos.wifihost.label = Wifi Device
thing-type.freeboxos.wifihost.description = Provides Wifi device reachability
# thing types config
bridge-type.config.freeboxos.api.apiDomain.label = Freebox Server Address
bridge-type.config.freeboxos.api.apiDomain.description = The domain to use in place of hardcoded Freebox ip
bridge-type.config.freeboxos.api.appToken.label = Application Token
bridge-type.config.freeboxos.api.appToken.description = Token generated by the Freebox server
bridge-type.config.freeboxos.api.discoverNetDevice.label = Network Device Discovery
bridge-type.config.freeboxos.api.discoverNetDevice.description = Enable the discovery of network device things
bridge-type.config.freeboxos.api.httpsAvailable.label = HTTPS Available
bridge-type.config.freeboxos.api.httpsAvailable.description = Tells if https has been configured on the Freebox
bridge-type.config.freeboxos.api.httpsPort.label = HTTPS port
bridge-type.config.freeboxos.api.httpsPort.description = Port to use for remote https access to the Freebox Api
thing-type.config.freeboxos.call.refreshInterval.label = State Refresh Interval
thing-type.config.freeboxos.call.refreshInterval.description = The refresh interval in seconds which is used to poll for phone state.
thing-type.config.freeboxos.home-node.id.label = ID
thing-type.config.freeboxos.home-node.id.description = Id of the Home Node
thing-type.config.freeboxos.home-node.refreshInterval.label = Refresh Interval
thing-type.config.freeboxos.home-node.refreshInterval.description = The refresh interval in seconds which is used to poll the Node
thing-type.config.freeboxos.host.macAddress.label = MAC Address
thing-type.config.freeboxos.host.macAddress.description = The MAC address of the network device
thing-type.config.freeboxos.host.refreshInterval.label = Refresh Interval
thing-type.config.freeboxos.host.refreshInterval.description = The refresh interval in seconds which is used to poll given device
thing-type.config.freeboxos.phone.id.label = ID
thing-type.config.freeboxos.phone.id.description = Id of the phone line
thing-type.config.freeboxos.phone.refreshInterval.label = State Refresh Interval
thing-type.config.freeboxos.phone.refreshInterval.description = The refresh interval in seconds which is used to poll for phone state.
thing-type.config.freeboxos.player.acceptAllMp3.label = Accept All MP3
thing-type.config.freeboxos.player.acceptAllMp3.description = Accept any bitrate for MP3 audio or only bitrates greater than 64 kbps
thing-type.config.freeboxos.player.id.label = ID
thing-type.config.freeboxos.player.id.description = Id of the player
thing-type.config.freeboxos.player.macAddress.label = MAC Address
thing-type.config.freeboxos.player.macAddress.description = The MAC address of the player device
thing-type.config.freeboxos.player.password.label = Password
thing-type.config.freeboxos.player.password.description = AirPlay password
thing-type.config.freeboxos.player.port.label = Player port
thing-type.config.freeboxos.player.refreshInterval.label = Refresh Interval
thing-type.config.freeboxos.player.refreshInterval.description = The refresh interval in seconds which is used to poll the player
thing-type.config.freeboxos.player.remoteCode.label = Remote Code
thing-type.config.freeboxos.player.remoteCode.description = Code associated to remote control
thing-type.config.freeboxos.repeater.id.label = ID
thing-type.config.freeboxos.repeater.id.description = Id of the repeater
thing-type.config.freeboxos.repeater.macAddress.label = MAC Address
thing-type.config.freeboxos.repeater.macAddress.description = The MAC address of the network device
thing-type.config.freeboxos.repeater.refreshInterval.label = Refresh Interval
thing-type.config.freeboxos.repeater.refreshInterval.description = The refresh interval in seconds which is used to poll the repeater
thing-type.config.freeboxos.server.macAddress.label = MAC Address
thing-type.config.freeboxos.server.macAddress.description = The MAC address of the network device
thing-type.config.freeboxos.server.refreshInterval.label = Refresh Interval
thing-type.config.freeboxos.server.refreshInterval.description = The refresh interval in seconds which is used to poll given Freebox Server
thing-type.config.freeboxos.vm.id.label = ID
thing-type.config.freeboxos.vm.id.description = Id of the Virtual Machine
thing-type.config.freeboxos.vm.macAddress.label = MAC Address
thing-type.config.freeboxos.vm.macAddress.description = The MAC address of the network device
thing-type.config.freeboxos.vm.refreshInterval.label = Refresh Interval
thing-type.config.freeboxos.vm.refreshInterval.description = The refresh interval in seconds which is used to poll given virtual machine
# channel group types
channel-group-type.freeboxos.accepted.label = Accepted Call
channel-group-type.freeboxos.accepted.description = The last accepted phone call
channel-group-type.freeboxos.accepted.channel.duration.label = Incoming Call Duration
channel-group-type.freeboxos.accepted.channel.name.label = Accepted Caller
channel-group-type.freeboxos.accepted.channel.name.description = Caller name
channel-group-type.freeboxos.accepted.channel.number.label = Calling Number
channel-group-type.freeboxos.accepted.channel.number.description = Caller phone number
channel-group-type.freeboxos.accepted.channel.timestamp.label = Incoming Call Timestamp
channel-group-type.freeboxos.actions.label = Server Settings
channel-group-type.freeboxos.connection-status.label = Connection Status Details
channel-group-type.freeboxos.connection-status.channel.bandwidth-down.label = Bandwidth Down
channel-group-type.freeboxos.connection-status.channel.bandwidth-down.description = Raw value of the download bandwidth currently used
channel-group-type.freeboxos.connection-status.channel.bandwidth-up.label = Bandwidth Up
channel-group-type.freeboxos.connection-status.channel.bandwidth-up.description = Raw value of the upload bandwidth currently used
channel-group-type.freeboxos.connection-status.channel.bandwidth-usage-down.label = Bandwidth Usage Down
channel-group-type.freeboxos.connection-status.channel.bandwidth-usage-down.description = Portion of the download bandwidth currently used
channel-group-type.freeboxos.connection-status.channel.bandwidth-usage-up.label = Bandwidth Usage Up
channel-group-type.freeboxos.connection-status.channel.bandwidth-usage-up.description = Portion of the upload bandwidth currently used
channel-group-type.freeboxos.connection-status.channel.bytes-down.label = Downloaded
channel-group-type.freeboxos.connection-status.channel.bytes-down.description = Total data downloaded since last restart
channel-group-type.freeboxos.connection-status.channel.bytes-up.label = Uploaded
channel-group-type.freeboxos.connection-status.channel.bytes-up.description = Total data uploaded since last restart
channel-group-type.freeboxos.connection-status.channel.ip-address.label = Public IPv4
channel-group-type.freeboxos.connection-status.channel.ip-address.description = Public IPv4 Address of the Freebox (this field is only available when connection state is up)
channel-group-type.freeboxos.connection-status.channel.ipv6-address.label = Public IPv6
channel-group-type.freeboxos.connection-status.channel.ipv6-address.description = Public IPv6 Address of the Freebox (this field is only available when connection state is up)
channel-group-type.freeboxos.connection-status.channel.rate-down.label = Download Rate
channel-group-type.freeboxos.connection-status.channel.rate-down.description = Current download rate
channel-group-type.freeboxos.connection-status.channel.rate-up.label = Upload Rate
channel-group-type.freeboxos.connection-status.channel.rate-up.description = Current upload rate
channel-group-type.freeboxos.connectivity.label = Network Connectivity
channel-group-type.freeboxos.connectivity.channel.ip-address.label = IP Address
channel-group-type.freeboxos.connectivity.channel.ip-address.description = IPv4 Address of the host
channel-group-type.freeboxos.connectivity.channel.last-seen.label = Last Activity
channel-group-type.freeboxos.display.label = Front Display Panel
channel-group-type.freeboxos.fans.label = Fans
channel-group-type.freeboxos.file-sharing.label = File Sharing
channel-group-type.freeboxos.incoming.label = Incoming Call
channel-group-type.freeboxos.incoming.description = Currently presented phone call
channel-group-type.freeboxos.incoming.channel.name.label = Incoming Caller
channel-group-type.freeboxos.incoming.channel.name.description = Caller name
channel-group-type.freeboxos.incoming.channel.number.label = Calling Number
channel-group-type.freeboxos.incoming.channel.number.description = Caller phone number
channel-group-type.freeboxos.incoming.channel.timestamp.label = Call Timestamp
channel-group-type.freeboxos.missed.label = Missed Call
channel-group-type.freeboxos.missed.description = The last missed phone call
channel-group-type.freeboxos.missed.channel.name.label = Missed Caller
channel-group-type.freeboxos.missed.channel.name.description = Caller name
channel-group-type.freeboxos.missed.channel.number.label = Missed Calling Number
channel-group-type.freeboxos.missed.channel.number.description = Caller phone number
channel-group-type.freeboxos.missed.channel.timestamp.label = Missed Call Timestamp
channel-group-type.freeboxos.outgoing.label = Outgoing Call
channel-group-type.freeboxos.outgoing.description = The last outgoing phone call
channel-group-type.freeboxos.outgoing.channel.duration.label = Outgoing Call Duration
channel-group-type.freeboxos.outgoing.channel.name.label = Called Name
channel-group-type.freeboxos.outgoing.channel.name.description = Name, if known, of the called person
channel-group-type.freeboxos.outgoing.channel.number.label = Called Number
channel-group-type.freeboxos.outgoing.channel.number.description = Called phone number
channel-group-type.freeboxos.outgoing.channel.timestamp.label = Outgoing Call Timestamp
channel-group-type.freeboxos.player-actions.label = Player Actions
channel-group-type.freeboxos.player-status.label = Player Status
channel-group-type.freeboxos.player-sysinfo.label = System Informations
channel-group-type.freeboxos.repeater-misc.label = Repeater Settings
channel-group-type.freeboxos.repeater-misc.channel.box-event.label = Repeater Event
channel-group-type.freeboxos.sensors.label = System Sensors
channel-group-type.freeboxos.sysinfo.label = System Informations
channel-group-type.freeboxos.sysinfo.channel.ip-address.label = Internal IP
channel-group-type.freeboxos.sysinfo.channel.ip-address.description = Internal IPv4 Address of the host
channel-group-type.freeboxos.vmstatus.label = VM Status
channel-group-type.freeboxos.wifi.label = Wifi Related Information
channel-group-type.freeboxos.wifi.channel.rate-down.label = Rx Rate
channel-group-type.freeboxos.wifi.channel.rate-down.description = Current RX rate
channel-group-type.freeboxos.wifi.channel.rate-up.label = Tx Rate
channel-group-type.freeboxos.wifi.channel.rate-up.description = Current TX Rate
# channel types
channel-type.freeboxos.afp-file-status.label = Mac OS File Sharing
channel-type.freeboxos.afp-file-status.description = Status of Mac OS File Sharing
channel-type.freeboxos.airmedia-status.label = Air Media Enabled
channel-type.freeboxos.airmedia-status.description = Indicates whether Air Media is enabled
channel-type.freeboxos.alarm-pin.label = PIN Code
channel-type.freeboxos.alarm-timeout.label = Alarm Duration
channel-type.freeboxos.alarm-volume.label = Alarm Volume
channel-type.freeboxos.alternate-ring.label = Alternating Ring
channel-type.freeboxos.bandwidth-usage.label = Bandwidth Usage
channel-type.freeboxos.bandwidth-usage.description = Current bandwidth usage
channel-type.freeboxos.bandwidth.label = Bandwidth
channel-type.freeboxos.bandwidth.description = Available bandwidth
channel-type.freeboxos.basic-shutter.label = Shutter
channel-type.freeboxos.basic-shutter.description = Shutter command
channel-type.freeboxos.box-event.label = Server Event
channel-type.freeboxos.box-event.description = Triggers when an event related to the Freebox server has been detected
channel-type.freeboxos.connection-status.label = Connection
channel-type.freeboxos.connection-status.description = Connection Status
channel-type.freeboxos.dect-active.label = DECT Enabled
channel-type.freeboxos.dect-active.description = Activates / stops the integrated DECT base
channel-type.freeboxos.duration.label = Duration
channel-type.freeboxos.duration.description = Call duration in seconds
channel-type.freeboxos.fanspeed.label = Fan Speed
channel-type.freeboxos.fanspeed.description = Actual measured rotation speed of the fan
channel-type.freeboxos.ftp-status.label = FTP Server Enabled
channel-type.freeboxos.ftp-status.description = Indicates whether the FTP server is enabled
channel-type.freeboxos.gain.label = Gain
channel-type.freeboxos.hardware-status.label = Hardware Status
channel-type.freeboxos.hardware-status.description = Hardware status of the phone line
channel-type.freeboxos.host-count.label = Host Count
channel-type.freeboxos.host-count.description = Number of hosts connected to the device
channel-type.freeboxos.ip-address.label = IP Address
channel-type.freeboxos.ip-address.description = IP address of the client
channel-type.freeboxos.key-code.label = Remote Key Code
channel-type.freeboxos.key-code.description = Simulates pushing a remote control button
channel-type.freeboxos.key-code.state.option.red = Red
channel-type.freeboxos.key-code.state.option.green = Green
channel-type.freeboxos.key-code.state.option.blue = Blue
channel-type.freeboxos.key-code.state.option.yellow = Yellow
channel-type.freeboxos.key-code.state.option.power = On/Off
channel-type.freeboxos.key-code.state.option.list = List
channel-type.freeboxos.key-code.state.option.tv = TV
channel-type.freeboxos.key-code.state.option.0 = 0
channel-type.freeboxos.key-code.state.option.1 = 1
channel-type.freeboxos.key-code.state.option.2 = 2
channel-type.freeboxos.key-code.state.option.3 = 3
channel-type.freeboxos.key-code.state.option.4 = 4
channel-type.freeboxos.key-code.state.option.5 = 5
channel-type.freeboxos.key-code.state.option.6 = 6
channel-type.freeboxos.key-code.state.option.7 = 7
channel-type.freeboxos.key-code.state.option.8 = 8
channel-type.freeboxos.key-code.state.option.9 = 9
channel-type.freeboxos.key-code.state.option.vol_inc = Volume Up
channel-type.freeboxos.key-code.state.option.vol_dec = Volume Down
channel-type.freeboxos.key-code.state.option.mute = Mute
channel-type.freeboxos.key-code.state.option.prgm_inc = Prog Up
channel-type.freeboxos.key-code.state.option.prgm_dec = Prog Down
channel-type.freeboxos.key-code.state.option.prev = Previous
channel-type.freeboxos.key-code.state.option.bwd = Backward
channel-type.freeboxos.key-code.state.option.play = Play/Pause
channel-type.freeboxos.key-code.state.option.rec = Record
channel-type.freeboxos.key-code.state.option.fwd = Forward
channel-type.freeboxos.key-code.state.option.next = Next
channel-type.freeboxos.key-code.state.option.up = Up
channel-type.freeboxos.key-code.state.option.right = Right
channel-type.freeboxos.key-code.state.option.down = Down
channel-type.freeboxos.key-code.state.option.left = Left
channel-type.freeboxos.key-code.state.option.back = Back
channel-type.freeboxos.key-code.state.option.swap = Swap
channel-type.freeboxos.key-code.state.option.info = Info
channel-type.freeboxos.key-code.state.option.epg = EPG
channel-type.freeboxos.key-code.state.option.mail = Mail
channel-type.freeboxos.key-code.state.option.media = Media
channel-type.freeboxos.key-code.state.option.help = Help
channel-type.freeboxos.key-code.state.option.options = Options
channel-type.freeboxos.key-code.state.option.pip = PIP
channel-type.freeboxos.key-code.state.option.ok = OK
channel-type.freeboxos.key-code.state.option.home = Home
channel-type.freeboxos.keyfob-enable.label = Keyfob Enabled
channel-type.freeboxos.keyfob-enable.description = Activates / deactivates the keyfob
channel-type.freeboxos.lcd-brightness.label = Screen Brightness
channel-type.freeboxos.lcd-brightness.description = Brightness level of the screen in percent
channel-type.freeboxos.lcd-forced.label = Forced Orientation
channel-type.freeboxos.lcd-forced.description = Indicates whether the screen orientation is forced
channel-type.freeboxos.lcd-orientation.label = Screen Orientation
channel-type.freeboxos.lcd-orientation.description = Screen Orientation in degrees
channel-type.freeboxos.lcd-orientation.state.option.0 = Horizontal
channel-type.freeboxos.lcd-orientation.state.option.90 = Turned left
channel-type.freeboxos.lcd-orientation.state.option.180 = Reversed
channel-type.freeboxos.lcd-orientation.state.option.270 = Turned right
channel-type.freeboxos.led.label = Led Activated
channel-type.freeboxos.led.description = Led indicator status
channel-type.freeboxos.line-media.label = Line Media
channel-type.freeboxos.line-media.description = Media of network line connection
channel-type.freeboxos.line-media.state.option.FTTH = FTTH
channel-type.freeboxos.line-media.state.option.ETHERNET = Ethernet
channel-type.freeboxos.line-media.state.option.XDSL = xDSL
channel-type.freeboxos.line-media.state.option.BACKUP_4G = Internet Backup
channel-type.freeboxos.line-status.label = Line Status
channel-type.freeboxos.line-status.description = Status of network line connection
channel-type.freeboxos.line-status.state.option.GOING_UP = Connection is initializing
channel-type.freeboxos.line-status.state.option.UP = Connection is active
channel-type.freeboxos.line-status.state.option.GOING_DOWN = Connection is about to become inactive
channel-type.freeboxos.line-status.state.option.DOWN = Connection is inactive
channel-type.freeboxos.line-type.label = Line Type
channel-type.freeboxos.line-type.description = Type of network line connection
channel-type.freeboxos.line-type.state.option.ETHERNET = FTTH/ethernet
channel-type.freeboxos.line-type.state.option.RFC2684 = xDSL (unbundled)
channel-type.freeboxos.line-type.state.option.PPPOATM = xDSL
channel-type.freeboxos.name.label = Name
channel-type.freeboxos.name.description = Called name for outgoing calls. Caller name for incoming calls
channel-type.freeboxos.number.label = Incoming Call
channel-type.freeboxos.number.description = Details about call
channel-type.freeboxos.onhook.label = On Hook
channel-type.freeboxos.onhook.description = Indicates whether the phone is on hook
channel-type.freeboxos.package.label = Active Package
channel-type.freeboxos.package.description = Name of the package currently active on the player
channel-type.freeboxos.phone-event.label = Phone Event
channel-type.freeboxos.phone-event.description = Triggers when an event related to the phone has been detected
channel-type.freeboxos.phone-number.label = Phone Number
channel-type.freeboxos.player-status.label = Player Status
channel-type.freeboxos.player-status.description = Status of the Freebox TV player
channel-type.freeboxos.reachable.label = Reachable
channel-type.freeboxos.reachable.description = Indicates if the network device is reachable or not
channel-type.freeboxos.ringing.label = Ringing
channel-type.freeboxos.ringing.description = Is the phone ringing
channel-type.freeboxos.rssi.label = RSSI
channel-type.freeboxos.rssi.description = Received signal strength indicator
channel-type.freeboxos.samba-file-status.label = Windows File Sharing
channel-type.freeboxos.samba-file-status.description = Status of Windows File Sharing (Samba)
channel-type.freeboxos.samba-printer-status.label = Windows Printer Sharing
channel-type.freeboxos.samba-printer-status.description = Status of Windows Printer Sharing
channel-type.freeboxos.shutter.label = Shutter Position
channel-type.freeboxos.shutter.description = Read / Write position of the shutter
channel-type.freeboxos.ssid.label = SSID
channel-type.freeboxos.status.label = VM Status
channel-type.freeboxos.telephony-service.label = Telephony Service
channel-type.freeboxos.telephony-service.description = Status of the telephony service
channel-type.freeboxos.temperature.label = Temperature
channel-type.freeboxos.temperature.description = Actual measured temperature of the sensor
channel-type.freeboxos.timestamp.label = Timestamp
channel-type.freeboxos.transfer-bytes.label = Transfered Bytes
channel-type.freeboxos.transfer-bytes.description = Total data transfered since last connection
channel-type.freeboxos.transfer-rate-bps.label = Transfer Rate
channel-type.freeboxos.transfer-rate-bps.description = Current transfer rate
channel-type.freeboxos.transfer-rate.label = Transfer Rate
channel-type.freeboxos.transfer-rate.description = Current transfer rate
channel-type.freeboxos.upnpav-status.label = UPnP AV Enabled
channel-type.freeboxos.upnpav-status.description = Indicates whether UPnP AV is enabled
channel-type.freeboxos.uptime.label = Uptime
channel-type.freeboxos.uptime.description = Time since last reboot of the equipment
channel-type.freeboxos.wifi-host.label = Access Point
channel-type.freeboxos.wifi-status.label = Wifi Enabled
channel-type.freeboxos.wifi-status.description = Indicates whether the wifi network is enabled
# messages
info-conf-pending = Please accept pairing request directly on your freebox

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="freeboxos"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="api">
<label>Freebox OS Api</label>
<description>Bridge between hosts and the API rest service</description>
<representation-property>apiDomain</representation-property>
<config-description-ref uri="bridge-type:freeboxos:api"/>
</bridge-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,465 @@
<?xml version="1.0" encoding="UTF-8" ?>
<thing:thing-descriptions bindingId="freeboxos"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-type id="lcd-brightness" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Screen Brightness</label>
<description>Brightness level of the screen in percent</description>
<category>DimmableLight</category>
<state pattern="%d %unit%" min="0" max="100"/>
</channel-type>
<channel-type id="lcd-orientation">
<item-type>Number</item-type>
<label>Screen Orientation</label>
<description>Screen Orientation in degrees</description>
<state pattern="%d °">
<options>
<option value="0">Horizontal</option>
<option value="90">Turned left</option>
<option value="180">Reversed</option>
<option value="270">Turned right</option>
</options>
</state>
</channel-type>
<channel-type id="rssi">
<item-type>Number:Power</item-type>
<label>RSSI</label>
<description>Received signal strength indicator</description>
<category>QualityOfService</category>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ssid" advanced="true">
<item-type>String</item-type>
<label>SSID</label>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="wifi-host" advanced="true">
<item-type>String</item-type>
<label>Access Point</label>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="lcd-forced" advanced="true">
<item-type>Switch</item-type>
<label>Forced Orientation</label>
<description>Indicates whether the screen orientation is forced</description>
<category>Switch</category>
</channel-type>
<channel-type id="dect-active">
<item-type>Switch</item-type>
<label>DECT Enabled</label>
<description>Activates / stops the integrated DECT base</description>
<category>Switch</category>
</channel-type>
<channel-type id="alternate-ring">
<item-type>Switch</item-type>
<label>Alternating Ring</label>
<category>Switch</category>
</channel-type>
<channel-type id="temperature" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Actual measured temperature of the sensor</description>
<category>Temperature</category>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="fanspeed" advanced="true">
<item-type>Number:Frequency</item-type>
<label>Fan Speed</label>
<description>Actual measured rotation speed of the fan</description>
<category>Fan</category>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="samba-file-status" advanced="true">
<item-type>Switch</item-type>
<label>Windows File Sharing</label>
<description>Status of Windows File Sharing (Samba)</description>
<category>Switch</category>
</channel-type>
<channel-type id="afp-file-status" advanced="true">
<item-type>Switch</item-type>
<label>Mac OS File Sharing</label>
<description>Status of Mac OS File Sharing</description>
<category>Switch</category>
</channel-type>
<channel-type id="samba-printer-status" advanced="true">
<item-type>Switch</item-type>
<label>Windows Printer Sharing</label>
<description>Status of Windows Printer Sharing</description>
<category>Switch</category>
</channel-type>
<channel-type id="bandwidth-usage">
<item-type>Number:Dimensionless</item-type>
<label>Bandwidth Usage</label>
<description>Current bandwidth usage</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="transfer-rate">
<item-type>Number:DataTransferRate</item-type>
<label>Transfer Rate</label>
<description>Current transfer rate</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="transfer-rate-bps" advanced="true">
<item-type>Number:DataTransferRate</item-type>
<label>Transfer Rate</label>
<description>Current transfer rate</description>
<state readOnly="true" pattern="%.2f bit/s"/>
</channel-type>
<channel-type id="transfer-bytes" advanced="true">
<item-type>Number:DataAmount</item-type>
<label>Transfered Bytes</label>
<description>Total data transfered since last connection</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="bandwidth" advanced="true">
<item-type>Number:DataTransferRate</item-type>
<label>Bandwidth</label>
<description>Available bandwidth</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="uptime" advanced="true">
<item-type>Number:Time</item-type>
<label>Uptime</label>
<description>Time since last reboot of the equipment</description>
<category>time</category>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="line-status">
<item-type>String</item-type>
<label>Line Status</label>
<description>Status of network line connection</description>
<state readOnly="true" pattern="%s">
<options>
<option value="GOING_UP">Connection is initializing</option>
<option value="UP">Connection is active</option>
<option value="GOING_DOWN">Connection is about to become inactive</option>
<option value="DOWN">Connection is inactive</option>
</options>
</state>
</channel-type>
<channel-type id="line-type">
<item-type>String</item-type>
<label>Line Type</label>
<description>Type of network line connection</description>
<state readOnly="true" pattern="%s">
<options>
<option value="ETHERNET">FTTH/ethernet</option>
<option value="RFC2684">xDSL (unbundled)</option>
<option value="PPPOATM">xDSL</option>
</options>
</state>
</channel-type>
<channel-type id="line-media">
<item-type>String</item-type>
<label>Line Media</label>
<description>Media of network line connection</description>
<state readOnly="true" pattern="%s">
<options>
<option value="FTTH">FTTH</option>
<option value="ETHERNET">Ethernet</option>
<option value="XDSL">xDSL</option>
<option value="BACKUP_4G">Internet Backup</option>
</options>
</state>
</channel-type>
<channel-type id="player-status">
<item-type>String</item-type>
<label>Player Status</label>
<description>Status of the Freebox TV player</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="package">
<item-type>String</item-type>
<label>Active Package</label>
<description>Name of the package currently active on the player</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="wifi-status">
<item-type>Switch</item-type>
<label>Wifi Enabled</label>
<description>Indicates whether the wifi network is enabled</description>
<category>Switch</category>
</channel-type>
<channel-type id="ftp-status" advanced="true">
<item-type>Switch</item-type>
<label>FTP Server Enabled</label>
<description>Indicates whether the FTP server is enabled</description>
<category>Switch</category>
</channel-type>
<channel-type id="airmedia-status">
<item-type>Switch</item-type>
<label>Air Media Enabled</label>
<description>Indicates whether Air Media is enabled</description>
<category>Switch</category>
</channel-type>
<channel-type id="upnpav-status" advanced="true">
<item-type>Switch</item-type>
<label>UPnP AV Enabled</label>
<description>Indicates whether UPnP AV is enabled</description>
<category>Switch</category>
</channel-type>
<channel-type id="onhook">
<item-type>Switch</item-type>
<label>On Hook</label>
<description>Indicates whether the phone is on hook</description>
<category>Switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="ringing">
<item-type>Switch</item-type>
<label>Ringing</label>
<description>Is the phone ringing</description>
<category>Alarm</category>
</channel-type>
<channel-type id="phone-number">
<item-type>String</item-type>
<label>Phone Number</label>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="number">
<item-type>Call</item-type>
<label>Incoming Call</label>
<description>Details about call</description>
<state pattern="%1$s => %2$s" readOnly="true"/>
</channel-type>
<channel-type id="duration">
<item-type>Number:Time</item-type>
<label>Duration</label>
<description>Call duration in seconds</description>
<category>time</category>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="timestamp">
<item-type>DateTime</item-type>
<label>Timestamp</label>
<category>time</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="name" advanced="true">
<item-type>String</item-type>
<label>Name</label>
<description>Called name for outgoing calls. Caller name for incoming calls</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="reachable">
<item-type>Switch</item-type>
<label>Reachable</label>
<description>Indicates if the network device is reachable or not</description>
<category>Switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="status">
<item-type>Switch</item-type>
<label>VM Status</label>
</channel-type>
<channel-type id="box-event">
<kind>trigger</kind>
<label>Server Event</label>
<description>Triggers when an event related to the Freebox server has been detected</description>
</channel-type>
<channel-type id="phone-event">
<kind>trigger</kind>
<label>Phone Event</label>
<description>Triggers when an event related to the phone has been detected</description>
</channel-type>
<channel-type id="key-code">
<item-type>String</item-type>
<label>Remote Key Code</label>
<description>Simulates pushing a remote control button</description>
<state readOnly="false" pattern="%s">
<options>
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
<option value="yellow">Yellow</option>
<option value="power">On/Off</option>
<option value="list">List</option>
<option value="tv">TV</option>
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="vol_inc">Volume Up</option>
<option value="vol_dec">Volume Down</option>
<option value="mute">Mute</option>
<option value="prgm_inc">Prog Up</option>
<option value="prgm_dec">Prog Down</option>
<option value="prev">Previous</option>
<option value="bwd">Backward</option>
<option value="play">Play/Pause</option>
<option value="rec">Record</option>
<option value="fwd">Forward</option>
<option value="next">Next</option>
<option value="up">Up</option>
<option value="right">Right</option>
<option value="down">Down</option>
<option value="left">Left</option>
<option value="back">Back</option>
<option value="swap">Swap</option>
<option value="info">Info</option>
<option value="epg">EPG</option>
<option value="mail">Mail</option>
<option value="media">Media</option>
<option value="help">Help</option>
<option value="options">Options</option>
<option value="pip">PIP</option>
<option value="ok">OK</option>
<option value="home">Home</option>
</options>
</state>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="ip-address" advanced="true">
<item-type>String</item-type>
<label>IP Address</label>
<description>IP address of the client</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="led">
<item-type>Switch</item-type>
<label>Led Activated</label>
<description>Led indicator status</description>
<category>Switch</category>
</channel-type>
<channel-type id="gain">
<item-type>Dimmer</item-type>
<label>Gain</label>
</channel-type>
<channel-type id="connection-status">
<item-type>String</item-type>
<label>Connection</label>
<description>Connection Status</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="hardware-status">
<item-type>String</item-type>
<label>Hardware Status</label>
<description>Hardware status of the phone line</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="telephony-service">
<item-type>String</item-type>
<label>Telephony Service</label>
<description>Status of the telephony service</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="host-count">
<item-type>Number</item-type>
<label>Host Count</label>
<description>Number of hosts connected to the device</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="keyfob-enable">
<item-type>Switch</item-type>
<label>Keyfob Enabled</label>
<description>Activates / deactivates the keyfob</description>
<category>Switch</category>
<config-description>
<parameter name="slot" type="integer"/>
<parameter name="signal" type="integer"/>
</config-description>
</channel-type>
<channel-type id="basic-shutter">
<item-type>Rollershutter</item-type>
<label>Shutter</label>
<description>Shutter command</description>
<category>Blinds</category>
<config-description>
<parameter name="up" type="integer"/>
<parameter name="down" type="integer"/>
<parameter name="stop" type="integer"/>
<parameter name="signal" type="integer"/>
</config-description>
</channel-type>
<channel-type id="shutter">
<item-type>Rollershutter</item-type>
<label>Shutter Position</label>
<description>Read / Write position of the shutter</description>
<state pattern="%d %unit%"/>
<config-description>
<parameter name="slot" type="integer"/>
<parameter name="signal" type="integer"/>
<parameter name="stop" type="integer"/>
</config-description>
</channel-type>
<channel-type id="alarm-timeout">
<item-type>Number:Time</item-type>
<label>Alarm Duration</label>
<category>oh:freeboxos:zone_temporisee</category>
<state pattern="%d %unit%"/>
</channel-type>
<channel-type id="alarm-volume">
<item-type>Number:Dimensionless</item-type>
<label>Alarm Volume</label>
<category>oh:freeboxos:sirene</category>
<state min="0" max="100" step="1" pattern="%d %unit%"/>
</channel-type>
<channel-type id="alarm-pin">
<item-type>String</item-type>
<label>PIN Code</label>
<category>oh:freeboxos:pin_code</category>
<state pattern="%s"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="freeboxos"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="freeplug">
<supported-bridge-type-refs>
<bridge-type-ref id="api"/>
</supported-bridge-type-refs>
<label>Freeplug</label>
<description>Ethernet / CPL gateway</description>
<channels>
<channel id="line-status" typeId="line-status"/>
<channel id="last-seen" typeId="timestamp">
<label>Last Activity</label>
</channel>
<channel id="reachable" typeId="reachable"/>
<channel id="rate-up" typeId="transfer-rate">
<label>Tx Rate</label>
<description>Current TX Rate</description>
</channel>
<channel id="rate-down" typeId="transfer-rate">
<label>Rx Rate</label>
<description>Current RX rate</description>
</channel>
</channels>
<representation-property>macAddress</representation-property>
<config-description-ref uri="thing-type:freeboxos:host"/>
</thing-type>
</thing:thing-descriptions>

Some files were not shown because too many files have changed in this diff Show More