]> git.basschouten.com Git - openhab-addons.git/commitdiff
[Freeboxos] New binding alternative to Freebox binding (#12342)
authorGaël L'hopital <gael@lhopital.org>
Wed, 5 Jul 2023 18:13:29 +0000 (20:13 +0200)
committerGitHub <noreply@github.com>
Wed, 5 Jul 2023 18:13:29 +0000 (20:13 +0200)
* 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>
118 files changed:
CODEOWNERS
bom/openhab-addons/pom.xml
bundles/org.openhab.binding.freeboxos/NOTICE [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/README.md [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/pom.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/FreeboxOsBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/FreeboxOsHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/ActivePlayerActions.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/CallActions.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/FreeplugActions.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/HostActions.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/PlayerActions.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/RepeaterActions.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/ServerActions.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/ApiHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxException.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxOsIconProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxTlsCertificateProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/IliadboxTlsCertificateProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/PermissionException.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/Response.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/ForegroundAppDeserializer.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/ListDeserializer.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/StrictEnumTypeAdapterFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/APManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/AfpManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/AirMediaManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/CallManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ConfigurableRest.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ConnectionManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FreeboxOsSession.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FreeplugManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FtpManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/HomeManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LanBrowserManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LanManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LcdManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ListableRest.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LoginManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/MediaReceiverManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/NetShareManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/PhoneManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/PlayerManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/RepeaterManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/RestManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/SambaManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/SystemManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/UPnPAVManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/VmManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/WebSocketManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/WifiManager.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/ApiConsumerConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/ClientConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/FreeboxOsConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/FreeplugConfigurationBuilder.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/HostConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/LandlineConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/NodeConfigurationBuilder.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/PhoneConfigurationBuilder.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/PlayerConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/console/FreeboxOsCommandExtension.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/discovery/ApiDiscoveryParticipant.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/discovery/FreeboxOsDiscoveryService.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ActivePlayerHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/AirMediaSink.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/AlarmHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ApiConsumerHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ApiConsumerIntf.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/BasicShutterHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/CallHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/CameraHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/DectHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/FreeDeviceIntf.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/FreeboxOsHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/FreeplugHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/FxsHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/HomeNodeHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/HostHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/KeyfobHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/PlayerHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/RepeaterHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/RevolutionHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ServerHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ShutterHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/VmHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/WifiStationHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/addon/addon.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/bridge-config.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/home-node-config.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/host-config.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/phone-config.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/player-config.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/repeater-config.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/server-config.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/vm-config.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/i18n/freeboxos.properties [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/api-bridge-type.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/channel-types.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/freeplug-thing-type.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/home-thing-type.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/host-channel-groups.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/host-thing-type.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/phone-channel-groups.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/phone-thing-type.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/player-channel-groups.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/player-thing-type.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/repeater-channel-groups.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/repeater-thing-type.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/server-channel-groups.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/server-thing-type.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/vm-channel-groups.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/vm-thing-type.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/wifi-channel-groups.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/wifihost-thing-type.xml [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/freeboxECCRootCA.crt [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/resources/iliadboxECCRootCA.crt [new file with mode: 0644]
bundles/pom.xml

index a6d6e160d38584138cbb377c77ccceb57cd844a6..903e746634aaaad01b18757a12215cac2cc32c1b 100644 (file)
 /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
index 2a48a1987d3a1f3c8b4540752e56b7af643b10fc..61c86ec949d2b2453f562237e91d084780e00e94 100644 (file)
       <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>
diff --git a/bundles/org.openhab.binding.freeboxos/NOTICE b/bundles/org.openhab.binding.freeboxos/NOTICE
new file mode 100644 (file)
index 0000000..8928601
--- /dev/null
@@ -0,0 +1,20 @@
+This content is produced and maintained by the openHAB project.\r
+\r
+* Project home: https://www.openhab.org\r
+\r
+== Declared Project Licenses\r
+\r
+This program and the accompanying materials are made available under the terms\r
+of the Eclipse Public License 2.0 which is available at\r
+https://www.eclipse.org/legal/epl-2.0/.\r
+\r
+== Source Code\r
+\r
+https://github.com/openhab/openhab-addons\r
+\r
+== Third-party Content\r
+\r
+IPAddress: Java library for handling IP addresses and subnets, both IPv4 and IPv6\r
+* License: Apache License 2.0\r
+* Project: https://github.com/seancfoley/IPAddress\r
+* Source:  https://github.com/seancfoley/IPAddress\r
diff --git a/bundles/org.openhab.binding.freeboxos/README.md b/bundles/org.openhab.binding.freeboxos/README.md
new file mode 100644 (file)
index 0000000..5c4c8ac
--- /dev/null
@@ -0,0 +1,210 @@
+# FreeboxOS Binding
+
+Free is a French telecom operator providing advanced equipments to manage your broadband access.
+
+This binding integrates the [Freebox Revolution](https://www.free.fr/freebox/freebox-revolution/) and [Freebox Delta](https://www.free.fr/freebox/freebox-delta/) to your openHAB installation.
+
+The server can be connected to Free infrastructures either by xDSL or fiber optic.
+
+This binding provides metrics about your network bridge/router and attached devices (wifi repeaters, TV boxes ...).
+It also provides home automation capabilities when appropriate dongle has been inserted in the server.
+
+IliadBox, italian version of the Freebox Pop are also compatible.
+
+## Supported Things
+
+This binding supports the following thing types:
+
+| Thing             | Thing Type | Description                                                   |
+|-------------------|------------|---------------------------------------------------------------|
+| api               | Bridge     | Bridge to access freebox OS API hosted by the server          |
+| delta             | Thing      | A Freebox Delta server                                        |
+| revolution        | Thing      | A Freebox Revolution server                                   |
+| player            | Thing      | A TV player equipment                                         |
+| active-player     | Thing      | The TV player equipment with API capabilities (e.g. Devialet) |
+| landline          | Thing      | The phone wired to the Freebox Server                         |
+| host              | Thing      | A network device on the local network                         |
+| wifihost          | Thing      | A wifi networked device on the local network                  |
+| vm            (*) | Thing      | A virtual machine hosted on the server                        |
+| freeplug          | Thing      | A virtual machine hosted on the server                        |
+| repeater          | Thing      | A Free wifi repeater                                          |
+| basic-shutter (*) | Thing      | RTS Shutter configured in Freebox Home                        |
+| shutter       (*) | Thing      | IO Home Control shutter configured in Freebox Home            |
+| kfb (*)           | Thing      | A keyfob associated with your alarm system                    |
+| alarm (*)         | Thing      | The Freebox Home Alarm System                                 |
+
+(*) Restricted to servers >= Delta
+
+## Discovery
+
+The API bridge is discovered automatically through mDNS in the local network.
+After the bridge is discovered and available to openHAB, the binding will automatically discover phone, network devices available on the local network.
+Note that the discovered thing will be setup to use only HTTP API (and not HTTPS) because only an IP can be automatically determined while a domain name is required to use HTTPS with a valid certificate.
+
+## Binding configuration
+
+FreeboxOS binding has the following configuration parameters:
+
+| Name            | Description                                        | Mandatory |
+|-----------------|----------------------------------------------------|-----------|
+| timeout         | The timeout for reading from the device in seconds | yes       |
+
+
+## Thing Configuration
+
+### API bridge
+
+| Parameter Label          | Parameter ID      | Description                                            | Required | Default              |
+|--------------------------|-------------------|--------------------------------------------------------|----------|----------------------|
+| Freebox Server Address   | apiDomain         | The domain to use in place of hardcoded Freebox ip     | No       | mafreebox.freebox.fr |
+| Application Token        | appToken          | Token generated by the Freebox Server.                 | Yes      |                      |
+| Network Device Discovery | discoverNetDevice | Enable the discovery of network device things.         | No       | false                |
+| HTTPS Available          | httpsAvailable    | Tells if https has been configured on the Freebox      | No       | false                |
+| HTTPS port               | httpsPort         | Port to use for remote https access to the Freebox Api | No       | 15682                |
+
+If the parameter *apiDomain* is not set, the binding will use the default address used by Free to access your Freebox Server (mafreebox.freebox.fr).
+The bridge thing will initialize only if a valid application token (parameter *appToken*) is filled.
+
+### Server: Revolution or Delta
+
+The *revolution* or *delta* thing requires the following configuration parameters:
+
+| Parameter Label  | Parameter ID    | Description                                                              | Required | Default |
+|------------------|-----------------|--------------------------------------------------------------------------|----------|---------|
+| Refresh Interval | refreshInterval | The refresh interval (seconds) which is used to poll the Freebox Server. | No       | 30      |
+
+### Player thing 
+
+The *player* thing requires the following configuration parameters:
+
+| Parameter Label  | Parameter ID    | Description                                                                | Required | Default |
+|------------------|-----------------|----------------------------------------------------------------------------|----------|---------|
+| MAC Address      | macAddress      | The MAC address of the player device.                                      | Yes      |         |
+| ID               | id              | Id of the player within Freebox Api                                        | Yes      | 1       |
+| Player port      | port            |                                                                            | No       | 24322   |
+| Password         | password        | AirPlay password                                                           | No       |         |
+| Remote Code      | remoteCode      | Code associated to remote control                                          | No       |         |
+| Accept all MP3   | acceptAllMp3    | Accept any bitrate for MP3 audio or only bitrates greater than 64 kbps     | No       | true    |
+| Refresh Interval | refreshInterval | The refresh interval in seconds which is used to poll the player           | Yes      | 30      |
+| Callback URL     | callbackUrl     | URL to use for playing notification sounds, e.g. 'http://192.168.0.2:8080' | No       |         |
+
+### Landline
+
+The *landline* thing requires the following configuration parameters:
+
+| Parameter Label  | Parameter ID    | Description                                                            | Required | Default |
+|------------------|-----------------|------------------------------------------------------------------------|----------|---------|
+| Refresh Interval | refreshInterval | The refresh interval in seconds which is used to poll for phone state. | No       | 2       |
+
+### Network devices: Host and WifiHost
+
+The *host* and *wifihost* things requires the following configuration parameters:
+
+| Parameter Label  | Parameter ID    | Description                                                            | Required | Default |
+|------------------|-----------------|------------------------------------------------------------------------|----------|---------|
+| MAC Address      | macAddress      | The MAC address of the network host .                                  | Yes      |         |
+| Refresh Interval | refreshInterval | The refresh interval in seconds which is used to poll for phone state. | No       | 30      |
+
+### Repeater and Vm thing 
+
+The *repeater* thing is a specialized case of a *wifihost*. The *vm* derives from *host*. They share the same configuration definition: 
+
+| Parameter Label  | Parameter ID    | Description                                                      | Required | Default |
+|------------------|-----------------|------------------------------------------------------------------|----------|---------|
+| MAC Address      | macAddress      | The MAC address of the player device.                            | No       |         |
+| ID               | id              | Id of the repeater within Freebox Api                            | Yes      | 1       |
+| Refresh Interval | refreshInterval | The refresh interval in seconds which is used to poll the player | Yes      | 30      |
+
+### Basic shutter thing
+
+The *basic-shutter* thing requires the following configuration parameters:
+
+| Parameter Label  | Parameter ID    | Description                                                      | Required | Default |
+|------------------|-----------------|------------------------------------------------------------------|----------|---------|
+| ID               | id              | Id of the Home Node                                              | Yes      | 1       |
+| UP Slot ID       | upSlotId        | Id of the UP Slot.                                               | Yes      | 0       |
+| STOP Slot ID     | stopSlotId      | Id of the STOP Slot.                                             | Yes      | 1       |
+| DOWN Slot ID     | downSlotId      | Id of the DOWN Slot.                                             | Yes      | 2       |
+| STATE Signal ID  | stateSignalId   | Id of the STATE Signal.                                          | Yes      | 3       |
+
+## Authentication
+
+You will have to authorize openHAB to connect to your Freebox. Here is the process described:
+
+**Step 1** At binding startup, if no token is recorded in the Freebox Server (bridge) configuration, the binding will run a pairing request
+
+**Step 2** Run to your Freebox and approve the pairing request for openHAB FreeboxOS Binding that is displayed on the Freebox screen
+
+**Step 3** the application token is automatically recorded in the Freebox Server (bridge) configuration
+
+**Step 4** you can use the console command `freeboxos apptoken` to display the application token and use it later to set up your thing in a configuration file
+
+**Step 5** log in your Freebox admin console to allocate needed rights to FreeboxOS Binding
+
+Once initialized, the thing will generate all available channels.
+
+## Channels
+
+The following channels are supported:
+
+| Thing         | Channel Type ID             | Item Type     | Access Mode | Description                                                                    |
+|---------------|-----------------------------|---------------|-------------|--------------------------------------------------------------------------------|
+| revolution    | lcd-brightness              | Number        | RW          | Brightness level of the screen in percent                                      |
+| revolution    | lcd-orientation             | Number        | RW          | Screen Orientation in degrees (0 or 90 or 180 or 270)                          |
+| revolution    | lcd-forced                  | Switch        | RW          | Indicates whether the screen orientation forced                                |
+| server (*)    | uptime                      | Number        | R           | Time since last reboot of the Freebox Server                                   |
+| server (*)    | restarted                   | Switch        | R           | Indicates whether the Freebox server has restarted during the last poll period |
+| server (*)    | wifi-status                 | Switch        | RW          | Indicates whether the WiFi network is enabled                                  |
+| server (*)    | ftp-status                  | Switch        | RW          | Indicates whether the FTP server is enabled                                    |
+| server (*)    | airmedia-status             | Switch        | RW          | Indicates whether Air Media is enabled                                         |
+| server (*)    | upnpav-status               | Switch        | RW          | Indicates whether UPnP AV is enabled                                           |
+| server (*)    | samba-file-status           | Switch        | RW          | Indicates whether Window File Sharing is enabled                               |
+| server (*)    | samba-printer-status        | Switch        | RW          | Indicates whether Window Printer Sharing is enabled                            |
+| server (*)    | xdsl-status                 | String        | R           | Status of the xDSL line                                                        |
+| server (*)    | ftth-status                 | Switch        | R           | Status of the Ftth line                                                        |
+| server (*)    | line-status                 | String        | R           | Status of network line connexion                                               |
+| server (*)    | ipv4                        | String        | R           | Public IP Address of the Freebox Server                                        |
+| server (*)    | rate-up                     | Number        | R           | Current upload rate in byte/s                                                  |
+| server (*)    | rate-down                   | Number        | R           | Current download  rate in byte/s                                               |
+| server (*)    | bytes-up                    | Number        | R           | Total uploaded bytes since last connection                                     |
+| server (*)    | bytes-down                  | Number        | R           | Total downloaded  bytes since last connection                                  |
+| phone         | state#onhook                | Switch        | R           | Indicates whether the phone is on hook                                         |
+| phone         | state#ringing               | Switch        | R           | Is the phone ringing                                                           |
+| call          | incoming#number             | Call          | R           | Current incoming call number                                                   |
+| call          | incoming#timestamp          | DateTime      | R           | Current incoming call creation timestamp                                       |
+| call          | incoming#name               | String        | R           | Current incoming caller name                                                   |
+| call          | accepted#number             | Call          | R           | Last accepted call number                                                      |
+| call          | accepted#duration           | Number        | R           | Last accepted call duration in seconds                                         |
+| call          | accepted#timestamp          | DateTime      | R           | Last accepted call creation timestamp                                          |
+| call          | accepted#name               | String        | R           | Last accepted caller name                                                      |
+| call          | missed#number               | Call          | R           | Last missed call number                                                        |
+| call          | missed#timestamp            | DateTime      | R           | Last missed call creation timestamp                                            |
+| call          | missed#name                 | String        | R           | Last missed caller name                                                        |
+| call          | outgoing#number             | Call          | R           | Last outgoing call number                                                      |
+| call          | outgoing#duration           | Number        | R           | Last outgoing call duration in seconds                                         |
+| call          | outgoing#timestamp          | DateTime      | R           | Last outgoing call creation timestamp                                          |
+| call          | outgoing#name               | String        | R           | Last outgoing called name                                                      |
+| net_device    | reachable                   | Switch        | R           | Indicates whether the network device is reachable                              |
+| net_interface | reachable                   | Switch        | R           | Indicates whether the network interface is reachable                           |
+| airplay       | playurl                     | String        | W           | Play an audio or video media from the given URL                                |
+| airplay       | stop                        | Switch        | W           | Stop the media playback                                                        |
+| basic-shutter | basic-shutter#basic-shutter | RollerShutter | W           | Up, stop and down commands for a RTS shutter                                   |
+
+(*): server means *delta* or *revolution*
+
+## Actions for rules
+
+The following actions are available in rules/scripting:
+
+| Thing Type  | Action Name      | Parameters              | Description                                          | 
+|-------------|------------------|-------------------------|------------------------------------------------------|
+| host        | wol              | None                    | Sends a wake on lan packet to the lan connected host |
+| player      | reboot           | None                    | Reboots the player device                            |
+| player      | sendKey          | key: String             | Send a key (remote emulation) to the player          |
+| player      | sendLongKey      | key: String             | Sends the key emulating a longpress on the button    |
+| player      | sendMultipleKeys | keys: String            | Sends multiple keys to the player, comma separated   |
+| player      | sendKeyRepeat    | key: String, count: int | Sends the key multiple times                         |
+| server      | reboot           | None                    | Reboots the Freebox Server                           |
+| freeplug    | reset            | None                    | Resets the Freeplug                                  |
+| call        | reset            | None                    | Clears the call log queue                            |
+| repeater    | reboot           | None                    | Reboots the Repeater                                 |
diff --git a/bundles/org.openhab.binding.freeboxos/pom.xml b/bundles/org.openhab.binding.freeboxos/pom.xml
new file mode 100644 (file)
index 0000000..f28ee76
--- /dev/null
@@ -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>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/feature/feature.xml b/bundles/org.openhab.binding.freeboxos/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..bb55dd1
--- /dev/null
@@ -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>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/FreeboxOsBindingConstants.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/FreeboxOsBindingConstants.java
new file mode 100644 (file)
index 0000000..88c71f2
--- /dev/null
@@ -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);
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/FreeboxOsHandlerFactory.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/FreeboxOsHandlerFactory.java
new file mode 100644 (file)
index 0000000..f835aa2
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/ActivePlayerActions.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/ActivePlayerActions.java
new file mode 100644 (file)
index 0000000..1023aff
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.freeboxos.internal.action;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.handler.ActivePlayerHandler;
+import org.openhab.binding.freeboxos.internal.handler.PlayerHandler;
+import org.openhab.core.automation.annotation.RuleAction;
+import org.openhab.core.thing.binding.ThingActions;
+import org.openhab.core.thing.binding.ThingActionsScope;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {ActivePlayerActions} class is responsible to call corresponding actions on Freebox Player with API
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@ThingActionsScope(name = "freeboxos")
+@NonNullByDefault
+public class ActivePlayerActions extends PlayerActions {
+    private final Logger logger = LoggerFactory.getLogger(ActivePlayerActions.class);
+
+    @RuleAction(label = "reboot freebox player", description = "Reboots the Freebox Player")
+    public void reboot() {
+        logger.debug("Player reboot called");
+        PlayerHandler localHandler = this.handler;
+        if (localHandler instanceof ActivePlayerHandler apHandler) {
+            apHandler.reboot();
+        } else {
+            logger.warn("Freebox Player Action service ThingHandler is null");
+        }
+    }
+
+    public static void reboot(ThingActions actions) {
+        ((ActivePlayerActions) actions).reboot();
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/CallActions.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/CallActions.java
new file mode 100644 (file)
index 0000000..0f8d9b4
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.freeboxos.internal.action;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.handler.CallHandler;
+import org.openhab.core.automation.annotation.RuleAction;
+import org.openhab.core.thing.binding.ThingActions;
+import org.openhab.core.thing.binding.ThingActionsScope;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {FreeplugActions} class is responsible to call corresponding actions on Freeplugs
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@ThingActionsScope(name = "freeboxos")
+@NonNullByDefault
+public class CallActions implements ThingActions {
+    private final Logger logger = LoggerFactory.getLogger(CallActions.class);
+    private @Nullable CallHandler handler;
+
+    @Override
+    public void setThingHandler(@Nullable ThingHandler handler) {
+        if (handler instanceof CallHandler callHandler) {
+            this.handler = callHandler;
+        }
+    }
+
+    @Override
+    public @Nullable ThingHandler getThingHandler() {
+        return handler;
+    }
+
+    @RuleAction(label = "clear call queue", description = "Delete all call logged in the queue")
+    public void reset() {
+        logger.debug("Call log clear called");
+        CallHandler localHandler = handler;
+        if (localHandler != null) {
+            localHandler.emptyQueue();
+        } else {
+            logger.warn("Call Action service ThingHandler is null");
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/FreeplugActions.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/FreeplugActions.java
new file mode 100644 (file)
index 0000000..b24af4c
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.freeboxos.internal.action;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.handler.FreeplugHandler;
+import org.openhab.core.automation.annotation.RuleAction;
+import org.openhab.core.thing.binding.ThingActions;
+import org.openhab.core.thing.binding.ThingActionsScope;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {FreeplugActions} class is responsible to call corresponding actions on Freeplugs
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@ThingActionsScope(name = "freeboxos")
+@NonNullByDefault
+public class FreeplugActions implements ThingActions {
+    private final Logger logger = LoggerFactory.getLogger(FreeplugActions.class);
+    private @Nullable FreeplugHandler handler;
+
+    @Override
+    public void setThingHandler(@Nullable ThingHandler handler) {
+        if (handler instanceof FreeplugHandler plugHandler) {
+            this.handler = plugHandler;
+        }
+    }
+
+    @Override
+    public @Nullable ThingHandler getThingHandler() {
+        return this.handler;
+    }
+
+    @RuleAction(label = "reset freeplug", description = "Resets the Freeplug")
+    public void reset() {
+        logger.debug("Freeplug reset requested");
+        FreeplugHandler plugHandler = this.handler;
+        if (plugHandler != null) {
+            plugHandler.reset();
+        } else {
+            logger.warn("Freeplug Action service ThingHandler is null");
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/HostActions.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/HostActions.java
new file mode 100644 (file)
index 0000000..b076063
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.freeboxos.internal.action;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.handler.HostHandler;
+import org.openhab.core.automation.annotation.RuleAction;
+import org.openhab.core.thing.binding.ThingActions;
+import org.openhab.core.thing.binding.ThingActionsScope;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {HostActions} class is responsible to call corresponding actions on a given lan host
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@ThingActionsScope(name = "freeboxos")
+@NonNullByDefault
+public class HostActions implements ThingActions {
+    private final Logger logger = LoggerFactory.getLogger(HostActions.class);
+    private @Nullable HostHandler handler;
+
+    @Override
+    public void setThingHandler(@Nullable ThingHandler handler) {
+        if (handler instanceof HostHandler hostHandler) {
+            this.handler = hostHandler;
+        }
+    }
+
+    @Override
+    public @Nullable ThingHandler getThingHandler() {
+        return this.handler;
+    }
+
+    @RuleAction(label = "wol host", description = "Awakes a lan host")
+    public void wol() {
+        logger.debug("Host WOL called");
+        HostHandler hostHandler = this.handler;
+        if (hostHandler != null) {
+            hostHandler.wol();
+        } else {
+            logger.warn("LanHost Action service ThingHandler is null");
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/PlayerActions.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/PlayerActions.java
new file mode 100644 (file)
index 0000000..b1e7d61
--- /dev/null
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.freeboxos.internal.action;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.handler.PlayerHandler;
+import org.openhab.core.automation.annotation.ActionInput;
+import org.openhab.core.automation.annotation.RuleAction;
+import org.openhab.core.thing.binding.ThingActions;
+import org.openhab.core.thing.binding.ThingActionsScope;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {PlayerActions} class is responsible to call corresponding actions on Freebox Player
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@ThingActionsScope(name = "freeboxos")
+@NonNullByDefault
+public class PlayerActions implements ThingActions {
+    private final Logger logger = LoggerFactory.getLogger(PlayerActions.class);
+    protected @Nullable PlayerHandler handler;
+
+    @Override
+    public void setThingHandler(@Nullable ThingHandler handler) {
+        if (handler instanceof PlayerHandler playerHandler) {
+            this.handler = playerHandler;
+        }
+    }
+
+    @Override
+    public @Nullable ThingHandler getThingHandler() {
+        return this.handler;
+    }
+
+    @RuleAction(label = "send a key to player", description = "Sends a given key to the player")
+    public void sendKey(@ActionInput(name = "key") String key) {
+        logger.debug("Sending key {} to player", key);
+        PlayerHandler playerHandler = this.handler;
+        if (playerHandler != null) {
+            playerHandler.sendKey(key, false, 1);
+        } else {
+            logger.warn("Freebox Player Action service ThingHandler is null");
+        }
+    }
+
+    @RuleAction(label = "send a long key to player", description = "Sends a given key to the player and keep it pressed")
+    public void sendLongKey(@ActionInput(name = "key") String key) {
+        logger.debug("Sending long press key {} to player", key);
+        PlayerHandler playerHandler = this.handler;
+        if (playerHandler != null) {
+            playerHandler.sendKey(key, true, 1);
+        } else {
+            logger.warn("Freebox Player Action service ThingHandler is null");
+        }
+    }
+
+    @RuleAction(label = "send multiple keys to player", description = "Sends multiple keys to the player, comma separated")
+    public void sendMultipleKeys(@ActionInput(name = "keys") String keys) {
+        logger.debug("Sending keys {} to player", keys);
+        PlayerHandler playerHandler = this.handler;
+        if (playerHandler != null) {
+            playerHandler.sendMultipleKeys(keys);
+        } else {
+            logger.warn("Freebox Player Action service ThingHandler is null");
+        }
+    }
+
+    @RuleAction(label = "send repeating key to player", description = "Sends a given key multiple times to the player")
+    public void sendKeyRepeat(@ActionInput(name = "key") String key, @ActionInput(name = "count") int count) {
+        logger.debug("Sending key {} to player {} times", key, count);
+        PlayerHandler playerHandler = this.handler;
+        if (playerHandler != null) {
+            playerHandler.sendKey(key, false, count);
+        } else {
+            logger.warn("Freebox Player Action service ThingHandler is null");
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/RepeaterActions.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/RepeaterActions.java
new file mode 100644 (file)
index 0000000..ac33373
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.freeboxos.internal.action;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.handler.RepeaterHandler;
+import org.openhab.core.automation.annotation.RuleAction;
+import org.openhab.core.thing.binding.ThingActions;
+import org.openhab.core.thing.binding.ThingActionsScope;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {RepeaterActions} class is responsible to call corresponding actions on Freebox Repeater
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@ThingActionsScope(name = "freeboxos")
+@NonNullByDefault
+public class RepeaterActions implements ThingActions {
+    private final Logger logger = LoggerFactory.getLogger(RepeaterActions.class);
+    private @Nullable RepeaterHandler handler;
+
+    @Override
+    public void setThingHandler(@Nullable ThingHandler handler) {
+        if (handler instanceof RepeaterHandler repeaterHandler) {
+            this.handler = repeaterHandler;
+        }
+    }
+
+    @Override
+    public @Nullable ThingHandler getThingHandler() {
+        return handler;
+    }
+
+    @RuleAction(label = "reboot free repeater", description = "Reboots the Free Repeater")
+    public void reboot() {
+        logger.debug("Repeater reboot called");
+        RepeaterHandler localHandler = this.handler;
+        if (localHandler != null) {
+            localHandler.reboot();
+        } else {
+            logger.warn("Repeater Action service ThingHandler is null");
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/ServerActions.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/action/ServerActions.java
new file mode 100644 (file)
index 0000000..4c5aea8
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.freeboxos.internal.action;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.freeboxos.internal.handler.ServerHandler;
+import org.openhab.core.automation.annotation.RuleAction;
+import org.openhab.core.thing.binding.ThingActions;
+import org.openhab.core.thing.binding.ThingActionsScope;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {ServerActions} class is responsible to call corresponding actions on Freebox Server
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@ThingActionsScope(name = "freeboxos")
+@NonNullByDefault
+public class ServerActions implements ThingActions {
+    private final Logger logger = LoggerFactory.getLogger(ServerActions.class);
+    private @Nullable ServerHandler handler;
+
+    @Override
+    public void setThingHandler(@Nullable ThingHandler handler) {
+        if (handler instanceof ServerHandler serverHandler) {
+            this.handler = serverHandler;
+        }
+    }
+
+    @Override
+    public @Nullable ThingHandler getThingHandler() {
+        return this.handler;
+    }
+
+    @RuleAction(label = "reboot freebox server", description = "Reboots the Freebox Server")
+    public void reboot() {
+        logger.debug("Server reboot called");
+        ServerHandler serverHandler = this.handler;
+        if (serverHandler != null) {
+            serverHandler.reboot();
+        } else {
+            logger.warn("Freebox Action service ThingHandler is null");
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/ApiHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/ApiHandler.java
new file mode 100644 (file)
index 0000000..df5509c
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxException.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxException.java
new file mode 100644 (file)
index 0000000..5b04f63
--- /dev/null
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.freeboxos.internal.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.Response.ErrorCode;
+
+/**
+ * Exception for errors when using the Freebox API
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class FreeboxException extends Exception {
+    private static final long serialVersionUID = 9197365222439228186L;
+    private ErrorCode errorCode = ErrorCode.NONE;
+
+    public FreeboxException(String format, Object... args) {
+        super(String.format(format, args));
+    }
+
+    public FreeboxException(Exception cause, String format, Object... args) {
+        super(String.format(format, args), cause);
+    }
+
+    public FreeboxException(ErrorCode errorCode, String message) {
+        this(message);
+        this.errorCode = errorCode;
+    }
+
+    public ErrorCode getErrorCode() {
+        return errorCode;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxOsIconProvider.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxOsIconProvider.java
new file mode 100644 (file)
index 0000000..2895add
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxTlsCertificateProvider.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/FreeboxTlsCertificateProvider.java
new file mode 100644 (file)
index 0000000..4711124
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.freeboxos.internal.api;
+
+import java.net.URL;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.io.net.http.TlsCertificateProvider;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * Provides a CertificateManager for the Freebox SSL certificate
+ *
+ * @author Gaël L'hopital - Initial Contribution
+ */
+@Component
+@NonNullByDefault
+public class FreeboxTlsCertificateProvider implements TlsCertificateProvider {
+
+    private static final String CERTIFICATE_NAME = "freeboxECCRootCA.crt";
+
+    public static final String DEFAULT_NAME = "mafreebox.freebox.fr";
+
+    @Override
+    public String getHostName() {
+        return DEFAULT_NAME;
+    }
+
+    @Override
+    public URL getCertificate() {
+        URL resource = Thread.currentThread().getContextClassLoader().getResource(CERTIFICATE_NAME);
+        if (resource != null) {
+            return resource;
+        }
+        throw new IllegalStateException("Certificate '%s' not found or not accessible".formatted(CERTIFICATE_NAME));
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/IliadboxTlsCertificateProvider.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/IliadboxTlsCertificateProvider.java
new file mode 100644 (file)
index 0000000..c6e2549
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.freeboxos.internal.api;
+
+import java.net.URL;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.io.net.http.TlsCertificateProvider;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * Provides a CertificateManager for the IliadBox SSL certificate
+ *
+ * @author Gaël L'hopital - Initial Contribution
+ */
+@Component
+@NonNullByDefault
+public class IliadboxTlsCertificateProvider implements TlsCertificateProvider {
+
+    private static final String CERTIFICATE_NAME = "iliadboxECCRootCA.crt";
+
+    public static final String DEFAULT_NAME = "myiliadbox.iliad.it";
+
+    @Override
+    public String getHostName() {
+        return DEFAULT_NAME;
+    }
+
+    @Override
+    public URL getCertificate() {
+        URL resource = Thread.currentThread().getContextClassLoader().getResource(CERTIFICATE_NAME);
+        if (resource != null) {
+            return resource;
+        }
+        throw new IllegalStateException("Certificate '%s' not found or not accessible".formatted(CERTIFICATE_NAME));
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/PermissionException.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/PermissionException.java
new file mode 100644 (file)
index 0000000..d18c0c5
--- /dev/null
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.freeboxos.internal.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.rest.LoginManager;
+
+/**
+ * Exception for errors when Session require missing permission
+ *
+ * @author ben12 - Initial contribution
+ */
+@NonNullByDefault
+public class PermissionException extends FreeboxException {
+    private static final long serialVersionUID = 3965810786699311126L;
+
+    private final LoginManager.Permission permission;
+
+    public PermissionException(LoginManager.Permission permission, String format, Object... args) {
+        super(format, args);
+        this.permission = permission;
+    }
+
+    public LoginManager.Permission getPermission() {
+        return permission;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/Response.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/Response.java
new file mode 100644 (file)
index 0000000..1b95cf4
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/ForegroundAppDeserializer.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/ForegroundAppDeserializer.java
new file mode 100644 (file)
index 0000000..74c8305
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/ListDeserializer.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/ListDeserializer.java
new file mode 100644 (file)
index 0000000..0e403d4
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/StrictEnumTypeAdapterFactory.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/deserialization/StrictEnumTypeAdapterFactory.java
new file mode 100644 (file)
index 0000000..3c881fc
--- /dev/null
@@ -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);
+            }
+        };
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/APManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/APManager.java
new file mode 100644 (file)
index 0000000..233046d
--- /dev/null
@@ -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();
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/AfpManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/AfpManager.java
new file mode 100644 (file)
index 0000000..ed077ef
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/AirMediaManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/AirMediaManager.java
new file mode 100644 (file)
index 0000000..8b8ddc2
--- /dev/null
@@ -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();
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/CallManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/CallManager.java
new file mode 100644 (file)
index 0000000..54444ba
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ConfigurableRest.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ConfigurableRest.java
new file mode 100644 (file)
index 0000000..7d57f93
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ConnectionManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ConnectionManager.java
new file mode 100644 (file)
index 0000000..c500847
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FreeboxOsSession.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FreeboxOsSession.java
new file mode 100644 (file)
index 0000000..d67b3ed
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FreeplugManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FreeplugManager.java
new file mode 100644 (file)
index 0000000..0444f87
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FtpManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/FtpManager.java
new file mode 100644 (file)
index 0000000..6be5801
--- /dev/null
@@ -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();
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/HomeManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/HomeManager.java
new file mode 100644 (file)
index 0000000..6fd9187
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LanBrowserManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LanBrowserManager.java
new file mode 100644 (file)
index 0000000..694d964
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LanManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LanManager.java
new file mode 100644 (file)
index 0000000..e628cb2
--- /dev/null
@@ -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()));
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LcdManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LcdManager.java
new file mode 100644 (file)
index 0000000..b19678f
--- /dev/null
@@ -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");
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ListableRest.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/ListableRest.java
new file mode 100644 (file)
index 0000000..f88b30d
--- /dev/null
@@ -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));
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LoginManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/LoginManager.java
new file mode 100644 (file)
index 0000000..41e38e2
--- /dev/null
@@ -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");
+        };
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/MediaReceiverManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/MediaReceiverManager.java
new file mode 100644 (file)
index 0000000..da7849b
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/NetShareManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/NetShareManager.java
new file mode 100644 (file)
index 0000000..617eac4
--- /dev/null
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.freeboxos.internal.api.rest;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.FreeboxException;
+
+/**
+ * The {@link NetShareManager} is the Java class used to handle api requests related to network shares
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class NetShareManager extends RestManager {
+    private static final String PATH = "netshare";
+
+    public NetShareManager(FreeboxOsSession session) throws FreeboxException {
+        super(session, LoginManager.Permission.NONE, session.getUriBuilder().path(PATH));
+        session.addManager(SambaManager.class, new SambaManager(session, getUriBuilder()));
+        session.addManager(AfpManager.class, new AfpManager(session, getUriBuilder()));
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/PhoneManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/PhoneManager.java
new file mode 100644 (file)
index 0000000..dc797a1
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/PlayerManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/PlayerManager.java
new file mode 100644 (file)
index 0000000..7ef7907
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/RepeaterManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/RepeaterManager.java
new file mode 100644 (file)
index 0000000..d76b4a6
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/RestManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/RestManager.java
new file mode 100644 (file)
index 0000000..bb1a57c
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/SambaManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/SambaManager.java
new file mode 100644 (file)
index 0000000..f947977
--- /dev/null
@@ -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();
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/SystemManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/SystemManager.java
new file mode 100644 (file)
index 0000000..2ed5b0a
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/UPnPAVManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/UPnPAVManager.java
new file mode 100644 (file)
index 0000000..035df9a
--- /dev/null
@@ -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();
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/VmManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/VmManager.java
new file mode 100644 (file)
index 0000000..07b75d6
--- /dev/null
@@ -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");
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/WebSocketManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/WebSocketManager.java
new file mode 100644 (file)
index 0000000..9607c7b
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/WifiManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/WifiManager.java
new file mode 100644 (file)
index 0000000..d5a5f09
--- /dev/null
@@ -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();
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/ApiConsumerConfiguration.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/ApiConsumerConfiguration.java
new file mode 100644 (file)
index 0000000..2435ccd
--- /dev/null
@@ -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;
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/ClientConfiguration.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/ClientConfiguration.java
new file mode 100644 (file)
index 0000000..116375e
--- /dev/null
@@ -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;
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/FreeboxOsConfiguration.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/FreeboxOsConfiguration.java
new file mode 100644 (file)
index 0000000..b079080
--- /dev/null
@@ -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();
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/FreeplugConfigurationBuilder.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/FreeplugConfigurationBuilder.java
new file mode 100644 (file)
index 0000000..0de2a2f
--- /dev/null
@@ -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());
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/HostConfiguration.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/HostConfiguration.java
new file mode 100644 (file)
index 0000000..c69194e
--- /dev/null
@@ -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();
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/LandlineConfiguration.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/LandlineConfiguration.java
new file mode 100644 (file)
index 0000000..8c80cb1
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/NodeConfigurationBuilder.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/NodeConfigurationBuilder.java
new file mode 100644 (file)
index 0000000..9aedc88
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/PhoneConfigurationBuilder.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/PhoneConfigurationBuilder.java
new file mode 100644 (file)
index 0000000..226171b
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/PlayerConfiguration.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/PlayerConfiguration.java
new file mode 100644 (file)
index 0000000..d67a735
--- /dev/null
@@ -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 = "";
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/console/FreeboxOsCommandExtension.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/console/FreeboxOsCommandExtension.java
new file mode 100644 (file)
index 0000000..3303d95
--- /dev/null
@@ -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)));
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/discovery/ApiDiscoveryParticipant.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/discovery/ApiDiscoveryParticipant.java
new file mode 100644 (file)
index 0000000..0f3624c
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/discovery/FreeboxOsDiscoveryService.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/discovery/FreeboxOsDiscoveryService.java
new file mode 100644 (file)
index 0000000..f81b2dc
--- /dev/null
@@ -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());
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ActivePlayerHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ActivePlayerHandler.java
new file mode 100644 (file)
index 0000000..bc7d781
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/AirMediaSink.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/AirMediaSink.java
new file mode 100644 (file)
index 0000000..7d0d480
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/AlarmHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/AlarmHandler.java
new file mode 100644 (file)
index 0000000..ba90fda
--- /dev/null
@@ -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;
+        };
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ApiConsumerHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ApiConsumerHandler.java
new file mode 100644 (file)
index 0000000..ed4a699
--- /dev/null
@@ -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();
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ApiConsumerIntf.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ApiConsumerIntf.java
new file mode 100644 (file)
index 0000000..d1c43ca
--- /dev/null
@@ -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();
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/BasicShutterHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/BasicShutterHandler.java
new file mode 100644 (file)
index 0000000..4a51d8b
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/CallHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/CallHandler.java
new file mode 100644 (file)
index 0000000..780f9fa
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/CameraHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/CameraHandler.java
new file mode 100644 (file)
index 0000000..e237317
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/DectHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/DectHandler.java
new file mode 100644 (file)
index 0000000..ae89dc2
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/FreeDeviceIntf.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/FreeDeviceIntf.java
new file mode 100644 (file)
index 0000000..20dc134
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/FreeboxOsHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/FreeboxOsHandler.java
new file mode 100644 (file)
index 0000000..bb62c4f
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/FreeplugHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/FreeplugHandler.java
new file mode 100644 (file)
index 0000000..9849ae5
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/FxsHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/FxsHandler.java
new file mode 100644 (file)
index 0000000..2486c3b
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/HomeNodeHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/HomeNodeHandler.java
new file mode 100644 (file)
index 0000000..33c8f12
--- /dev/null
@@ -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);
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/HostHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/HostHandler.java
new file mode 100644 (file)
index 0000000..d796654
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/KeyfobHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/KeyfobHandler.java
new file mode 100644 (file)
index 0000000..96c601f
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/PlayerHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/PlayerHandler.java
new file mode 100644 (file)
index 0000000..c4eb959
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/RepeaterHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/RepeaterHandler.java
new file mode 100644 (file)
index 0000000..6b345a8
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/RevolutionHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/RevolutionHandler.java
new file mode 100644 (file)
index 0000000..1771690
--- /dev/null
@@ -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);
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ServerHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ServerHandler.java
new file mode 100644 (file)
index 0000000..f6d607e
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ShutterHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ShutterHandler.java
new file mode 100644 (file)
index 0000000..20644ad
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/VmHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/VmHandler.java
new file mode 100644 (file)
index 0000000..2f1ab7b
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/WifiStationHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/WifiStationHandler.java
new file mode 100644 (file)
index 0000000..c22c9ad
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/addon/addon.xml
new file mode 100644 (file)
index 0000000..9c9b553
--- /dev/null
@@ -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>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/bridge-config.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/bridge-config.xml
new file mode 100644 (file)
index 0000000..4c5f6be
--- /dev/null
@@ -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>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/home-node-config.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/home-node-config.xml
new file mode 100644 (file)
index 0000000..e280240
--- /dev/null
@@ -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>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/host-config.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/host-config.xml
new file mode 100644 (file)
index 0000000..ac30732
--- /dev/null
@@ -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>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/phone-config.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/phone-config.xml
new file mode 100644 (file)
index 0000000..430d95b
--- /dev/null
@@ -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>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/player-config.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/player-config.xml
new file mode 100644 (file)
index 0000000..77eb768
--- /dev/null
@@ -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>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/repeater-config.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/repeater-config.xml
new file mode 100644 (file)
index 0000000..b789896
--- /dev/null
@@ -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>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/server-config.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/server-config.xml
new file mode 100644 (file)
index 0000000..7bdfad5
--- /dev/null
@@ -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>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/vm-config.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/config/vm-config.xml
new file mode 100644 (file)
index 0000000..46fad86
--- /dev/null
@@ -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>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/i18n/freeboxos.properties b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/i18n/freeboxos.properties
new file mode 100644 (file)
index 0000000..86b59db
--- /dev/null
@@ -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
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/api-bridge-type.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/api-bridge-type.xml
new file mode 100644 (file)
index 0000000..cf0f95d
--- /dev/null
@@ -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>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/channel-types.xml
new file mode 100644 (file)
index 0000000..b785f83
--- /dev/null
@@ -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>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/freeplug-thing-type.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/freeplug-thing-type.xml
new file mode 100644 (file)
index 0000000..89e5e31
--- /dev/null
@@ -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>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/home-thing-type.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/home-thing-type.xml
new file mode 100644 (file)
index 0000000..6c081fa
--- /dev/null
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <thing-type id="basic-shutter">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="api"/>
+               </supported-bridge-type-refs>
+
+               <label>Freebox Home Basic Shutter</label>
+               <description>The Basic Shutter (UP,DOWN,STOP) configured in your Freebox Delta Server</description>
+
+               <channels>
+                       <channel id="state" typeId="basic-shutter"/>
+               </channels>
+
+               <representation-property>id</representation-property>
+
+               <config-description-ref uri="thing-type:freeboxos:home-node"/>
+       </thing-type>
+
+       <thing-type id="alarm">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="api"/>
+               </supported-bridge-type-refs>
+
+               <label>Freebox Alarm</label>
+               <description>The Alarm system configured in your Freebox Delta Server</description>
+
+               <channels>
+                       <channel id="pin" typeId="alarm-pin"/>
+                       <channel id="sound" typeId="alarm-volume">
+                               <label>Bips Volume</label>
+                       </channel>
+                       <channel id="volume" typeId="alarm-volume">
+                               <label>Alarm Volume</label>
+                       </channel>
+                       <channel id="timeout1" typeId="alarm-timeout">
+                               <label>Alarm Activation Duration</label>
+                       </channel>
+                       <channel id="timeout2" typeId="alarm-timeout">
+                               <label>Safe Zone Alert Timeout</label>
+                       </channel>
+                       <channel id="timeout3" typeId="alarm-timeout">
+                               <label>Alert Duration</label>
+                       </channel>
+                       <channel id="battery" typeId="system.battery-level"/>
+               </channels>
+
+               <representation-property>id</representation-property>
+
+               <config-description-ref uri="thing-type:freeboxos:home-node"/>
+       </thing-type>
+
+       <thing-type id="kfb">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="api"/>
+               </supported-bridge-type-refs>
+
+
+               <label>Freebox Keyfob</label>
+               <description>A keyfob configured for your Freebox Security system</description>
+
+               <channels>
+                       <channel id="enable" typeId="keyfob-enable"/>
+                       <channel id="battery" typeId="system.battery-level"/>
+               </channels>
+
+               <representation-property>id</representation-property>
+
+               <config-description-ref uri="thing-type:freeboxos:home-node"/>
+       </thing-type>
+
+       <thing-type id="shutter">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="api"/>
+               </supported-bridge-type-refs>
+
+               <label>Freebox Home Shutter</label>
+               <description>The Shutter configured in your Freebox Delta Server</description>
+
+               <channels>
+                       <channel id="position-set" typeId="shutter"/>
+               </channels>
+
+               <representation-property>id</representation-property>
+
+               <config-description-ref uri="thing-type:freeboxos:home-node"/>
+       </thing-type>
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/host-channel-groups.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/host-channel-groups.xml
new file mode 100644 (file)
index 0000000..cfa6980
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <channel-group-type id="connectivity">
+               <label>Network Connectivity</label>
+               <channels>
+                       <channel id="reachable" typeId="reachable"/>
+                       <channel id="last-seen" typeId="timestamp">
+                               <label>Last Activity</label>
+                       </channel>
+                       <channel id="ip-address" typeId="ip-address">
+                               <label>IP Address</label>
+                               <description>IPv4 Address of the host</description>
+                       </channel>
+               </channels>
+       </channel-group-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/host-thing-type.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/host-thing-type.xml
new file mode 100644 (file)
index 0000000..e1db309
--- /dev/null
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <thing-type id="host">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="api"/>
+               </supported-bridge-type-refs>
+
+               <label>Network Device</label>
+               <description>Provides network device reachability</description>
+
+               <channel-groups>
+                       <channel-group typeId="connectivity" id="connectivity"/>
+               </channel-groups>
+
+               <representation-property>macAddress</representation-property>
+
+               <config-description-ref uri="thing-type:freeboxos:host"/>
+       </thing-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/phone-channel-groups.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/phone-channel-groups.xml
new file mode 100644 (file)
index 0000000..b93b4c3
--- /dev/null
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <channel-group-type id="accepted">
+               <label>Accepted Call</label>
+               <description>The last accepted phone call</description>
+               <channels>
+                       <channel id="number" typeId="number">
+                               <label>Calling Number</label>
+                               <description>Caller phone number</description>
+                       </channel>
+                       <channel id="duration" typeId="duration">
+                               <label>Incoming Call Duration</label>
+                       </channel>
+                       <channel id="timestamp" typeId="timestamp">
+                               <label>Incoming Call Timestamp</label>
+                       </channel>
+                       <channel id="name" typeId="name">
+                               <label>Accepted Caller</label>
+                               <description>Caller name</description>
+                       </channel>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="missed">
+               <label>Missed Call</label>
+               <description>The last missed phone call</description>
+               <channels>
+                       <channel id="number" typeId="number">
+                               <label>Missed Calling Number</label>
+                               <description>Caller phone number</description>
+                       </channel>
+                       <channel id="timestamp" typeId="timestamp">
+                               <label>Missed Call Timestamp</label>
+                       </channel>
+                       <channel id="name" typeId="name">
+                               <label>Missed Caller</label>
+                               <description>Caller name</description>
+                       </channel>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="incoming">
+               <label>Incoming Call</label>
+               <description>Currently presented phone call</description>
+               <channels>
+                       <channel id="number" typeId="number">
+                               <label>Calling Number</label>
+                               <description>Caller phone number</description>
+                       </channel>
+                       <channel id="timestamp" typeId="timestamp">
+                               <label>Call Timestamp</label>
+                       </channel>
+                       <channel id="name" typeId="name">
+                               <label>Incoming Caller</label>
+                               <description>Caller name</description>
+                       </channel>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="outgoing">
+               <label>Outgoing Call</label>
+               <description>The last outgoing phone call</description>
+               <channels>
+                       <channel id="number" typeId="number">
+                               <label>Called Number</label>
+                               <description>Called phone number</description>
+                       </channel>
+                       <channel id="duration" typeId="duration">
+                               <label>Outgoing Call Duration</label>
+                       </channel>
+                       <channel id="timestamp" typeId="timestamp">
+                               <label>Outgoing Call Timestamp</label>
+                       </channel>
+                       <channel id="name" typeId="name">
+                               <label>Called Name</label>
+                               <description>Name, if known, of the called person</description>
+                       </channel>
+               </channels>
+       </channel-group-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/phone-thing-type.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/phone-thing-type.xml
new file mode 100644 (file)
index 0000000..2a0e53b
--- /dev/null
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <thing-type id="fxs">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="api"/>
+               </supported-bridge-type-refs>
+
+               <label>Landline</label>
+               <description>Provides various informations regarding the landline state</description>
+
+               <channels>
+                       <channel id="telephony-service" typeId="telephony-service"/>
+                       <channel id="onhook" typeId="onhook"/>
+                       <channel id="ringing" typeId="ringing"/>
+                       <channel id="hardware-status" typeId="hardware-status"/>
+               </channels>
+
+               <representation-property>id</representation-property>
+
+               <config-description-ref uri="thing-type:freeboxos:phone"/>
+       </thing-type>
+
+       <thing-type id="dect">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="api"/>
+               </supported-bridge-type-refs>
+
+               <label>DECT</label>
+               <description>Provides various informations regarding the DECT state and configuration</description>
+
+               <channels>
+                       <channel id="telephony-service" typeId="telephony-service"/>
+                       <channel id="dect-active" typeId="dect-active"/>
+                       <channel id="alternate-ring" typeId="alternate-ring"/>
+                       <channel id="ringing" typeId="ringing"/>
+                       <channel id="hardware-status" typeId="hardware-status"/>
+                       <channel id="gain-rx" typeId="gain">
+                               <label>Gain RX</label>
+                       </channel>
+                       <channel id="gain-tx" typeId="gain">
+                               <label>Gain TX</label>
+                       </channel>
+               </channels>
+
+               <representation-property>id</representation-property>
+
+               <config-description-ref uri="thing-type:freeboxos:phone"/>
+       </thing-type>
+
+       <thing-type id="call">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="api"/>
+               </supported-bridge-type-refs>
+
+               <label>Calls</label>
+               <description>Provides various informations regarding the phone calls</description>
+
+               <channel-groups>
+                       <channel-group typeId="incoming" id="incoming"/>
+                       <channel-group typeId="accepted" id="accepted"/>
+                       <channel-group typeId="missed" id="missed"/>
+                       <channel-group typeId="outgoing" id="outgoing"/>
+               </channel-groups>
+
+               <config-description-ref uri="thing-type:freeboxos:call"/>
+       </thing-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/player-channel-groups.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/player-channel-groups.xml
new file mode 100644 (file)
index 0000000..9a4f7e2
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <channel-group-type id="player-actions">
+               <label>Player Actions</label>
+               <channels>
+                       <channel id="key-code" typeId="key-code"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="player-status">
+               <label>Player Status</label>
+               <channels>
+                       <channel id="player-status" typeId="player-status"/>
+                       <channel id="package" typeId="package"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="player-sysinfo">
+               <label>System Informations</label>
+               <channels>
+                       <channel id="uptime" typeId="uptime"/>
+                       <channel id="box-event" typeId="box-event"/>
+               </channels>
+       </channel-group-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/player-thing-type.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/player-thing-type.xml
new file mode 100644 (file)
index 0000000..e6c94f1
--- /dev/null
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <thing-type id="player">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="api"/>
+               </supported-bridge-type-refs>
+
+               <label>Freebox Player</label>
+               <description>The player is the device connected to your TV</description>
+
+               <channel-groups>
+                       <channel-group typeId="player-actions" id="player-actions"/>
+                       <channel-group typeId="sysinfo" id="sysinfo"/>
+                       <channel-group typeId="connectivity" id="connectivity"/>
+               </channel-groups>
+
+               <representation-property>macAddress</representation-property>
+
+               <config-description-ref uri="thing-type:freeboxos:player"/>
+       </thing-type>
+
+       <thing-type id="active-player">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="api"/>
+               </supported-bridge-type-refs>
+
+               <label>Freebox Player</label>
+               <description>The player is the device connected to your TV with API capabilities</description>
+
+               <channel-groups>
+                       <channel-group typeId="player-actions" id="player-actions"/>
+                       <channel-group typeId="player-status" id="player-status"/>
+                       <channel-group typeId="sysinfo" id="sysinfo"/>
+                       <channel-group typeId="connectivity" id="connectivity"/>
+               </channel-groups>
+
+               <representation-property>macAddress</representation-property>
+
+               <config-description-ref uri="thing-type:freeboxos:player"/>
+       </thing-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/repeater-channel-groups.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/repeater-channel-groups.xml
new file mode 100644 (file)
index 0000000..ea74f3d
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <channel-group-type id="repeater-misc">
+               <label>Repeater Settings</label>
+               <channels>
+                       <channel id="uptime" typeId="uptime"/>
+                       <channel id="led" typeId="led"/>
+                       <channel id="connection-status" typeId="connection-status"/>
+                       <channel id="host-count" typeId="host-count"/>
+                       <channel id="box-event" typeId="box-event">
+                               <label>Repeater Event</label>
+                       </channel>
+               </channels>
+       </channel-group-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/repeater-thing-type.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/repeater-thing-type.xml
new file mode 100644 (file)
index 0000000..326c98b
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <thing-type id="repeater">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="api"/>
+               </supported-bridge-type-refs>
+
+               <label>Wifi Repeater</label>
+               <description>Provides informations and control over a Wifi Repeater</description>
+
+               <channel-groups>
+                       <channel-group typeId="connectivity" id="connectivity"/>
+                       <channel-group typeId="repeater-misc" id="repeater-misc"/>
+               </channel-groups>
+
+               <representation-property>macAddress</representation-property>
+
+               <config-description-ref uri="thing-type:freeboxos:repeater"/>
+       </thing-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/server-channel-groups.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/server-channel-groups.xml
new file mode 100644 (file)
index 0000000..50ed9b4
--- /dev/null
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <channel-group-type id="sensors">
+               <label>System Sensors</label>
+       </channel-group-type>
+
+       <channel-group-type id="fans">
+               <label>Fans</label>
+       </channel-group-type>
+
+       <channel-group-type id="sysinfo">
+               <label>System Informations</label>
+               <channels>
+                       <channel id="uptime" typeId="uptime"/>
+                       <channel id="ip-address" typeId="ip-address">
+                               <label>Internal IP</label>
+                               <description>Internal IPv4 Address of the host</description>
+                       </channel>
+                       <channel id="box-event" typeId="box-event"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="actions">
+               <label>Server Settings</label>
+               <channels>
+                       <channel id="wifi-status" typeId="wifi-status"/>
+                       <channel id="upnpav-status" typeId="upnpav-status"/>
+                       <channel id="airmedia-status" typeId="airmedia-status"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="file-sharing">
+               <label>File Sharing</label>
+               <channels>
+                       <channel id="ftp-status" typeId="ftp-status"/>
+                       <channel id="samba-file-status" typeId="samba-file-status"/>
+                       <channel id="samba-printer-status" typeId="samba-printer-status"/>
+                       <channel id="afp-file-status" typeId="afp-file-status"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="display">
+               <label>Front Display Panel</label>
+               <channels>
+                       <channel id="lcd-brightness" typeId="lcd-brightness"/>
+                       <channel id="lcd-orientation" typeId="lcd-orientation"/>
+                       <channel id="lcd-forced" typeId="lcd-forced"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="connection-status">
+               <label>Connection Status Details</label>
+               <channels>
+                       <channel id="line-status" typeId="line-status"/>
+                       <channel id="line-type" typeId="line-type"/>
+                       <channel id="line-media" typeId="line-media"/>
+                       <channel id="ip-address" typeId="ip-address">
+                               <label>Public IPv4</label>
+                               <description>Public IPv4 Address of the Freebox (this field is only available when connection state is up)</description>
+                       </channel>
+                       <channel id="ipv6-address" typeId="ip-address">
+                               <label>Public IPv6</label>
+                               <description>Public IPv6 Address of the Freebox (this field is only available when connection state is up)</description>
+                       </channel>
+                       <channel id="bandwidth-up" typeId="transfer-rate">
+                               <label>Bandwidth Up</label>
+                               <description>Raw value of the upload bandwidth currently used</description>
+                       </channel>
+                       <channel id="bandwidth-down" typeId="transfer-rate">
+                               <label>Bandwidth Down</label>
+                               <description>Raw value of the download bandwidth currently used</description>
+                       </channel>
+                       <channel id="bandwidth-usage-up" typeId="bandwidth-usage">
+                               <label>Bandwidth Usage Up</label>
+                               <description>Portion of the upload bandwidth currently used</description>
+                       </channel>
+                       <channel id="bandwidth-usage-down" typeId="bandwidth-usage">
+                               <label>Bandwidth Usage Down</label>
+                               <description>Portion of the download bandwidth currently used</description>
+                       </channel>
+                       <channel id="rate-up" typeId="transfer-rate">
+                               <label>Upload Rate</label>
+                               <description>Current upload rate</description>
+                       </channel>
+                       <channel id="rate-down" typeId="transfer-rate">
+                               <label>Download Rate</label>
+                               <description>Current download rate</description>
+                       </channel>
+                       <channel id="bytes-up" typeId="transfer-bytes">
+                               <label>Uploaded</label>
+                               <description>Total data uploaded since last restart</description>
+                       </channel>
+                       <channel id="bytes-down" typeId="transfer-bytes">
+                               <label>Downloaded</label>
+                               <description>Total data downloaded since last restart</description>
+                       </channel>
+               </channels>
+       </channel-group-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/server-thing-type.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/server-thing-type.xml
new file mode 100644 (file)
index 0000000..faafbb2
--- /dev/null
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <thing-type id="revolution">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="api"/>
+               </supported-bridge-type-refs>
+
+               <label>Freebox Revolution</label>
+               <description>Provides various informations regarding the status of the Freebox Revolution Server</description>
+
+               <channel-groups>
+                       <channel-group typeId="display" id="display"/>
+                       <channel-group typeId="sensors" id="sensors"/>
+                       <channel-group typeId="fans" id="fans"/>
+                       <channel-group typeId="file-sharing" id="file-sharing"/>
+                       <channel-group typeId="sysinfo" id="sysinfo"/>
+                       <channel-group typeId="actions" id="actions"/>
+                       <channel-group typeId="connection-status" id="connection-status"/>
+               </channel-groups>
+
+               <config-description-ref uri="thing-type:freeboxos:server"/>
+       </thing-type>
+
+       <thing-type id="delta">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="api"/>
+               </supported-bridge-type-refs>
+
+               <label>Freebox Delta</label>
+               <description>Provides various informations regarding the status of the Freebox Delta Server</description>
+
+               <channel-groups>
+                       <channel-group typeId="sensors" id="sensors"/>
+                       <channel-group typeId="fans" id="fans"/>
+                       <channel-group typeId="file-sharing" id="file-sharing"/>
+                       <channel-group typeId="sysinfo" id="sysinfo"/>
+                       <channel-group typeId="actions" id="actions"/>
+                       <channel-group typeId="connection-status" id="connection-status"/>
+               </channel-groups>
+
+               <config-description-ref uri="thing-type:freeboxos:server"/>
+       </thing-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/vm-channel-groups.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/vm-channel-groups.xml
new file mode 100644 (file)
index 0000000..6c3092c
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <channel-group-type id="vmstatus">
+               <label>VM Status</label>
+               <channels>
+                       <channel id="status" typeId="status"/>
+               </channels>
+       </channel-group-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/vm-thing-type.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/vm-thing-type.xml
new file mode 100644 (file)
index 0000000..ac5a742
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <thing-type id="vm">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="api"/>
+               </supported-bridge-type-refs>
+
+               <label>Virtual Machine</label>
+               <description>Provides informations and control over virtual machine hosted on the server</description>
+
+               <channel-groups>
+                       <channel-group typeId="connectivity" id="connectivity"/>
+                       <channel-group typeId="vmstatus" id="vmstatus"/>
+               </channel-groups>
+
+               <representation-property>macAddress</representation-property>
+
+               <config-description-ref uri="thing-type:freeboxos:vm"/>
+       </thing-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/wifi-channel-groups.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/wifi-channel-groups.xml
new file mode 100644 (file)
index 0000000..fb52a35
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <channel-group-type id="wifi">
+               <label>Wifi Related Information</label>
+               <channels>
+                       <channel id="rssi" typeId="rssi"/>
+                       <channel id="wifi-quality" typeId="system.signal-strength"/>
+                       <channel id="ssid" typeId="ssid"/>
+                       <channel id="wifi-host" typeId="wifi-host"/>
+                       <channel id="rate-up" typeId="transfer-rate-bps">
+                               <label>Tx Rate</label>
+                               <description>Current TX Rate</description>
+                       </channel>
+                       <channel id="rate-down" typeId="transfer-rate-bps">
+                               <label>Rx Rate</label>
+                               <description>Current RX rate</description>
+                       </channel>
+               </channels>
+       </channel-group-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/wifihost-thing-type.xml b/bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/wifihost-thing-type.xml
new file mode 100644 (file)
index 0000000..581ba2c
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="freeboxos"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <thing-type id="wifihost">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="api"/>
+               </supported-bridge-type-refs>
+
+               <label>Wifi Device</label>
+               <description>Provides Wifi device reachability</description>
+
+               <channel-groups>
+                       <channel-group typeId="connectivity" id="connectivity"/>
+                       <channel-group typeId="wifi" id="wifi"/>
+               </channel-groups>
+
+               <representation-property>macAddress</representation-property>
+
+               <config-description-ref uri="thing-type:freeboxos:host"/>
+       </thing-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/freeboxECCRootCA.crt b/bundles/org.openhab.binding.freeboxos/src/main/resources/freeboxECCRootCA.crt
new file mode 100644 (file)
index 0000000..eab91f6
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----\r
+MIICWTCCAd+gAwIBAgIJAMaRcLnIgyukMAoGCCqGSM49BAMCMGExCzAJBgNVBAYT\r
+AkZSMQ8wDQYDVQQIDAZGcmFuY2UxDjAMBgNVBAcMBVBhcmlzMRMwEQYDVQQKDApG\r
+cmVlYm94IFNBMRwwGgYDVQQDDBNGcmVlYm94IEVDQyBSb290IENBMB4XDTE1MDkw\r
+MTE4MDIwN1oXDTM1MDgyNzE4MDIwN1owYTELMAkGA1UEBhMCRlIxDzANBgNVBAgM\r
+BkZyYW5jZTEOMAwGA1UEBwwFUGFyaXMxEzARBgNVBAoMCkZyZWVib3ggU0ExHDAa\r
+BgNVBAMME0ZyZWVib3ggRUNDIFJvb3QgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNi\r
+AASCjD6ZKn5ko6cU5Vxh8GA1KqRi6p2GQzndxHtuUmwY8RvBbhZ0GIL7bQ4f08ae\r
+JOv0ycWjEW0fyOnAw6AYdsN6y1eNvH2DVfoXQyGoCSvXQNAUxla+sJuLGICRYiZz\r
+mnijYzBhMB0GA1UdDgQWBBTIB3c2GlbV6EIh2ErEMJvFxMz/QTAfBgNVHSMEGDAW\r
+gBTIB3c2GlbV6EIh2ErEMJvFxMz/QTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB\r
+/wQEAwIBhjAKBggqhkjOPQQDAgNoADBlAjA8tzEMRVX8vrFuOGDhvZr7OSJjbBr8\r
+gl2I70LeVNGEXZsAThUkqj5Rg9bV8xw3aSMCMQCDjB5CgsLH8EdZmiksdBRRKM2r\r
+vxo6c0dSSNrr7dDN+m2/dRvgoIpGL2GauOGqDFY=\r
+-----END CERTIFICATE-----\r
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/resources/iliadboxECCRootCA.crt b/bundles/org.openhab.binding.freeboxos/src/main/resources/iliadboxECCRootCA.crt
new file mode 100644 (file)
index 0000000..12bcc60
--- /dev/null
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICOjCCAcCgAwIBAgIUI0Tu7zsrBJACQIZgLMJobtbdNn4wCgYIKoZIzj0EAwIw
+TDELMAkGA1UEBhMCSVQxDjAMBgNVBAgMBUl0YWx5MQ4wDAYDVQQKDAVJbGlhZDEd
+MBsGA1UEAwwUSWxpYWRib3ggRUNDIFJvb3QgQ0EwHhcNMjAxMTI3MDkzODEzWhcN
+NDAxMTIyMDkzODEzWjBMMQswCQYDVQQGEwJJVDEOMAwGA1UECAwFSXRhbHkxDjAM
+BgNVBAoMBUlsaWFkMR0wGwYDVQQDDBRJbGlhZGJveCBFQ0MgUm9vdCBDQTB2MBAG
+ByqGSM49AgEGBSuBBAAiA2IABMryJyb2loHNAioY8IztN5MI3UgbVHVP/vZwcnre
+ZvJOyDvE4HJgIti5qmfswlnMzpNbwf/MkT+7HAU8jJoTorRm1wtAnQ9cWD3Ebv79
+RPwtjjy3Bza3SgdVxmd6fWPUKaNjMGEwHQYDVR0OBBYEFDUij/4lpoJ+kOXRyrcM
+jf2RPzOqMB8GA1UdIwQYMBaAFDUij/4lpoJ+kOXRyrcMjf2RPzOqMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQC6eUV1
+pFh4UpJOTc1JToztN4ttnQR6rIzxMZ6mNCe+nhjkohWp24pr7BpUYSbEizYCMAQ6
+LCiBKV2j7QQGy7N1aBmdur17ZepYzR1YV0eI+Kd978aZggsmhjXENQYVTmm/XA==
+-----END CERTIFICATE-----
index 4a2b07e052c79bd6b2e1bf37e8f0f4adf2622635..0fff040f0341976e7ccce38b2db9bdd737342d63 100644 (file)
     <module>org.openhab.binding.folding</module>
     <module>org.openhab.binding.foobot</module>
     <module>org.openhab.binding.freebox</module>
+    <module>org.openhab.binding.freeboxos</module>
     <module>org.openhab.binding.fronius</module>
     <module>org.openhab.binding.fsinternetradio</module>
     <module>org.openhab.binding.ftpupload</module>