From bc4ff2fbcdc5697341f2ffceacdb0f0ef423ab47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20D=C3=B6rfler?= Date: Thu, 7 Jan 2021 21:01:43 +0100 Subject: [PATCH] [pilight] Pilight Binding migration to OHv3 + add discovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also-by: Stefan Roellin stefan@roellin-baumann.ch Signed-off-by: Niklas Dörfler --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + bundles/org.openhab.binding.pilight/NOTICE | 13 + bundles/org.openhab.binding.pilight/README.md | 107 +++++++ bundles/org.openhab.binding.pilight/pom.xml | 35 +++ .../src/main/feature/feature.xml | 23 ++ .../pilight/internal/IPilightCallback.java | 64 ++++ .../internal/PilightBindingConstants.java | 45 +++ .../internal/PilightBridgeConfiguration.java | 53 ++++ .../internal/PilightChannelConfiguration.java | 34 +++ .../pilight/internal/PilightConnector.java | 281 ++++++++++++++++++ .../internal/PilightDeviceConfiguration.java | 35 +++ .../internal/PilightHandlerFactory.java | 92 ++++++ .../PilightBridgeDiscoveryService.java | 158 ++++++++++ .../PilightDeviceDiscoveryService.java | 231 ++++++++++++++ .../binding/pilight/internal/dto/Action.java | 66 ++++ .../pilight/internal/dto/AllStatus.java | 45 +++ .../binding/pilight/internal/dto/Code.java | 60 ++++ .../binding/pilight/internal/dto/Config.java | 40 +++ .../binding/pilight/internal/dto/Device.java | 140 +++++++++ .../pilight/internal/dto/DeviceType.java | 33 ++ .../pilight/internal/dto/Identification.java | 52 ++++ .../binding/pilight/internal/dto/Message.java | 43 +++ .../binding/pilight/internal/dto/Options.java | 116 ++++++++ .../pilight/internal/dto/Response.java | 41 +++ .../binding/pilight/internal/dto/Status.java | 81 +++++ .../binding/pilight/internal/dto/Values.java | 33 ++ .../binding/pilight/internal/dto/Version.java | 36 +++ .../internal/handler/PilightBaseHandler.java | 148 +++++++++ .../handler/PilightBridgeHandler.java | 225 ++++++++++++++ .../handler/PilightContactHandler.java | 56 ++++ .../handler/PilightDimmerHandler.java | 127 ++++++++ .../handler/PilightGenericHandler.java | 130 ++++++++ .../handler/PilightSwitchHandler.java | 68 +++++ .../BooleanToIntegerSerializer.java | 41 +++ .../internal/types/PilightContactType.java | 31 ++ .../main/resources/OH-INF/binding/binding.xml | 10 + .../main/resources/OH-INF/config/config.xml | 15 + .../OH-INF/i18n/pilight_de.properties | 38 +++ .../main/resources/OH-INF/thing/bridge.xml | 39 +++ .../main/resources/OH-INF/thing/devices.xml | 118 ++++++++ bundles/pom.xml | 1 + 42 files changed, 3010 insertions(+) create mode 100644 bundles/org.openhab.binding.pilight/NOTICE create mode 100644 bundles/org.openhab.binding.pilight/README.md create mode 100644 bundles/org.openhab.binding.pilight/pom.xml create mode 100644 bundles/org.openhab.binding.pilight/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/IPilightCallback.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightBindingConstants.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightBridgeConfiguration.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightChannelConfiguration.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightConnector.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightDeviceConfiguration.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightHandlerFactory.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightBridgeDiscoveryService.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightDeviceDiscoveryService.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Action.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/AllStatus.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Code.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Config.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Device.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/DeviceType.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Identification.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Message.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Options.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Response.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Status.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Values.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Version.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBaseHandler.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBridgeHandler.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightContactHandler.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightDimmerHandler.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightGenericHandler.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightSwitchHandler.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/serializers/BooleanToIntegerSerializer.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/types/PilightContactType.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/binding/binding.xml create mode 100644 bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/config/config.xml create mode 100644 bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/i18n/pilight_de.properties create mode 100644 bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/bridge.xml create mode 100644 bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/devices.xml diff --git a/CODEOWNERS b/CODEOWNERS index 4f4cbc4e7a4a4..39a6730d6f0f8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -197,6 +197,7 @@ /bundles/org.openhab.binding.paradoxalarm/ @theater /bundles/org.openhab.binding.pentair/ @jsjames /bundles/org.openhab.binding.phc/ @gnlpfjh +/bundles/org.openhab.binding.pilight/ @stefanroellin @niklasdoerfler /bundles/org.openhab.binding.pioneeravr/ @Stratehm /bundles/org.openhab.binding.pixometer/ @Confectrician /bundles/org.openhab.binding.pjlinkdevice/ @nils diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 33a19ae51f577..fa5dee36364ba 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -971,6 +971,11 @@ org.openhab.binding.phc ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.pilight + ${project.version} + org.openhab.addons.bundles org.openhab.binding.pioneeravr diff --git a/bundles/org.openhab.binding.pilight/NOTICE b/bundles/org.openhab.binding.pilight/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.pilight/README.md b/bundles/org.openhab.binding.pilight/README.md new file mode 100644 index 0000000000000..3a67884855963 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/README.md @@ -0,0 +1,107 @@ +# pilight Binding + +The pilight binding allows openHAB to communicate with a [pilight](http://www.pilight.org/) instance running pilight version 6.0 or greater. + +> pilight is a free open source full fledge domotica solution that runs on a Raspberry Pi, HummingBoard, BananaPi, Radxa, but also on *BSD and various linuxes (tested on Arch, Ubuntu and Debian). It's open source and freely available for anyone. pilight works with a great deal of devices and is frequency independent. Therefor, it can control devices working at 315Mhz, 433Mhz, 868Mhz etc. Support for these devices are dependent on community, because we as developers don't own them all. + +pilight is a cheap way to control 'Click On Click Off' devices. It started as an application for the Raspberry Pi (using the GPIO interface) but it's also possible now to connect it to any other PC using an Arduino Nano. You will need a cheap 433Mhz transceiver in both cases. See the [Pilight manual](https://manual.pilight.org/electronics/wiring.html) for more information. + +## Supported Things + +| Thing | Type | Description | +|-----------|--------|----------------------------------------------------------------------------| +| `bridge` | Bridge | Pilight bridge required for the communication with the pilight daemon. | +| `contact` | Thing | Pilight contact (read-only). | +| `dimmer` | Thing | Pilight dimmer. | +| `switch` | Thing | Pilight switch. | +| `generic` | Thing | Pilight generic device for which you have to add the channels dynamically. | + +## Binding Configuration + +Things can be configured using Paper UI, or using a `.things` file. +The configuration in this documentation explains the `.things` file, although you can find the same parameters from the Paper UI. + +### `bridge` Thing + +A `bridge` is required for the communication with a pilight daemon. Multiple pilight instances are supported by creating different pilight `bridge` things. + +The `bridge` requires the following configuration parameters: + +| Parameter Label | Parameter ID | Description | Required | +|-----------------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------| +| IP Address | ipAddress | Host name or IP address of the pilight daemon | yes | +| Port | port | Port number on which the pilight daemon is listening. Default: 5000 | yes | +| Delay | delay | Delay (in millisecond) between consecutive commands. Recommended value without band pass filter: 1000. Recommended value with band pass filter: somewhere between 200-500. Default: 500 | no | + +Important: you must explicitly configure the port in the pilight daemon config or otherwise a random port will be used and the binding will not be able to connect. + + +### `contact`, `dimmer`, `switch`, `generic` Things + +These things have alle one required parameter: + +| Parameter Label | Parameter ID | Description | Required | +|-----------------|--------------|------------------------|----------| +| Name | name | Name of pilight device | yes | + + +## Channels + +The `bridge` thing has no channels. + +The `contact`, `dimmer` and `switch` things all have one channel: + +| Thing | Channel | Type | Description | +|-----------|----------|---------|-------------------------| +| `contact` | state | Contact | State of the contact | +| `dimmer` | dimlevel | Dimmer | Dim level of the dimmer | +| `switch` | state | Switch | State of the switch | + +The `generic` thing has no fixed channels and you have to add them manually. Currently, only String and Number channels are supported. + +## Auto Discovery + +### Bridge Auto Discovery + +The pilight daemon implements a SSDP interface, which can be used to search for running pilight daemon instances by sending a SSDP request via multicast udp (this mechanism may only work if the [standalone mode](https://manual.pilight.org/configuration/settings.html#standalone) in the pilight daemon is disabled. +After loading the binding this bridge discovery is automatically run and scheduled to scan for bridges every 10 minutes. + +### Device Auto Discovery +After a `bridge` thing has been configured in openHAB, it automatically establishes a connection between pilight daemon and openHAB. +As soon as the bridge is connected, the devices configured in the pilight daemon are automatically found via autodiscovery in background (or via a manually triggered discovery) and are displayed in the inbox to easily create things from them. + +## Examples + +things/pilight.things + +``` +Bridge pilight:bridge:raspi "Pilight Daemon raspi" [ ipAddress="192.168.1.1", port=5000 ] { + Thing switch office "Office" [ name="office" ] + Thing dimmer piano "Piano" [ name="piano" ] + Thing generic weather "Weather" [ name="weather" ] { + Channels: + State Number : temperature [ property="temperature"] + State Number : humidity [ property="humidity"] + } +} +``` + +items/pilight.items + +``` +Switch office_switch "Büro" { channel="pilight:switch:raspi:office:state" } +Dimmer piano_light "Klavier [%.0f %%]" { channel="pilight:dimmer:raspi:piano:dimlevel" } +Number weather_temperature "Aussentemperatur [%.1f °C]" { channel="pilight:generic:raspi:weather:temperature" } +Number weather_humidity "Feuchtigkeit [%.0f %%]" { channel="pilight:generic:raspi:weather:humidity" } + +``` + +sitemaps/fragment.sitemap + +``` +Switch item=office_switch +Slider item=piano_light +Text item=weather_temperature +Text item=weather_humidity +``` + diff --git a/bundles/org.openhab.binding.pilight/pom.xml b/bundles/org.openhab.binding.pilight/pom.xml new file mode 100644 index 0000000000000..54e96f45dc702 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/pom.xml @@ -0,0 +1,35 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.1.0-SNAPSHOT + + + org.openhab.binding.pilight + + openHAB Add-ons :: Bundles :: Pilight Binding + + + + com.fasterxml.jackson.core + jackson-databind + 2.10.4 + + + com.fasterxml.jackson.core + jackson-annotations + 2.10.4 + + + com.fasterxml.jackson.core + jackson-core + 2.10.4 + + + + diff --git a/bundles/org.openhab.binding.pilight/src/main/feature/feature.xml b/bundles/org.openhab.binding.pilight/src/main/feature/feature.xml new file mode 100644 index 0000000000000..41955b08252b0 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/feature/feature.xml @@ -0,0 +1,23 @@ + + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.pilight/${project.version} + + diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/IPilightCallback.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/IPilightCallback.java new file mode 100644 index 0000000000000..9caa84f3f976f --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/IPilightCallback.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.dto.Config; +import org.openhab.binding.pilight.internal.dto.Status; +import org.openhab.binding.pilight.internal.dto.Version; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; + +/** + * Callback interface to signal any listeners that an update was received from pilight + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public interface IPilightCallback { + + /** + * Update thing status + * + * @param status status of thing + * @param statusDetail status detail of thing + * @param description description of thing status + */ + void updateThingStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description); + + /** + * Update for one or more device received. + * + * @param allStatus list of Object containing list of devices that were updated and their current state + */ + void statusReceived(List allStatus); + + /** + * Configuration received. + * + * @param config Object containing configuration of pilight + */ + void configReceived(Config config); + + /** + * Version information received. + * + * @param version Object containing software version information of pilight daemon + */ + void versionReceived(Version version); +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightBindingConstants.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightBindingConstants.java new file mode 100644 index 0000000000000..bcf731315b83c --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightBindingConstants.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link PilightBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightBindingConstants { + + public static final String BINDING_ID = "pilight"; + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge"); + public static final ThingTypeUID THING_TYPE_CONTACT = new ThingTypeUID(BINDING_ID, "contact"); + public static final ThingTypeUID THING_TYPE_DIMMER = new ThingTypeUID(BINDING_ID, "dimmer"); + public static final ThingTypeUID THING_TYPE_SWITCH = new ThingTypeUID(BINDING_ID, "switch"); + public static final ThingTypeUID THING_TYPE_GENERIC = new ThingTypeUID(BINDING_ID, "generic"); + + // List of property names + public static final String PROPERTY_IP_ADDRESS = "ipAddress"; + public static final String PROPERTY_PORT = "port"; + public static final String PROPERTY_NAME = "name"; + + // List of all Channel ids + public static final String CHANNEL_STATE = "state"; + public static final String CHANNEL_DIMLEVEL = "dimlevel"; +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightBridgeConfiguration.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightBridgeConfiguration.java new file mode 100644 index 0000000000000..0a7eec03b5019 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightBridgeConfiguration.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PilightBridgeConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightBridgeConfiguration { + + private String ipAddress = ""; + private Integer port = 0; + private Integer delay = 500; + + public String getIpAddress() { + return ipAddress; + } + + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + public Integer getDelay() { + return delay; + } + + public void setDelay(Integer delay) { + this.delay = delay; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightChannelConfiguration.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightChannelConfiguration.java new file mode 100644 index 0000000000000..594eff9d77088 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightChannelConfiguration.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PilightChannelConfiguration} class contains fields mapping channel configuration parameters. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightChannelConfiguration { + private String property = ""; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightConnector.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightConnector.java new file mode 100644 index 0000000000000..2eabe46dddb99 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightConnector.java @@ -0,0 +1,281 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.net.Socket; +import java.util.Collections; +import java.util.Date; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.dto.*; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.MappingJsonFactory; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * This class listens for updates from the pilight daemon. It is also responsible for requesting + * and propagating the current pilight configuration. + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + * + */ +@NonNullByDefault +public class PilightConnector extends Thread { + + private static final Integer RECONNECT_DELAY_MSEC = 10 * 1000; // 10 seconds + + private final Logger logger = LoggerFactory.getLogger(PilightConnector.class); + + private final PilightBridgeConfiguration config; + + private final IPilightCallback callback; + + private final ObjectMapper inputMapper = new ObjectMapper( + new MappingJsonFactory().configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false)); + + private final ObjectMapper outputMapper = new ObjectMapper( + new MappingJsonFactory().configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false)) + .setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL); + + private boolean running = true; + + private @Nullable Socket socket; + private @Nullable PrintStream printStream; + + private Date lastUpdate = new Date(0); + + private ExecutorService delayedUpdateThreadPool = Executors.newSingleThreadExecutor(); + + public PilightConnector(PilightBridgeConfiguration config, IPilightCallback callback) { + this.config = config; + this.callback = callback; + setDaemon(true); + } + + @Override + public void run() { + connect(); + + while (running) { + try { + final @Nullable Socket socket = this.socket; + if (socket != null && !socket.isClosed()) { + BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + String line = in.readLine(); + while (running && line != null) { + if (!line.isEmpty()) { + logger.trace("Received from pilight: {}", line); + if (line.startsWith("{\"message\":\"config\"")) { + // Configuration received + logger.debug("Config received"); + callback.configReceived(inputMapper.readValue(line, Message.class).getConfig()); + } else if (line.startsWith("{\"message\":\"values\"")) { + AllStatus status = inputMapper.readValue(line, AllStatus.class); + callback.statusReceived(status.getValues()); + } else if (line.startsWith("{\"version\":")) { + Version version = inputMapper.readValue(line, Version.class); + callback.versionReceived(version); + logger.debug("version received: {}", line); + } else if (line.startsWith("{\"status\":")) { + // Status message, we're not using this for now. + Response response = inputMapper.readValue(line, Response.class); + logger.trace("Response success: {}", response.isSuccess()); + } else if (line.equals("1")) { + // pilight stopping + throw new IOException("Connection to pilight lost"); + } else { + Status status = inputMapper.readValue(line, Status.class); + callback.statusReceived(Collections.singletonList(status)); + } + } + line = in.readLine(); + } + } + } catch (IOException e) { + if (running) { + logger.debug("Error in pilight listener thread", e); + } + } + + logger.info("Disconnected from pilight server at {}:{}", config.getIpAddress(), config.getPort()); + + if (running) { + callback.updateThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, null); + // empty line received (socket closed) or pilight stopped but binding + // is still running, try to reconnect + connect(); + } + } + } + + /** + * Tells the connector to refresh the configuration + */ + public void refreshConfig() { + logger.trace("refreshConfig"); + doSendAction(new Action(Action.ACTION_REQUEST_CONFIG)); + } + + /** + * Tells the connector to refresh the status of all devices + */ + public void refreshStatus() { + logger.trace("refreshStatus"); + doSendAction(new Action(Action.ACTION_REQUEST_VALUES)); + } + + /** + * Stops the listener + */ + public void close() { + running = false; + disconnect(); + interrupt(); + } + + private void disconnect() { + final @Nullable PrintStream printStream = this.printStream; + if (printStream != null) { + printStream.close(); + this.printStream = null; + } + + final @Nullable Socket socket = this.socket; + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + logger.debug("Error while closing pilight socket", e); + } + this.socket = null; + } + } + + private boolean isConnected() { + final @Nullable Socket socket = this.socket; + return socket != null && !socket.isClosed(); + } + + private void connect() { + disconnect(); + + int delay = 0; + + while (!isConnected()) { + try { + logger.debug("pilight connecting to {}:{}", config.getIpAddress(), config.getPort()); + + Thread.sleep(delay); + Socket socket = new Socket(config.getIpAddress(), config.getPort()); + + Options options = new Options(); + options.setConfig(true); + + Identification identification = new Identification(); + identification.setOptions(options); + + // For some reason, directly using the outputMapper to write to the socket's OutputStream doesn't work. + PrintStream printStream = new PrintStream(socket.getOutputStream(), true); + printStream.println(outputMapper.writeValueAsString(identification)); + + Response response = inputMapper.readValue(socket.getInputStream(), Response.class); + + if (response.getStatus().equals(Response.SUCCESS)) { + logger.info("Established connection to pilight server at {}:{}", config.getIpAddress(), + config.getPort()); + this.socket = socket; + this.printStream = printStream; + callback.updateThingStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, null); + } else { + socket.close(); + logger.debug("pilight client not accepted: {}", response.getStatus()); + } + } catch (IOException e) { + logger.debug("connect failed", e); + callback.updateThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } catch (InterruptedException e) { + logger.debug("connect interrupted", e); + } + + delay = RECONNECT_DELAY_MSEC; + } + } + + /** + * send action to pilight daemon + * + * @param action action to send + */ + public synchronized void sendAction(Action action) { + DelayedUpdate delayed = new DelayedUpdate(action); + delayedUpdateThreadPool.execute(delayed); + } + + private void doSendAction(Action action) { + final @Nullable PrintStream printStream = this.printStream; + if (printStream != null) { + try { + printStream.println(outputMapper.writeValueAsString(action)); + } catch (IOException e) { + logger.debug("Error while sending action '{}' to pilight server", action.getAction(), e); + } + } else { + logger.debug("Cannot send action '{}', not connected to pilight!", action.getAction()); + } + } + + /** + * Simple thread to allow calls to pilight to be throttled + */ + private class DelayedUpdate implements Runnable { + + private final Action action; + + public DelayedUpdate(Action action) { + this.action = action; + } + + @Override + public void run() { + long delayBetweenUpdates = config.getDelay(); + + long diff = new Date().getTime() - lastUpdate.getTime(); + if (diff < delayBetweenUpdates) { + long delay = Math.min(delayBetweenUpdates - diff, config.getDelay()); + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + logger.debug("Error while processing pilight throttling delay"); + } + } + + lastUpdate = new Date(); + doSendAction(action); + } + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightDeviceConfiguration.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightDeviceConfiguration.java new file mode 100644 index 0000000000000..b5cbff1f000ab --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightDeviceConfiguration.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PilightDeviceConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightDeviceConfiguration { + + private String name = ""; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightHandlerFactory.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightHandlerFactory.java new file mode 100644 index 0000000000000..c11d4b93c1187 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightHandlerFactory.java @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal; + +import static org.openhab.binding.pilight.internal.PilightBindingConstants.*; + +import java.util.Set; +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.pilight.internal.handler.PilightBridgeHandler; +import org.openhab.binding.pilight.internal.handler.PilightContactHandler; +import org.openhab.binding.pilight.internal.handler.PilightDimmerHandler; +import org.openhab.binding.pilight.internal.handler.PilightGenericHandler; +import org.openhab.binding.pilight.internal.handler.PilightSwitchHandler; +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.openhab.core.thing.type.ChannelTypeRegistry; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link PilightHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +@Component(configurationPid = "binding.pilight", service = ThingHandlerFactory.class) +public class PilightHandlerFactory extends BaseThingHandlerFactory { + + public static final Set SUPPORTED_THING_TYPES_UIDS = Stream + .of(THING_TYPE_BRIDGE, THING_TYPE_CONTACT, THING_TYPE_DIMMER, THING_TYPE_GENERIC, THING_TYPE_SWITCH) + .collect(Collectors.toSet()); + + private final ChannelTypeRegistry channelTypeRegistry; + + @Activate + public PilightHandlerFactory(@Reference ChannelTypeRegistry channelTypeRegistry) { + this.channelTypeRegistry = channelTypeRegistry; + } + + @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 (THING_TYPE_BRIDGE.equals(thingTypeUID)) { + return new PilightBridgeHandler((Bridge) thing); + } + + if (THING_TYPE_CONTACT.equals(thingTypeUID)) { + return new PilightContactHandler(thing); + } + + if (THING_TYPE_DIMMER.equals(thingTypeUID)) { + return new PilightDimmerHandler(thing); + } + + if (THING_TYPE_GENERIC.equals(thingTypeUID)) { + return new PilightGenericHandler(thing, channelTypeRegistry); + } + + if (THING_TYPE_SWITCH.equals(thingTypeUID)) { + return new PilightSwitchHandler(thing); + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightBridgeDiscoveryService.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightBridgeDiscoveryService.java new file mode 100644 index 0000000000000..cdab653686e74 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightBridgeDiscoveryService.java @@ -0,0 +1,158 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.discovery; + +import java.io.*; +import java.net.*; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.PilightBindingConstants; +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.config.discovery.DiscoveryService; +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 PilightBridgeDiscoveryService} is responsible for discovering new pilight daemons on the network + * by sending a ssdp multicast request via udp. + * + * @author Niklas Dörfler - Initial contribution + */ +@NonNullByDefault +@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.pilight") +public class PilightBridgeDiscoveryService extends AbstractDiscoveryService { + + private static final int AUTODISCOVERY_SEARCH_TIME = 5; // in seconds + private static final int AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL = 60 * 10; // in seconds + + private static final String SSDP_DISCOVERY_REQUEST_MESSAGE = "M-SEARCH * HTTP/1.1\r\n" + + "Host:239.255.255.250:1900\r\n" + "ST:urn:schemas-upnp-org:service:pilight:1\r\n" + + "Man:\"ssdp:discover\"\r\n" + "MX:3\r\n\r\n"; + public static final String SSDP_MULTICAST_ADDRESS = "239.255.255.250"; + public static final int SSDP_PORT = 1900; + public static final int SSDP_WAIT_TIMEOUT = 2000; // in milliseconds + + private final Logger logger = LoggerFactory.getLogger(PilightBridgeDiscoveryService.class); + + private @Nullable ScheduledFuture backgroundDiscoveryJob; + + public PilightBridgeDiscoveryService() throws IllegalArgumentException { + super(getSupportedThingTypeUIDs(), AUTODISCOVERY_SEARCH_TIME, true); + } + + public static Set getSupportedThingTypeUIDs() { + return Collections.singleton(PilightBindingConstants.THING_TYPE_BRIDGE); + } + + @Override + protected void startScan() { + logger.debug("Pilight bridge discovery scan started"); + removeOlderResults(getTimestampOfLastScan()); + try { + List interfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); + for (NetworkInterface nic : interfaces) { + Enumeration inetAddresses = nic.getInetAddresses(); + for (InetAddress inetAddress : Collections.list(inetAddresses)) { + if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) { + DatagramSocket ssdp = new DatagramSocket( + new InetSocketAddress(inetAddress.getHostAddress(), 0)); + byte[] buff = SSDP_DISCOVERY_REQUEST_MESSAGE.getBytes(); + DatagramPacket sendPack = new DatagramPacket(buff, buff.length); + sendPack.setAddress(InetAddress.getByName(SSDP_MULTICAST_ADDRESS)); + sendPack.setPort(SSDP_PORT); + ssdp.send(sendPack); + ssdp.setSoTimeout(SSDP_WAIT_TIMEOUT); + + boolean loop = true; + while (loop) { + DatagramPacket recvPack = new DatagramPacket(new byte[1024], 1024); + ssdp.receive(recvPack); + byte[] recvData = recvPack.getData(); + InputStreamReader recvInput = new InputStreamReader(new ByteArrayInputStream(recvData), + StandardCharsets.UTF_8); + StringBuilder recvOutput = new StringBuilder(); + for (int value; (value = recvInput.read()) != -1;) { + recvOutput.append((char) value); + } + BufferedReader bufReader = new BufferedReader(new StringReader(recvOutput.toString())); + Pattern pattern = Pattern.compile("Location:([0-9.]+):(.*)"); + String line; + while ((line = bufReader.readLine()) != null) { + Matcher matcher = pattern.matcher(line); + if (matcher.matches()) { + String server = matcher.group(1); + Integer port = Integer.parseInt(matcher.group(2)); + loop = false; + logger.debug("Found pilight daemon at {}:{}", server, port); + + Map properties = new HashMap<>(2); + properties.put(PilightBindingConstants.PROPERTY_IP_ADDRESS, server); + properties.put(PilightBindingConstants.PROPERTY_PORT, port); + + ThingUID uid = new ThingUID(PilightBindingConstants.THING_TYPE_BRIDGE, + server.replace(".", "") + "" + port); + + DiscoveryResult result = DiscoveryResultBuilder.create(uid) + .withProperties(properties).withLabel("Pilight Bridge (" + server + ")") + .build(); + + thingDiscovered(result); + } + } + } + } + } + } + } catch (IOException e) { + if (!e.getMessage().equals("Receive timed out")) { + logger.warn("Unable to enumerate the local network interfaces {}", e.getMessage()); + } + } + } + + @Override + protected synchronized void stopScan() { + super.stopScan(); + removeOlderResults(getTimestampOfLastScan()); + } + + @Override + protected void startBackgroundDiscovery() { + logger.debug("Start Pilight device background discovery"); + if (backgroundDiscoveryJob == null || backgroundDiscoveryJob.isCancelled()) { + backgroundDiscoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 5, + AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL, TimeUnit.SECONDS); + } + } + + @Override + protected void stopBackgroundDiscovery() { + logger.debug("Stop Pilight device background discovery"); + if (backgroundDiscoveryJob != null) { + backgroundDiscoveryJob.cancel(true); + backgroundDiscoveryJob = null; + } + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightDeviceDiscoveryService.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightDeviceDiscoveryService.java new file mode 100644 index 0000000000000..14659ebf49522 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightDeviceDiscoveryService.java @@ -0,0 +1,231 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.discovery; + +import static org.openhab.binding.pilight.internal.PilightBindingConstants.*; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.PilightHandlerFactory; +import org.openhab.binding.pilight.internal.dto.Config; +import org.openhab.binding.pilight.internal.dto.DeviceType; +import org.openhab.binding.pilight.internal.dto.Status; +import org.openhab.binding.pilight.internal.handler.PilightBridgeHandler; +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.config.discovery.DiscoveryService; +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.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PilightDeviceDiscoveryService} discovers pilight devices after a bridge thing has been created and + * connected to the pilight daemon. Things are discovered periodically in the background or after a manual trigger. + * + * @author Niklas Dörfler - Initial contribution + */ +@NonNullByDefault +@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.pilight") +public class PilightDeviceDiscoveryService extends AbstractDiscoveryService + implements DiscoveryService, ThingHandlerService { + + private static final Set SUPPORTED_THING_TYPES_UIDS = new HashSet<>( + PilightHandlerFactory.SUPPORTED_THING_TYPES_UIDS); + + private static final int AUTODISCOVERY_SEARCH_TIME = 10; // in seconds + private static final int AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL = 60 * 10; // in seconds + + private final Logger logger = LoggerFactory.getLogger(PilightDeviceDiscoveryService.class); + + private @Nullable PilightBridgeHandler pilightBridgeHandler; + private @Nullable ThingUID bridgeUID; + + private @Nullable ScheduledFuture backgroundDiscoveryJob; + private CompletableFuture configFuture; + private CompletableFuture> statusFuture; + + public PilightDeviceDiscoveryService() throws IllegalArgumentException { + super(SUPPORTED_THING_TYPES_UIDS, AUTODISCOVERY_SEARCH_TIME); + configFuture = new CompletableFuture<>(); + statusFuture = new CompletableFuture<>(); + } + + @Override + protected void startScan() { + if (pilightBridgeHandler != null) { + configFuture = new CompletableFuture<>(); + statusFuture = new CompletableFuture<>(); + + CompletableFuture.allOf(configFuture, statusFuture).thenAccept(unused -> { + @Nullable + Config config = null; + @Nullable + List allStatus = null; + + try { + config = configFuture.get(); + allStatus = statusFuture.get(); + } catch (InterruptedException | ExecutionException ignored) { + } + + if (config != null && allStatus != null) { + removeOlderResults(getTimestampOfLastScan(), bridgeUID); + List finalAllStatus = allStatus; + config.getDevices().forEach((deviceId, device) -> { + if (this.pilightBridgeHandler != null) { + final @Nullable Status status = finalAllStatus.stream() + .filter(s -> deviceId.equals(s.getDevices().get(0))).findFirst().orElse(null); + + final ThingTypeUID thingTypeUID; + final String typeString; + + if (status != null) { + if (status.getType().equals(DeviceType.SWITCH)) { + thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_SWITCH.getId()); + typeString = "Switch"; + } else if (status.getType().equals(DeviceType.DIMMER)) { + thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_DIMMER.getId()); + typeString = "Dimmer"; + } else if (status.getType().equals(DeviceType.VALUE)) { + thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_GENERIC.getId()); + typeString = "Generic"; + } else if (status.getType().equals(DeviceType.CONTACT)) { + thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_CONTACT.getId()); + typeString = "Contact"; + } else { + thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_GENERIC.getId()); + typeString = "Generic"; + } + } else { + thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_GENERIC.getId()); + typeString = "Generic"; + } + + final @Nullable ThingUID thingUID = new ThingUID(thingTypeUID, + this.pilightBridgeHandler.getThing().getUID(), deviceId); + + Map properties = new HashMap<>(); + properties.put(PROPERTY_NAME, deviceId); + + DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID) + .withThingType(thingTypeUID).withProperties(properties).withBridge(bridgeUID) + .withLabel("Pilight " + typeString + " Device '" + deviceId + "'").build(); + + thingDiscovered(discoveryResult); + } + }); + } + }); + + pilightBridgeHandler.refreshConfigAndStatus(); + } + } + + @Override + protected synchronized void stopScan() { + super.stopScan(); + if (bridgeUID != null) { + removeOlderResults(getTimestampOfLastScan(), bridgeUID); + } + } + + @Override + protected void startBackgroundDiscovery() { + logger.debug("Start Pilight device background discovery"); + if (backgroundDiscoveryJob == null || backgroundDiscoveryJob.isCancelled()) { + backgroundDiscoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 5, + AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL, TimeUnit.SECONDS); + } + } + + @Override + protected void stopBackgroundDiscovery() { + logger.debug("Stop Pilight device background discovery"); + if (backgroundDiscoveryJob != null) { + backgroundDiscoveryJob.cancel(true); + backgroundDiscoveryJob = null; + } + } + + @Override + public void setThingHandler(@NonNullByDefault({}) ThingHandler handler) { + if (handler instanceof PilightBridgeHandler) { + pilightBridgeHandler = (PilightBridgeHandler) handler; + bridgeUID = pilightBridgeHandler.getThing().getUID(); + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return pilightBridgeHandler; + } + + @Override + public void activate() { + super.activate(null); + if (pilightBridgeHandler != null) { + pilightBridgeHandler.registerDiscoveryListener(this); + } + } + + @Override + public void deactivate() { + if (bridgeUID != null) { + removeOlderResults(getTimestampOfLastScan(), bridgeUID); + } + + if (pilightBridgeHandler != null) { + pilightBridgeHandler.unregisterDiscoveryListener(); + } + + super.deactivate(); + } + + /** + * Method used to get pilight device config into the discovery class. + * + * @param config config to get + */ + public void setConfig(Config config) { + if (!configFuture.isDone()) { + configFuture.complete(config); + } + } + + /** + * Method used to get pilight device status list into the discovery class. + * + * @param status list of status objects + */ + public void setStatus(List status) { + if (!statusFuture.isDone()) { + statusFuture.complete(status); + } + } + + @Override + public Set getSupportedThingTypes() { + return SUPPORTED_THING_TYPES_UIDS; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Action.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Action.java new file mode 100644 index 0000000000000..ed0a9259295db --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Action.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.dto; + +/** + * This message is sent when we want to change the state of a device or request the + * current configuration in pilight. + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class Action { + + public static final String ACTION_SEND = "send"; + + public static final String ACTION_CONTROL = "control"; + + public static final String ACTION_REQUEST_CONFIG = "request config"; + + public static final String ACTION_REQUEST_VALUES = "request values"; + + private String action; + + private Code code; + + private Options options; + + public Action(String action) { + this.action = action; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public Code getCode() { + return code; + } + + public void setCode(Code code) { + this.code = code; + } + + public Options getOptions() { + return options; + } + + public void setOptions(Options options) { + this.options = options; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/AllStatus.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/AllStatus.java new file mode 100644 index 0000000000000..69418aa581ea0 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/AllStatus.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.dto; + +import java.util.ArrayList; +import java.util.List; + +/** + * All status messages. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class AllStatus { + + private String message; + + private List values = new ArrayList<>(); + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Code.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Code.java new file mode 100644 index 0000000000000..9cc253bc1407e --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Code.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.dto; + +/** + * Part of the {@link Action} message that is sent to pilight. + * This contains the desired state for a single device. + * + * {@link http://www.pilight.org/development/api/#sender} + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class Code { + + public static final String STATE_ON = "on"; + + public static final String STATE_OFF = "off"; + + private String device; + + private String state; + + private Values values; + + public String getDevice() { + return device; + } + + public void setDevice(String device) { + this.device = device; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public Values getValues() { + return values; + } + + public void setValues(Values values) { + this.values = values; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Config.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Config.java new file mode 100644 index 0000000000000..6f368a62f1d4d --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Config.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.dto; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * pilight configuration object + * + * {@link http://www.pilight.org/development/api/#controller} + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class Config { + + private Map devices; + + public Map getDevices() { + return devices; + } + + public void setDevices(Map devices) { + this.devices = devices; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Device.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Device.java new file mode 100644 index 0000000000000..62c6c26cdcdb4 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Device.java @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.dto; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Class describing a device in pilight + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class Device { + + private String uuid; + + private String origin; + + private String timestamp; + + private List protocol; + + private String state; + + private Integer dimlevel = null; + + // @SerializedName("dimlevel-maximum") + private Integer dimlevelMaximum = null; + + private Integer dimlevelMinimum = null; + + private List> id; + + private Map properties = new HashMap<>(); + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getOrigin() { + return origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + + public List getProtocol() { + return protocol; + } + + public void setProtocol(List protocol) { + this.protocol = protocol; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public Integer getDimlevel() { + return dimlevel; + } + + public void setDimlevel(Integer dimlevel) { + this.dimlevel = dimlevel; + } + + public Integer getDimlevelMaximum() { + return dimlevelMaximum; + } + + @JsonProperty("dimlevel-maximum") + public void setDimlevelMaximum(Integer dimlevelMaximum) { + this.dimlevelMaximum = dimlevelMaximum; + } + + public Integer getDimlevelMinimum() { + return dimlevelMinimum; + } + + @JsonProperty("dimlevel-minimum") + public void setDimlevelMinimum(Integer dimlevelMinimum) { + this.dimlevelMinimum = dimlevelMinimum; + } + + public List> getId() { + return id; + } + + public void setId(List> id) { + this.id = id; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + public Map getProperties() { + return properties; + } + + @JsonAnySetter + public void set(String name, Object value) { + properties.put(name, value.toString()); + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/DeviceType.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/DeviceType.java new file mode 100644 index 0000000000000..19a1929287d7d --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/DeviceType.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.dto; + +/** + * Different types of devices in pilight + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class DeviceType { + + public static final Integer SERVER = -1; + + public static final Integer SWITCH = 1; + + public static final Integer DIMMER = 2; + + public static final Integer VALUE = 3; + + public static final Integer CONTACT = 6; +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Identification.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Identification.java new file mode 100644 index 0000000000000..749492a5d3be6 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Identification.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * This object is sent to pilight right after the initial connection. It describes what kind of client we want to be. + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class Identification { + + public static final String ACTION_IDENTIFY = "identify"; + + private String action; + + private Options options = new Options(); + + public Identification() { + this.action = ACTION_IDENTIFY; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public Options getOptions() { + return options; + } + + public void setOptions(Options options) { + this.options = options; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Message.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Message.java new file mode 100644 index 0000000000000..449f204ca47cf --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Message.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.dto; + +/** + * Wrapper for the {@code Config} object + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class Message { + + private Config config; + + private String message; + + public Config getConfig() { + return config; + } + + public void setConfig(Config config) { + this.config = config; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Options.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Options.java new file mode 100644 index 0000000000000..3f639c819969b --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Options.java @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.dto; + +import org.openhab.binding.pilight.internal.serializers.BooleanToIntegerSerializer; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +/** + * Options that can be set as a pilight client. + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class Options { + + public static final String MEDIA_ALL = "all"; + + public static final String MEDIA_WEB = "web"; + + public static final String MEDIA_MOBILE = "mobile"; + + public static final String MEDIA_DESKTOP = "desktop"; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonSerialize(using = BooleanToIntegerSerializer.class) + private Boolean core; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonSerialize(using = BooleanToIntegerSerializer.class) + private Boolean receiver; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonSerialize(using = BooleanToIntegerSerializer.class) + private Boolean config; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonSerialize(using = BooleanToIntegerSerializer.class) + private Boolean forward; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonSerialize(using = BooleanToIntegerSerializer.class) + private Boolean stats; + + private String uuid; + + private String media; + + public Boolean getCore() { + return core; + } + + public void setCore(Boolean core) { + this.core = core; + } + + public Boolean getReceiver() { + return receiver; + } + + public void setReceiver(Boolean receiver) { + this.receiver = receiver; + } + + public Boolean getConfig() { + return config; + } + + public void setConfig(Boolean config) { + this.config = config; + } + + public Boolean getForward() { + return forward; + } + + public void setForward(Boolean forward) { + this.forward = forward; + } + + public Boolean getStats() { + return stats; + } + + public void setStats(Boolean stats) { + this.stats = stats; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getMedia() { + return media; + } + + public void setMedia(String media) { + this.media = media; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Response.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Response.java new file mode 100644 index 0000000000000..bdde312ae96f3 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Response.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.dto; + +/** + * Response to a connection or state change request + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class Response { + + public static final String SUCCESS = "success"; + + public static final String FAILURE = "failure"; + + private String status; + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public boolean isSuccess() { + return SUCCESS.equals(status); + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Status.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Status.java new file mode 100644 index 0000000000000..8c5c4a3afd055 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Status.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.dto; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A Status message is received when a device in pilight changes state. + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class Status { + + private String origin; + + private Integer type; + + private String uuid; + + private List devices = new ArrayList<>(); + + private Map values = new HashMap<>(); + + public Status() { + } + + public String getOrigin() { + return origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public List getDevices() { + return devices; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public Map getValues() { + return values; + } + + public void setValues(Map values) { + this.values = values; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Values.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Values.java new file mode 100644 index 0000000000000..65798b7eacb22 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Values.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.dto; + +/** + * Describes the specific properties of a device + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class Values { + + private Integer dimlevel; + + public Integer getDimlevel() { + return dimlevel; + } + + public void setDimlevel(Integer dimlevel) { + this.dimlevel = dimlevel; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Version.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Version.java new file mode 100644 index 0000000000000..755eff8c4da79 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Version.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * pilight version information object + * + * {@link http://www.pilight.org/development/api/#controller} + * + * @author Niklas Dörfler - Initial contribution + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class Version { + + private String version; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBaseHandler.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBaseHandler.java new file mode 100644 index 0000000000000..c31918c6c5d4e --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBaseHandler.java @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.handler; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.PilightDeviceConfiguration; +import org.openhab.binding.pilight.internal.dto.Action; +import org.openhab.binding.pilight.internal.dto.Config; +import org.openhab.binding.pilight.internal.dto.Device; +import org.openhab.binding.pilight.internal.dto.Status; +import org.openhab.core.thing.*; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PilightBaseHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightBaseHandler extends BaseThingHandler { + + private final Logger logger = LoggerFactory.getLogger(PilightBaseHandler.class); + + private String name = ""; + + public PilightBaseHandler(Thing thing) { + super(thing); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + logger.debug("command refresh {}", name); + refreshConfigAndStatus(); + return; + } + + @Nullable + Action action = createUpdateCommand(channelUID, command); + if (action != null) { + sendAction(action); + } + } + + @Override + public void initialize() { + PilightDeviceConfiguration config = getConfigAs(PilightDeviceConfiguration.class); + name = config.getName(); + + logger.debug("initialize {}", name); + + refreshConfigAndStatus(); + } + + @Override + public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { + logger.debug("bridge status changed to {}.", bridgeStatusInfo.getStatus()); + + if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); + } + } + + public void updateFromStatusIfMatches(Status status) { + @Nullable + String device = status.getDevices().get(0); + + if (name.equals(device)) { + if (!ThingStatus.ONLINE.equals(getThing().getStatus())) { + updateStatus(ThingStatus.ONLINE); + } + logger.trace("update '{}'", name); + updateFromStatus(status); + } + } + + public void updateFromConfigIfMatches(Config config) { + Device device = config.getDevices().get(getName()); + if (device != null) { + updateFromConfigDevice(device); + } + } + + protected void updateFromStatus(Status status) { + // handled in derived class + } + + protected void updateFromConfigDevice(Device device) { + // may be handled in derived class + } + + protected @Nullable Action createUpdateCommand(ChannelUID channelUID, Command command) { + // handled in the derived class + return null; + } + + protected String getName() { + return name; + } + + private void sendAction(Action action) { + final @Nullable PilightBridgeHandler handler = getPilightBridgeHandler(); + if (handler != null) { + handler.sendAction(action); + } else { + logger.warn("No pilight bridge handler found to send action."); + } + } + + private void refreshConfigAndStatus() { + final @Nullable PilightBridgeHandler handler = getPilightBridgeHandler(); + if (handler != null) { + handler.refreshConfigAndStatus(); + } else { + logger.warn("No pilight bridge handler found to refresh config and status."); + } + } + + private @Nullable PilightBridgeHandler getPilightBridgeHandler() { + final @Nullable Bridge bridge = getBridge(); + if (bridge != null) { + @Nullable + BridgeHandler handler = bridge.getHandler(); + if (handler instanceof PilightBridgeHandler) { + return (PilightBridgeHandler) handler; + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBridgeHandler.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBridgeHandler.java new file mode 100644 index 0000000000000..3b8d357dbb2ea --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBridgeHandler.java @@ -0,0 +1,225 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.binding.pilight.internal.handler; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.IPilightCallback; +import org.openhab.binding.pilight.internal.PilightBridgeConfiguration; +import org.openhab.binding.pilight.internal.PilightConnector; +import org.openhab.binding.pilight.internal.discovery.PilightDeviceDiscoveryService; +import org.openhab.binding.pilight.internal.dto.*; +import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PilightBridgeHandler} is responsible dispatching commands for the child + * things to the Pilight daemon and sending status updates to the child things. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightBridgeHandler extends BaseBridgeHandler { + + private static final Integer REFRESH_CONFIG_MSEC = 500; + + private final Logger logger = LoggerFactory.getLogger(PilightBridgeHandler.class); + + private @Nullable PilightConnector connector = null; + + private @Nullable ScheduledFuture refreshJob = null; + + private @Nullable PilightDeviceDiscoveryService discoveryService = null; + + public PilightBridgeHandler(Bridge bridge) { + super(bridge); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + logger.debug("Pilight Bridge is read-only and does not handle commands."); + } + + @Override + public void initialize() { + PilightBridgeConfiguration config = getConfigAs(PilightBridgeConfiguration.class); + + PilightConnector connector = new PilightConnector(config, new IPilightCallback() { + @Override + public void updateThingStatus(ThingStatus status, ThingStatusDetail statusDetail, + @Nullable String description) { + updateStatus(status, statusDetail, description); + if (status == ThingStatus.ONLINE) { + refreshConfigAndStatus(); + } + } + + @Override + public void statusReceived(List allStatus) { + for (Status status : allStatus) { + processStatus(status); + } + + if (discoveryService != null) { + discoveryService.setStatus(allStatus); + } + } + + @Override + public void configReceived(Config config) { + processConfig(config); + } + + @Override + public void versionReceived(Version version) { + getThing().setProperty("softwareVersion", version.getVersion()); + } + }); + + updateStatus(ThingStatus.UNKNOWN); + + connector.setName("OH-binding-" + getThing().getUID().getAsString()); + connector.start(); + this.connector = connector; + } + + @Override + public void dispose() { + final @Nullable ScheduledFuture future = this.refreshJob; + if (future != null) { + future.cancel(true); + } + + final @Nullable PilightConnector connector = this.connector; + if (connector != null) { + connector.close(); + this.connector = null; + } + } + + /** + * send action to pilight daemon + * + * @param action action to send + */ + public void sendAction(Action action) { + final @Nullable PilightConnector connector = this.connector; + if (connector != null) { + connector.sendAction(action); + } + } + + /** + * refresh config and status by requesting config and all values from pilight daemon + */ + public synchronized void refreshConfigAndStatus() { + if (thing.getStatus() == ThingStatus.ONLINE) { + final @Nullable ScheduledFuture refreshJob = this.refreshJob; + if (refreshJob == null || refreshJob.isCancelled() || refreshJob.isDone()) { + logger.debug("schedule refresh of config and status"); + this.refreshJob = scheduler.schedule(this::doRefreshConfigAndStatus, REFRESH_CONFIG_MSEC, + TimeUnit.MILLISECONDS); + } + } else { + logger.warn("Bridge is not online - ignoring refresh of config and status."); + } + } + + private void doRefreshConfigAndStatus() { + logger.trace("do refresh config and status"); + final @Nullable PilightConnector connector = this.connector; + if (connector != null) { + // the config is required for dimmers to get the minimum and maximum dim levels + connector.refreshConfig(); + connector.refreshStatus(); + } + } + + /** + * Processes a status update received from pilight + * + * @param status The new Status + */ + private void processStatus(Status status) { + final Integer type = status.getType(); + logger.trace("processStatus device '{}' type {}", status.getDevices().get(0), type); + + if (!DeviceType.SERVER.equals(type)) { + for (Thing thing : getThing().getThings()) { + final @Nullable ThingHandler handler = thing.getHandler(); + if (handler instanceof PilightBaseHandler) { + ((PilightBaseHandler) handler).updateFromStatusIfMatches(status); + } + } + } + } + + @Override + public Collection> getServices() { + return Collections.singleton(PilightDeviceDiscoveryService.class); + } + + /** + * Register discovery service to this bridge instance. + */ + public boolean registerDiscoveryListener(PilightDeviceDiscoveryService listener) { + if (discoveryService == null) { + discoveryService = listener; + return true; + } + return false; + } + + /** + * Unregister discovery service from this bridge instance. + */ + public boolean unregisterDiscoveryListener() { + if (discoveryService != null) { + discoveryService = null; + return true; + } + + return false; + } + + /** + * Processes a config received from pilight + * + * @param config The new config + */ + private void processConfig(Config config) { + for (Thing thing : getThing().getThings()) { + final @Nullable ThingHandler handler = thing.getHandler(); + if (handler instanceof PilightBaseHandler) { + ((PilightBaseHandler) handler).updateFromConfigIfMatches(config); + } + } + + if (discoveryService != null) { + discoveryService.setConfig(config); + } + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightContactHandler.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightContactHandler.java new file mode 100644 index 0000000000000..c424accb1e21b --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightContactHandler.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.handler; + +import static org.openhab.binding.pilight.internal.PilightBindingConstants.CHANNEL_STATE; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.dto.Action; +import org.openhab.binding.pilight.internal.dto.Status; +import org.openhab.binding.pilight.internal.types.PilightContactType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PilightContactHandler} is responsible for handling a pilight contact. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightContactHandler extends PilightBaseHandler { + + private final Logger logger = LoggerFactory.getLogger(PilightContactHandler.class); + + public PilightContactHandler(Thing thing) { + super(thing); + } + + @Override + protected void updateFromStatus(Status status) { + String state = status.getValues().get("state"); + if (state != null) { + updateState(CHANNEL_STATE, PilightContactType.valueOf(state.toUpperCase()).toOpenClosedType()); + } + } + + @Override + protected @Nullable Action createUpdateCommand(ChannelUID channelUID, Command command) { + logger.warn("A contact is a read only device"); + return null; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightDimmerHandler.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightDimmerHandler.java new file mode 100644 index 0000000000000..f3353fa308585 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightDimmerHandler.java @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.handler; + +import static org.openhab.binding.pilight.internal.PilightBindingConstants.CHANNEL_DIMLEVEL; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.dto.Action; +import org.openhab.binding.pilight.internal.dto.Code; +import org.openhab.binding.pilight.internal.dto.Device; +import org.openhab.binding.pilight.internal.dto.Status; +import org.openhab.binding.pilight.internal.dto.Values; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PilightDimmerHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightDimmerHandler extends PilightBaseHandler { + + private static final int MAX_DIM_LEVEL_DEFAULT = 15; + + private final Logger logger = LoggerFactory.getLogger(PilightDimmerHandler.class); + + private int maxDimLevel = MAX_DIM_LEVEL_DEFAULT; + + public PilightDimmerHandler(Thing thing) { + super(thing); + } + + @Override + protected void updateFromStatus(Status status) { + BigDecimal dimLevel = BigDecimal.ZERO; + String dimLevelAsString = status.getValues().get("dimlevel"); + + if (dimLevelAsString != null) { + dimLevel = getPercentageFromDimLevel(dimLevelAsString); + } else { + // Dimmer items can can also be switched on or off in pilight. + // When this happens, the dimmer value is not reported. At least we know it's on or off. + String stateAsString = status.getValues().get("state"); + if (stateAsString != null) { + State state = OnOffType.valueOf(stateAsString.toUpperCase()); + dimLevel = state.equals(OnOffType.ON) ? new BigDecimal("100") : BigDecimal.ZERO; + } + } + + State state = new PercentType(dimLevel); + updateState(CHANNEL_DIMLEVEL, state); + } + + @Override + protected void updateFromConfigDevice(Device device) { + Integer max = device.getDimlevelMaximum(); + logger.trace("updateFromConfigDevice {}", max); + + if (max != null) { + maxDimLevel = max; + } + } + + @Override + protected @Nullable Action createUpdateCommand(ChannelUID unused, Command command) { + Code code = new Code(); + code.setDevice(getName()); + + if (command instanceof OnOffType) { + code.setState(command.equals(OnOffType.ON) ? Code.STATE_ON : Code.STATE_OFF); + } else if (command instanceof PercentType) { + setDimmerValue((PercentType) command, code); + } else { + logger.warn("Only OnOffType and PercentType are supported by a dimmer."); + return null; + } + + Action action = new Action(Action.ACTION_CONTROL); + action.setCode(code); + return action; + } + + private BigDecimal getPercentageFromDimLevel(String string) { + return new BigDecimal(string).setScale(2).divide(new BigDecimal(maxDimLevel), RoundingMode.HALF_UP) + .multiply(new BigDecimal(100)); + } + + private void setDimmerValue(PercentType percent, Code code) { + if (BigDecimal.ZERO.equals(percent.toBigDecimal())) { + // pilight is not responding to commands that set both the dimlevel to 0 and state to off. + // So, we're only updating the state for now + code.setState(Code.STATE_OFF); + } else { + BigDecimal dimlevel = percent.toBigDecimal().setScale(2) + .divide(BigDecimal.valueOf(100), RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(maxDimLevel)) + .setScale(0, RoundingMode.HALF_UP); + + Values values = new Values(); + values.setDimlevel(dimlevel.intValue()); + code.setValues(values); + code.setState(dimlevel.compareTo(BigDecimal.ZERO) == 1 ? Code.STATE_ON : Code.STATE_OFF); + } + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightGenericHandler.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightGenericHandler.java new file mode 100644 index 0000000000000..8ebd715267fec --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightGenericHandler.java @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.handler; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.PilightChannelConfiguration; +import org.openhab.binding.pilight.internal.dto.Action; +import org.openhab.binding.pilight.internal.dto.Status; +import org.openhab.core.library.CoreItemFactory; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.type.ChannelType; +import org.openhab.core.thing.type.ChannelTypeRegistry; +import org.openhab.core.thing.type.ChannelTypeUID; +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 PilightGenericHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightGenericHandler extends PilightBaseHandler { + + private final Logger logger = LoggerFactory.getLogger(PilightGenericHandler.class); + + private final ChannelTypeRegistry channelTypeRegistry; + + private final Map channelReadOnlyMap = new HashMap<>(); + + public PilightGenericHandler(Thing thing, ChannelTypeRegistry channelTypeRegistry) { + super(thing); + this.channelTypeRegistry = channelTypeRegistry; + } + + @Override + public void initialize() { + super.initialize(); + initializeReadOnlyChannels(); + } + + @Override + protected void updateFromStatus(Status status) { + for (Channel channel : thing.getChannels()) { + PilightChannelConfiguration config = channel.getConfiguration().as(PilightChannelConfiguration.class); + updateState(channel.getUID(), + getDynamicChannelState(channel, status.getValues().get(config.getProperty()))); + } + } + + @Override + protected @Nullable Action createUpdateCommand(ChannelUID channelUID, Command command) { + if (isChannelReadOnly(channelUID)) { + logger.debug("Can't apply command '{}' to '{}' because channel is readonly.", command, channelUID.getId()); + return null; + } + + logger.debug("Create update command for '{}' not implemented.", channelUID.getId()); + + return null; + } + + private State getDynamicChannelState(final Channel channel, final @Nullable String value) { + final @Nullable String acceptedItemType = channel.getAcceptedItemType(); + + if (value == null || acceptedItemType == null) { + return UnDefType.UNDEF; + } + + switch (acceptedItemType) { + case CoreItemFactory.NUMBER: + return new DecimalType(value); + case CoreItemFactory.STRING: + return StringType.valueOf(value); + case CoreItemFactory.SWITCH: + return OnOffType.from(value); + default: + logger.trace("Type '{}' for channel '{}' not implemented", channel.getAcceptedItemType(), channel); + return UnDefType.UNDEF; + } + } + + private void initializeReadOnlyChannels() { + channelReadOnlyMap.clear(); + for (Channel channel : thing.getChannels()) { + final @Nullable ChannelTypeUID channelTypeUID = channel.getChannelTypeUID(); + if (channelTypeUID != null) { + final @Nullable ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeUID, null); + + if (channelType != null) { + logger.debug("initializeReadOnly {} {}", channelType, channelType.getState()); + } + + if (channelType != null && channelType.getState() != null) { + channelReadOnlyMap.putIfAbsent(channel.getUID(), channelType.getState().isReadOnly()); + } + } + } + } + + @SuppressWarnings("null") + private boolean isChannelReadOnly(ChannelUID channelUID) { + Boolean isReadOnly = channelReadOnlyMap.get(channelUID); + return isReadOnly != null ? isReadOnly : true; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightSwitchHandler.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightSwitchHandler.java new file mode 100644 index 0000000000000..3a51ff9d548cb --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightSwitchHandler.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.handler; + +import static org.openhab.binding.pilight.internal.PilightBindingConstants.CHANNEL_STATE; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.dto.Action; +import org.openhab.binding.pilight.internal.dto.Code; +import org.openhab.binding.pilight.internal.dto.Status; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PilightSwitchHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightSwitchHandler extends PilightBaseHandler { + + private final Logger logger = LoggerFactory.getLogger(PilightSwitchHandler.class); + + public PilightSwitchHandler(Thing thing) { + super(thing); + } + + @Override + protected void updateFromStatus(Status status) { + String state = status.getValues().get("state"); + if (state != null) { + updateState(CHANNEL_STATE, OnOffType.valueOf(state.toUpperCase())); + } + } + + @Override + protected @Nullable Action createUpdateCommand(ChannelUID unused, Command command) { + if (command instanceof OnOffType) { + Code code = new Code(); + code.setDevice(getName()); + code.setState(command.equals(OnOffType.ON) ? Code.STATE_ON : Code.STATE_OFF); + + Action action = new Action(Action.ACTION_CONTROL); + action.setCode(code); + return action; + } + + logger.warn("A pilight switch only accepts OnOffType commands."); + return null; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/serializers/BooleanToIntegerSerializer.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/serializers/BooleanToIntegerSerializer.java new file mode 100644 index 0000000000000..0a28a9d0fa18c --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/serializers/BooleanToIntegerSerializer.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.serializers; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +/** + * Serializer to map boolean values to an integer (1 and 0). + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class BooleanToIntegerSerializer extends JsonSerializer { + + @Override + public void serialize(@Nullable Boolean bool, @Nullable JsonGenerator jsonGenerator, + @Nullable SerializerProvider serializerProvider) throws IOException { + if (bool != null && jsonGenerator != null) { + jsonGenerator.writeObject(bool ? 1 : 0); + } + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/types/PilightContactType.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/types/PilightContactType.java new file mode 100644 index 0000000000000..8867d83a82611 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/types/PilightContactType.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pilight.internal.types; + +import org.openhab.core.library.types.OpenClosedType; + +/** + * Enum to represent the state of a contact sensor in pilight + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public enum PilightContactType { + OPENED, + CLOSED; + + public OpenClosedType toOpenClosedType() { + return this.equals(PilightContactType.OPENED) ? OpenClosedType.OPEN : OpenClosedType.CLOSED; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/binding/binding.xml new file mode 100644 index 0000000000000..463c34d3d2a6f --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/binding/binding.xml @@ -0,0 +1,10 @@ + + + + Pilight Binding + This is the binding to communicate with a pilight daemon. + Stefan Röllin + + diff --git a/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/config/config.xml new file mode 100644 index 0000000000000..84809bcf447db --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/config/config.xml @@ -0,0 +1,15 @@ + + + + + + + The name of the pilight device. + + + + diff --git a/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/i18n/pilight_de.properties b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/i18n/pilight_de.properties new file mode 100644 index 0000000000000..2883dab5eaef9 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/i18n/pilight_de.properties @@ -0,0 +1,38 @@ +# binding +binding.pilight.name = Pilight Binding +binding.pilight.description = Das Pilight Binding erlaubt es mit pilight Daemons zu kommunizieren. + +# thing types +thing-type.pilight.bridge.label = Pilight Bridge +thing-type.pilight.bridge.description = Verbindung zwischen openHAB und einem pilight Daemon. + +thing-type.pilight.contact.label = Pilight Kontakt +thing-type.pilight.contact.description = Pilight Kontakt + +thing-type.pilight.dimmer.label = Pilight Dimmer +thing-type.pilight.dimmer.description = Pilight Dimmer + +thing-type.pilight.switch.label = Pilight Schalter +thing-type.pilight.switch.description = Pilight Schalter + +thing-type.pilight.generic.label = Generisches pilight Ger�t +thing-type.pilight.generic.description = Ger�t bei dem die Kan�le dynamisch hinzugef�gt werden. + +# thing type config description +thing-type.config.pilight.bridge.ipAddress.label = IP-Adresse +thing-type.config.pilight.bridge.ipAddress.description = Lokale IP-Adresse oder Hostname des pilight Daemons. +thing-type.config.pilight.bridge.port.label = Port +thing-type.config.pilight.bridge.port.description = Port des pilight Daemons. +thing-type.config.pilight.bridge.delay.label = Verz�gerung +thing-type.config.pilight.bridge.delay.description = Verz�gerung (in Millisekunden) zwischen zwei Kommandos. Empfohlener Wert ohne Bandpass filter: 1000 und mit Bandpass Filter zwischen 200 und 500. + +thing-type.config.pilight.device.name.label = Name +thing-type.config.pilight.device.name.description = Name des pilight Ger�ts + +# channel types +channel-type.pilight.contact-state.label = Status +channel-type.pilight.contact-state.description = Status des pilight Kontakts +channel-type.pilight.switch-state.label = Status +channel-type.pilight.switch-state.description = Status des pilight Schalters +channel-type.pilight.dimlevel.label = Dimmerwert +channel-type.pilight.dimlevel.description = Wert des pilight Dimmers diff --git a/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/bridge.xml new file mode 100644 index 0000000000000..12118b40e8237 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/bridge.xml @@ -0,0 +1,39 @@ + + + + + + Pilight Bridge which connects to a Pilight instance. + + + - + + + + + + The IP or host name of the Pilight instance. + network-address + + + + Port of the Pilight daemon. You must explicitly configure the port in the Pilight daemon config or + otherwise a random port will be used and the binding will not be able to connect. + + 5000 + + + + Delay (in millisecond) between consecutive commands. Recommended value without band pass filter: 1000. + Recommended value with band pass filter: somewhere between 200-500. + 500 + true + + + + + + diff --git a/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/devices.xml b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/devices.xml new file mode 100644 index 0000000000000..8e32b52a18c34 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/devices.xml @@ -0,0 +1,118 @@ + + + + + + + + + + Pilight Switch + + + + + + + + + + + + + + + Pilight Contact + + + + + + + + + + + + + + + Pilight Dimmer + + + + + + + + + + + + + + + Pilight Generic Device + + + + + + Contact + + State of pilight contact. + + + + + Dimmer + + Dim level of pilight dimmer. + Light + + Lighting + Switchable + + + + + Switch + + State of pilight switch. + Switch + + Lighting + Switchable + + + + + String + + + + + + The property of the device. + true + + + + + + Number + + + + + + The property of the device. + true + + + + + diff --git a/bundles/pom.xml b/bundles/pom.xml index 45d609f7d3c8b..3ff1f72aeede1 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -228,6 +228,7 @@ org.openhab.binding.paradoxalarm org.openhab.binding.pentair org.openhab.binding.phc + org.openhab.binding.pilight org.openhab.binding.pioneeravr org.openhab.binding.pixometer org.openhab.binding.pjlinkdevice