From 5ee4f93de7443d9ed2741503b2f45a1677b4652a 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 1/5] [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 c4aa2073cdd3e..13a1349aac8ad 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -204,6 +204,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 95becddb6ca41..63604ba15fcf4 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1006,6 +1006,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 79efb9c54e682..fb7fd449925ad 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -235,6 +235,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 From 24e735f565b059e561f0a292df1d4199c93af863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20D=C3=B6rfler?= Date: Fri, 5 Feb 2021 22:27:16 +0100 Subject: [PATCH 2/5] [pilight] Refactor some parts and texts of the binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Niklas Dörfler --- bundles/org.openhab.binding.pilight/README.md | 48 +++-- .../src/main/feature/feature.xml | 14 -- .../internal/PilightBridgeConfiguration.java | 6 +- .../pilight/internal/PilightConnector.java | 179 ++++++++---------- .../internal/PilightHandlerFactory.java | 7 +- .../PilightBridgeDiscoveryService.java | 69 +++---- .../PilightDeviceDiscoveryService.java | 34 ++-- .../internal/handler/PilightBaseHandler.java | 13 -- .../handler/PilightBridgeHandler.java | 16 +- .../handler/PilightDimmerHandler.java | 4 +- .../main/resources/OH-INF/binding/binding.xml | 5 +- .../OH-INF/i18n/pilight_de.properties | 4 +- .../main/resources/OH-INF/thing/bridge.xml | 2 +- .../main/resources/OH-INF/thing/devices.xml | 34 +--- 14 files changed, 189 insertions(+), 246 deletions(-) diff --git a/bundles/org.openhab.binding.pilight/README.md b/bundles/org.openhab.binding.pilight/README.md index 3a67884855963..84d35ac52ac16 100644 --- a/bundles/org.openhab.binding.pilight/README.md +++ b/bundles/org.openhab.binding.pilight/README.md @@ -1,10 +1,18 @@ # pilight Binding -The pilight binding allows openHAB to communicate with a [pilight](http://www.pilight.org/) instance running pilight version 6.0 or greater. +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 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. +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 @@ -18,12 +26,10 @@ pilight is a cheap way to control 'Click On Click Off' devices. It started as an ## 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. +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: @@ -31,20 +37,19 @@ The `bridge` requires the following configuration parameters: |-----------------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------| | 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. +| 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: +These things have all one required parameter: | Parameter Label | Parameter ID | Description | Required | |-----------------|--------------|------------------------|----------| | Name | name | Name of pilight device | yes | - ## Channels The `bridge` thing has no channels. @@ -57,20 +62,27 @@ The `contact`, `dimmer` and `switch` things all have one channel: | `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. +The `generic` thing has no fixed channels, so 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. +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 +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. + +## Full Example things/pilight.things diff --git a/bundles/org.openhab.binding.pilight/src/main/feature/feature.xml b/bundles/org.openhab.binding.pilight/src/main/feature/feature.xml index 41955b08252b0..38a968d3f2ba8 100644 --- a/bundles/org.openhab.binding.pilight/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.pilight/src/main/feature/feature.xml @@ -1,18 +1,4 @@ - mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features 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 index 0a7eec03b5019..85dce4004946f 100644 --- 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 @@ -24,8 +24,8 @@ public class PilightBridgeConfiguration { private String ipAddress = ""; - private Integer port = 0; - private Integer delay = 500; + private int port = 0; + private int delay = 500; public String getIpAddress() { return ipAddress; @@ -43,7 +43,7 @@ public void setPort(Integer port) { this.port = port; } - public Integer getDelay() { + public int getDelay() { return delay; } 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 index 2eabe46dddb99..b843108e3d317 100644 --- 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 @@ -18,9 +18,8 @@ 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 java.util.LinkedList; +import java.util.concurrent.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -45,9 +44,9 @@ * */ @NonNullByDefault -public class PilightConnector extends Thread { +public class PilightConnector implements Runnable { - private static final Integer RECONNECT_DELAY_MSEC = 10 * 1000; // 10 seconds + private static final int RECONNECT_DELAY_MSEC = 10 * 1000; // 10 seconds private final Logger logger = LoggerFactory.getLogger(PilightConnector.class); @@ -62,74 +61,74 @@ public class PilightConnector extends Thread { 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(); + private final ScheduledExecutorService scheduler; + private final LinkedList delayedActionQueue = new LinkedList<>(); + private @Nullable ScheduledFuture delayedActionWorkerFuture; - public PilightConnector(PilightBridgeConfiguration config, IPilightCallback callback) { + public PilightConnector(final PilightBridgeConfiguration config, final IPilightCallback callback, + final ScheduledExecutorService scheduler) { this.config = config; this.callback = callback; - setDaemon(true); + this.scheduler = scheduler; } @Override public void run() { - connect(); + try { + 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)); + while (!Thread.currentThread().isInterrupted()) { + try { + final Socket socket = this.socket; + if (socket != null && !socket.isClosed()) { + try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) { + String line = in.readLine(); + while (!Thread.currentThread().isInterrupted() && line != null) { + if (!line.isEmpty()) { + logger.trace("Received from pilight: {}", line); + if (line.startsWith("{\"message\":\"config\"")) { + 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); + } else if (line.startsWith("{\"status\":")) { + Response response = inputMapper.readValue(line, Response.class); + } else if (line.equals("1")) { + throw new IOException("Connection to pilight lost"); + } else { + Status status = inputMapper.readValue(line, Status.class); + callback.statusReceived(Collections.singletonList(status)); + } + } + line = in.readLine(); } } - line = in.readLine(); + } + } catch (IOException e) { + if (!Thread.currentThread().isInterrupted()) { + logger.debug("Error in pilight listener thread: {}", e.getMessage()); } } - } catch (IOException e) { - if (running) { - logger.debug("Error in pilight listener thread", e); - } - } - logger.info("Disconnected from pilight server at {}:{}", config.getIpAddress(), config.getPort()); + logger.debug("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(); + if (!Thread.currentThread().isInterrupted()) { + callback.updateThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, null); + // empty line received (socket closed) or pilight stopped but binding + // is still running, try to reconnect + connect(); + } } + + } catch (InterruptedException e) { + logger.debug("Interrupting thread."); + Thread.currentThread().interrupt(); } } @@ -137,7 +136,6 @@ public void run() { * Tells the connector to refresh the configuration */ public void refreshConfig() { - logger.trace("refreshConfig"); doSendAction(new Action(Action.ACTION_REQUEST_CONFIG)); } @@ -145,7 +143,6 @@ public void refreshConfig() { * Tells the connector to refresh the status of all devices */ public void refreshStatus() { - logger.trace("refreshStatus"); doSendAction(new Action(Action.ACTION_REQUEST_VALUES)); } @@ -153,35 +150,34 @@ public void refreshStatus() { * Stops the listener */ public void close() { - running = false; disconnect(); - interrupt(); + Thread.currentThread().interrupt(); } private void disconnect() { - final @Nullable PrintStream printStream = this.printStream; + final PrintStream printStream = this.printStream; if (printStream != null) { printStream.close(); this.printStream = null; } - final @Nullable Socket socket = this.socket; + final Socket socket = this.socket; if (socket != null) { try { socket.close(); } catch (IOException e) { - logger.debug("Error while closing pilight socket", e); + logger.debug("Error while closing pilight socket: {}", e.getMessage()); } this.socket = null; } } private boolean isConnected() { - final @Nullable Socket socket = this.socket; + final Socket socket = this.socket; return socket != null && !socket.isClosed(); } - private void connect() { + private void connect() throws InterruptedException { disconnect(); int delay = 0; @@ -206,20 +202,20 @@ private void connect() { Response response = inputMapper.readValue(socket.getInputStream(), Response.class); if (response.getStatus().equals(Response.SUCCESS)) { - logger.info("Established connection to pilight server at {}:{}", config.getIpAddress(), + logger.debug("Established connection to pilight server at {}:{}", config.getIpAddress(), config.getPort()); this.socket = socket; this.printStream = printStream; callback.updateThingStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, null); } else { + printStream.close(); socket.close(); logger.debug("pilight client not accepted: {}", response.getStatus()); } } catch (IOException e) { - logger.debug("connect failed", e); + this.printStream.close(); + logger.debug("connect failed: {}", e.getMessage()); callback.updateThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } catch (InterruptedException e) { - logger.debug("connect interrupted", e); } delay = RECONNECT_DELAY_MSEC; @@ -232,8 +228,18 @@ private void connect() { * @param action action to send */ public synchronized void sendAction(Action action) { - DelayedUpdate delayed = new DelayedUpdate(action); - delayedUpdateThreadPool.execute(delayed); + delayedActionQueue.add(action); + + if (delayedActionWorkerFuture == null || delayedActionWorkerFuture.isCancelled()) { + delayedActionWorkerFuture = scheduler.scheduleWithFixedDelay(() -> { + if (!delayedActionQueue.isEmpty()) { + doSendAction(delayedActionQueue.pop()); + } else { + delayedActionWorkerFuture.cancel(false); + delayedActionWorkerFuture = null; + } + }, 0, config.getDelay(), TimeUnit.MILLISECONDS); + } } private void doSendAction(Action action) { @@ -242,40 +248,11 @@ private void doSendAction(Action action) { try { printStream.println(outputMapper.writeValueAsString(action)); } catch (IOException e) { - logger.debug("Error while sending action '{}' to pilight server", action.getAction(), e); + logger.debug("Error while sending action '{}' to pilight server: {}", action.getAction(), + e.getMessage()); } } 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/PilightHandlerFactory.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightHandlerFactory.java index c11d4b93c1187..366b2e4b21de8 100644 --- 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 @@ -15,8 +15,6 @@ 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; @@ -47,9 +45,8 @@ @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()); + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BRIDGE, THING_TYPE_CONTACT, + THING_TYPE_DIMMER, THING_TYPE_GENERIC, THING_TYPE_SWITCH); private final ChannelTypeRegistry channelTypeRegistry; 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 index cdab653686e74..6349a410ad6a1 100644 --- 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 @@ -44,8 +44,8 @@ @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 int AUTODISCOVERY_SEARCH_TIME_SEC = 5; + private static final int AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL_SEC = 60 * 10; 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" @@ -59,7 +59,7 @@ public class PilightBridgeDiscoveryService extends AbstractDiscoveryService { private @Nullable ScheduledFuture backgroundDiscoveryJob; public PilightBridgeDiscoveryService() throws IllegalArgumentException { - super(getSupportedThingTypeUIDs(), AUTODISCOVERY_SEARCH_TIME, true); + super(getSupportedThingTypeUIDs(), AUTODISCOVERY_SEARCH_TIME_SEC, true); } public static Set getSupportedThingTypeUIDs() { @@ -90,35 +90,38 @@ protected void startScan() { 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); + try (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); + } + try (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); + } + } } } } @@ -143,7 +146,7 @@ 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); + AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL_SEC, TimeUnit.SECONDS); } } 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 index 14659ebf49522..1d80986ee84d4 100644 --- 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 @@ -50,11 +50,10 @@ 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 Set SUPPORTED_THING_TYPES_UIDS = 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 static final int AUTODISCOVERY_SEARCH_TIME_SEC = 10; + private static final int AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL_SEC = 60 * 10; private final Logger logger = LoggerFactory.getLogger(PilightDeviceDiscoveryService.class); @@ -65,8 +64,8 @@ public class PilightDeviceDiscoveryService extends AbstractDiscoveryService private CompletableFuture configFuture; private CompletableFuture> statusFuture; - public PilightDeviceDiscoveryService() throws IllegalArgumentException { - super(SUPPORTED_THING_TYPES_UIDS, AUTODISCOVERY_SEARCH_TIME); + public PilightDeviceDiscoveryService() { + super(SUPPORTED_THING_TYPES_UIDS, AUTODISCOVERY_SEARCH_TIME_SEC); configFuture = new CompletableFuture<>(); statusFuture = new CompletableFuture<>(); } @@ -78,9 +77,7 @@ protected void startScan() { statusFuture = new CompletableFuture<>(); CompletableFuture.allOf(configFuture, statusFuture).thenAccept(unused -> { - @Nullable Config config = null; - @Nullable List allStatus = null; try { @@ -94,23 +91,23 @@ protected void startScan() { 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 Optional status = finalAllStatus.stream() + .filter(s -> deviceId.equals(s.getDevices().get(0))).findFirst(); final ThingTypeUID thingTypeUID; final String typeString; - if (status != null) { - if (status.getType().equals(DeviceType.SWITCH)) { + if (status.isPresent()) { + if (status.get().getType().equals(DeviceType.SWITCH)) { thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_SWITCH.getId()); typeString = "Switch"; - } else if (status.getType().equals(DeviceType.DIMMER)) { + } else if (status.get().getType().equals(DeviceType.DIMMER)) { thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_DIMMER.getId()); typeString = "Dimmer"; - } else if (status.getType().equals(DeviceType.VALUE)) { + } else if (status.get().getType().equals(DeviceType.VALUE)) { thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_GENERIC.getId()); typeString = "Generic"; - } else if (status.getType().equals(DeviceType.CONTACT)) { + } else if (status.get().getType().equals(DeviceType.CONTACT)) { thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_CONTACT.getId()); typeString = "Contact"; } else { @@ -130,6 +127,7 @@ protected void startScan() { DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID) .withThingType(thingTypeUID).withProperties(properties).withBridge(bridgeUID) + .withRepresentationProperty(deviceId) .withLabel("Pilight " + typeString + " Device '" + deviceId + "'").build(); thingDiscovered(discoveryResult); @@ -154,8 +152,8 @@ protected synchronized void stopScan() { 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); + backgroundDiscoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 20, + AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL_SEC, TimeUnit.SECONDS); } } @@ -169,7 +167,7 @@ protected void stopBackgroundDiscovery() { } @Override - public void setThingHandler(@NonNullByDefault({}) ThingHandler handler) { + public void setThingHandler(final ThingHandler handler) { if (handler instanceof PilightBridgeHandler) { pilightBridgeHandler = (PilightBridgeHandler) handler; bridgeUID = pilightBridgeHandler.getThing().getUID(); 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 index c31918c6c5d4e..e96aa48ecffd4 100644 --- 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 @@ -48,7 +48,6 @@ public PilightBaseHandler(Thing thing) { @Override public void handleCommand(ChannelUID channelUID, Command command) { if (command instanceof RefreshType) { - logger.debug("command refresh {}", name); refreshConfigAndStatus(); return; } @@ -65,20 +64,9 @@ 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); @@ -87,7 +75,6 @@ public void updateFromStatusIfMatches(Status status) { if (!ThingStatus.ONLINE.equals(getThing().getStatus())) { updateStatus(ThingStatus.ONLINE); } - logger.trace("update '{}'", name); updateFromStatus(status); } } 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 index 3b8d357dbb2ea..fdb04b39e4a55 100644 --- 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 @@ -16,6 +16,8 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -26,6 +28,7 @@ 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.common.NamedThreadFactory; import org.openhab.core.thing.*; import org.openhab.core.thing.binding.BaseBridgeHandler; import org.openhab.core.thing.binding.ThingHandler; @@ -44,7 +47,7 @@ @NonNullByDefault public class PilightBridgeHandler extends BaseBridgeHandler { - private static final Integer REFRESH_CONFIG_MSEC = 500; + private static final int REFRESH_CONFIG_MSEC = 500; private final Logger logger = LoggerFactory.getLogger(PilightBridgeHandler.class); @@ -95,14 +98,16 @@ public void configReceived(Config config) { @Override public void versionReceived(Version version) { - getThing().setProperty("softwareVersion", version.getVersion()); + getThing().setProperty(Thing.PROPERTY_FIRMWARE_VERSION, version.getVersion()); } - }); + }, scheduler); updateStatus(ThingStatus.UNKNOWN); - connector.setName("OH-binding-" + getThing().getUID().getAsString()); - connector.start(); + final ExecutorService executor = Executors + .newSingleThreadExecutor(new NamedThreadFactory(getThing().getUID().getAsString(), true)); + executor.execute(connector); + this.connector = connector; } @@ -149,7 +154,6 @@ public synchronized void refreshConfigAndStatus() { } 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 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 index f3353fa308585..dc6c006c9f39e 100644 --- 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 @@ -44,6 +44,7 @@ public class PilightDimmerHandler extends PilightBaseHandler { private static final int MAX_DIM_LEVEL_DEFAULT = 15; + private static final BigDecimal PERCENTAGE_FROM_DIMLEVEL_MULTIPLICAND = new BigDecimal(100); private final Logger logger = LoggerFactory.getLogger(PilightDimmerHandler.class); @@ -77,7 +78,6 @@ protected void updateFromStatus(Status status) { @Override protected void updateFromConfigDevice(Device device) { Integer max = device.getDimlevelMaximum(); - logger.trace("updateFromConfigDevice {}", max); if (max != null) { maxDimLevel = max; @@ -105,7 +105,7 @@ protected void updateFromConfigDevice(Device device) { private BigDecimal getPercentageFromDimLevel(String string) { return new BigDecimal(string).setScale(2).divide(new BigDecimal(maxDimLevel), RoundingMode.HALF_UP) - .multiply(new BigDecimal(100)); + .multiply(PERCENTAGE_FROM_DIMLEVEL_MULTIPLICAND); } private void setDimmerValue(PercentType percent, Code code) { 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 index 463c34d3d2a6f..2540db4c312a0 100644 --- 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 @@ -4,7 +4,8 @@ xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd"> Pilight Binding - This is the binding to communicate with a pilight daemon. - Stefan Röllin + The pilight binding allows openHAB to communicate with a pilight instance. Pilight is a service used to + control 'Click On Click Off' devices like 433 MHz remote controlled sockets a cheap way, e.g. by using a Raspberry Pi + with corresponding 433 MHz sender. 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 index 2883dab5eaef9..b2b8e8ed5f750 100644 --- 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 @@ -1,6 +1,6 @@ # binding binding.pilight.name = Pilight Binding -binding.pilight.description = Das Pilight Binding erlaubt es mit pilight Daemons zu kommunizieren. +binding.pilight.description = Das pilight-Binding erm�glicht openHAB die Kommunikation mit einer pilight-Instanz. Pilight ist ein Dienst, der verwendet wird, um 'Click On Click Off'-Ger�te wie bspw. 433 MHz Funksteckdosen auf kosteng�nstige Weise zu steuern, z.B. durch Verwendung eines Raspberry Pi mit entsprechendem 433 MHz Sender. # thing types thing-type.pilight.bridge.label = Pilight Bridge @@ -24,7 +24,7 @@ thing-type.config.pilight.bridge.ipAddress.description = Lokale IP-Adresse oder 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.bridge.delay.description = Verz�gerung (in Millisekunden) zwischen zwei Kommandos. Empfohlener Wert ohne Bandpassfilter: 1000 und mit Bandpassfilter zwischen 200 und 500. thing-type.config.pilight.device.name.label = Name thing-type.config.pilight.device.name.description = Name des pilight Ger�ts 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 index 12118b40e8237..4ef6ce98da745 100644 --- 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 @@ -9,7 +9,7 @@ Pilight Bridge which connects to a Pilight instance. - - + - 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 index 8e32b52a18c34..2946f2cbcb458 100644 --- 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 @@ -13,7 +13,7 @@ Pilight Switch - + @@ -43,7 +43,7 @@ Pilight Dimmer - + @@ -62,33 +62,11 @@ Contact - - State of pilight contact. + + State of Pilight Contact. - - Dimmer - - Dim level of pilight dimmer. - Light - - Lighting - Switchable - - - - - Switch - - State of pilight switch. - Switch - - Lighting - Switchable - - - String @@ -96,7 +74,7 @@ - The property of the device. + The Property of the Device. true @@ -109,7 +87,7 @@ - The property of the device. + The Property of the Device. true From a6be51b7cd546a009aad6994d738f100f3ae99d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20D=C3=B6rfler?= Date: Fri, 12 Feb 2021 21:01:45 +0100 Subject: [PATCH 3/5] [pilight] Additional refactoring and optimizations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Niklas Dörfler --- .../pilight/internal/PilightConnector.java | 14 +-- .../PilightBridgeDiscoveryService.java | 65 ++++++------- .../PilightDeviceDiscoveryService.java | 92 ++++++++----------- .../internal/handler/PilightBaseHandler.java | 28 ++---- .../handler/PilightBridgeHandler.java | 10 +- .../handler/PilightContactHandler.java | 5 + .../handler/PilightDimmerHandler.java | 13 ++- .../handler/PilightGenericHandler.java | 5 + .../handler/PilightSwitchHandler.java | 5 + .../main/resources/OH-INF/thing/devices.xml | 12 +++ 10 files changed, 119 insertions(+), 130 deletions(-) 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 index b843108e3d317..264dc58f42374 100644 --- 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 @@ -12,13 +12,9 @@ */ package org.openhab.binding.pilight.internal; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.PrintStream; +import java.io.*; import java.net.Socket; import java.util.Collections; -import java.util.LinkedList; import java.util.concurrent.*; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -44,7 +40,7 @@ * */ @NonNullByDefault -public class PilightConnector implements Runnable { +public class PilightConnector implements Runnable, Closeable { private static final int RECONNECT_DELAY_MSEC = 10 * 1000; // 10 seconds @@ -65,7 +61,7 @@ public class PilightConnector implements Runnable { private @Nullable PrintStream printStream; private final ScheduledExecutorService scheduler; - private final LinkedList delayedActionQueue = new LinkedList<>(); + private final ConcurrentLinkedQueue delayedActionQueue = new ConcurrentLinkedQueue<>(); private @Nullable ScheduledFuture delayedActionWorkerFuture; public PilightConnector(final PilightBridgeConfiguration config, final IPilightCallback callback, @@ -227,13 +223,13 @@ private void connect() throws InterruptedException { * * @param action action to send */ - public synchronized void sendAction(Action action) { + public void sendAction(Action action) { delayedActionQueue.add(action); if (delayedActionWorkerFuture == null || delayedActionWorkerFuture.isCancelled()) { delayedActionWorkerFuture = scheduler.scheduleWithFixedDelay(() -> { if (!delayedActionQueue.isEmpty()) { - doSendAction(delayedActionQueue.pop()); + doSendAction(delayedActionQueue.poll()); } else { delayedActionWorkerFuture.cancel(false); delayedActionWorkerFuture = 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 index 6349a410ad6a1..7be850b85545b 100644 --- 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 @@ -18,8 +18,7 @@ import java.util.*; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -78,52 +77,40 @@ protected void startScan() { if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) { DatagramSocket ssdp = new DatagramSocket( new InetSocketAddress(inetAddress.getHostAddress(), 0)); - byte[] buff = SSDP_DISCOVERY_REQUEST_MESSAGE.getBytes(); + byte[] buff = SSDP_DISCOVERY_REQUEST_MESSAGE.getBytes(StandardCharsets.UTF_8); 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) { + final AtomicBoolean loop = new AtomicBoolean(true); + while (loop.get()) { DatagramPacket recvPack = new DatagramPacket(new byte[1024], 1024); ssdp.receive(recvPack); byte[] recvData = recvPack.getData(); - try (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); - } - try (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); - } - } - } - } + + final Scanner scanner = new Scanner(new ByteArrayInputStream(recvData), + StandardCharsets.UTF_8); + scanner.findAll("Location:([0-9.]+):(.*)").forEach(matchResult -> { + final String server = matchResult.group(1); + final Integer port = Integer.parseInt(matchResult.group(2)); + + logger.debug("Found pilight daemon at {}:{}", server, port); + + Map properties = new HashMap<>(); + 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); + loop.set(false); + }); } } } 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 index 1d80986ee84d4..1b59ffb80ae8e 100644 --- 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 @@ -16,7 +16,6 @@ import java.util.*; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -47,8 +46,7 @@ */ @NonNullByDefault @Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.pilight") -public class PilightDeviceDiscoveryService extends AbstractDiscoveryService - implements DiscoveryService, ThingHandlerService { +public class PilightDeviceDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService { private static final Set SUPPORTED_THING_TYPES_UIDS = PilightHandlerFactory.SUPPORTED_THING_TYPES_UIDS; @@ -76,64 +74,52 @@ protected void startScan() { configFuture = new CompletableFuture<>(); statusFuture = new CompletableFuture<>(); - CompletableFuture.allOf(configFuture, statusFuture).thenAccept(unused -> { - Config config = null; - 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 Optional status = finalAllStatus.stream() - .filter(s -> deviceId.equals(s.getDevices().get(0))).findFirst(); - - final ThingTypeUID thingTypeUID; - final String typeString; - - if (status.isPresent()) { - if (status.get().getType().equals(DeviceType.SWITCH)) { - thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_SWITCH.getId()); - typeString = "Switch"; - } else if (status.get().getType().equals(DeviceType.DIMMER)) { - thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_DIMMER.getId()); - typeString = "Dimmer"; - } else if (status.get().getType().equals(DeviceType.VALUE)) { - thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_GENERIC.getId()); - typeString = "Generic"; - } else if (status.get().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"; - } + configFuture.thenAcceptBoth(statusFuture, (config, allStatus) -> { + removeOlderResults(getTimestampOfLastScan(), bridgeUID); + config.getDevices().forEach((deviceId, device) -> { + if (this.pilightBridgeHandler != null) { + final Optional status = allStatus.stream() + .filter(s -> deviceId.equals(s.getDevices().get(0))).findFirst(); + + final ThingTypeUID thingTypeUID; + final String typeString; + + if (status.isPresent()) { + if (status.get().getType().equals(DeviceType.SWITCH)) { + thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_SWITCH.getId()); + typeString = "Switch"; + } else if (status.get().getType().equals(DeviceType.DIMMER)) { + thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_DIMMER.getId()); + typeString = "Dimmer"; + } else if (status.get().getType().equals(DeviceType.VALUE)) { + thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_GENERIC.getId()); + typeString = "Generic"; + } else if (status.get().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); + final ThingUID thingUID = new ThingUID(thingTypeUID, + this.pilightBridgeHandler.getThing().getUID(), deviceId); - Map properties = new HashMap<>(); - properties.put(PROPERTY_NAME, deviceId); + final Map properties = new HashMap<>(); + properties.put(PROPERTY_NAME, deviceId); - DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID) - .withThingType(thingTypeUID).withProperties(properties).withBridge(bridgeUID) - .withRepresentationProperty(deviceId) - .withLabel("Pilight " + typeString + " Device '" + deviceId + "'").build(); + DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID) + .withThingType(thingTypeUID).withProperties(properties).withBridge(bridgeUID) + .withRepresentationProperty(deviceId) + .withLabel("Pilight " + typeString + " Device '" + deviceId + "'").build(); - thingDiscovered(discoveryResult); - } - }); - } + thingDiscovered(discoveryResult); + } + }); }); pilightBridgeHandler.refreshConfigAndStatus(); 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 index e96aa48ecffd4..aeb1e4845a1fe 100644 --- 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 @@ -35,7 +35,7 @@ * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery */ @NonNullByDefault -public class PilightBaseHandler extends BaseThingHandler { +public abstract class PilightBaseHandler extends BaseThingHandler { private final Logger logger = LoggerFactory.getLogger(PilightBaseHandler.class); @@ -68,14 +68,13 @@ public void initialize() { } 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); + if (status.getDevices() != null && !status.getDevices().isEmpty()) { + if (name.equals(status.getDevices().get(0))) { + if (!ThingStatus.ONLINE.equals(getThing().getStatus())) { + updateStatus(ThingStatus.ONLINE); + } + updateFromStatus(status); } - updateFromStatus(status); } } @@ -86,18 +85,11 @@ public void updateFromConfigIfMatches(Config config) { } } - protected void updateFromStatus(Status status) { - // handled in derived class - } + abstract void updateFromStatus(Status status); - protected void updateFromConfigDevice(Device device) { - // may be handled in derived class - } + abstract void updateFromConfigDevice(Device device); - protected @Nullable Action createUpdateCommand(ChannelUID channelUID, Command command) { - // handled in the derived class - return null; - } + abstract @Nullable Action createUpdateCommand(ChannelUID channelUID, Command command); protected String getName() { return name; 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 index fdb04b39e4a55..3f0651db23459 100644 --- 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 @@ -57,6 +57,9 @@ public class PilightBridgeHandler extends BaseBridgeHandler { private @Nullable PilightDeviceDiscoveryService discoveryService = null; + private final ExecutorService connectorExecutor = Executors + .newSingleThreadExecutor(new NamedThreadFactory(getThing().getUID().getAsString(), true)); + public PilightBridgeHandler(Bridge bridge) { super(bridge); } @@ -104,10 +107,7 @@ public void versionReceived(Version version) { updateStatus(ThingStatus.UNKNOWN); - final ExecutorService executor = Executors - .newSingleThreadExecutor(new NamedThreadFactory(getThing().getUID().getAsString(), true)); - executor.execute(connector); - + connectorExecutor.execute(connector); this.connector = connector; } @@ -123,6 +123,8 @@ public void dispose() { connector.close(); this.connector = null; } + + connectorExecutor.shutdown(); } /** 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 index c424accb1e21b..d4d26e641c123 100644 --- 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 @@ -17,6 +17,7 @@ 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.Device; import org.openhab.binding.pilight.internal.dto.Status; import org.openhab.binding.pilight.internal.types.PilightContactType; import org.openhab.core.thing.ChannelUID; @@ -48,6 +49,10 @@ protected void updateFromStatus(Status status) { } } + @Override + void updateFromConfigDevice(Device device) { + } + @Override protected @Nullable Action createUpdateCommand(ChannelUID channelUID, Command command) { logger.warn("A contact is a read only device"); 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 index dc6c006c9f39e..a0d18d9c822ee 100644 --- 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 @@ -44,7 +44,7 @@ public class PilightDimmerHandler extends PilightBaseHandler { private static final int MAX_DIM_LEVEL_DEFAULT = 15; - private static final BigDecimal PERCENTAGE_FROM_DIMLEVEL_MULTIPLICAND = new BigDecimal(100); + private static final BigDecimal BIG_DECIMAL_100 = new BigDecimal(100); private final Logger logger = LoggerFactory.getLogger(PilightDimmerHandler.class); @@ -67,7 +67,7 @@ protected void updateFromStatus(Status status) { 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; + dimLevel = state.equals(OnOffType.ON) ? BIG_DECIMAL_100 : BigDecimal.ZERO; } } @@ -105,18 +105,17 @@ protected void updateFromConfigDevice(Device device) { private BigDecimal getPercentageFromDimLevel(String string) { return new BigDecimal(string).setScale(2).divide(new BigDecimal(maxDimLevel), RoundingMode.HALF_UP) - .multiply(PERCENTAGE_FROM_DIMLEVEL_MULTIPLICAND); + .multiply(BIG_DECIMAL_100); } private void setDimmerValue(PercentType percent, Code code) { - if (BigDecimal.ZERO.equals(percent.toBigDecimal())) { + if (PercentType.ZERO.equals(percent)) { // 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); + BigDecimal dimlevel = percent.toBigDecimal().setScale(2).divide(BIG_DECIMAL_100, RoundingMode.HALF_UP) + .multiply(BigDecimal.valueOf(maxDimLevel)).setScale(0, RoundingMode.HALF_UP); Values values = new Values(); values.setDimlevel(dimlevel.intValue()); 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 index 8ebd715267fec..d66af6605cac1 100644 --- 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 @@ -19,6 +19,7 @@ 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.Device; import org.openhab.binding.pilight.internal.dto.Status; import org.openhab.core.library.CoreItemFactory; import org.openhab.core.library.types.DecimalType; @@ -72,6 +73,10 @@ protected void updateFromStatus(Status status) { } } + @Override + void updateFromConfigDevice(Device device) { + } + @Override protected @Nullable Action createUpdateCommand(ChannelUID channelUID, Command command) { if (isChannelReadOnly(channelUID)) { 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 index 3a51ff9d548cb..179d6d2f83c59 100644 --- 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 @@ -18,6 +18,7 @@ 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.core.library.types.OnOffType; import org.openhab.core.thing.ChannelUID; @@ -50,6 +51,10 @@ protected void updateFromStatus(Status status) { } } + @Override + void updateFromConfigDevice(Device device) { + } + @Override protected @Nullable Action createUpdateCommand(ChannelUID unused, Command command) { if (command instanceof OnOffType) { 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 index 2946f2cbcb458..1dad29d3c104b 100644 --- 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 @@ -67,6 +67,18 @@ + + Dimmer + + Dim level of pilight dimmer. + + + + Switch + + Power ON/OFF the pilight switch. + + String From c352d60d0760ec95dfc92dac548fb3a403fefd8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20D=C3=B6rfler?= Date: Mon, 15 Feb 2021 20:54:46 +0100 Subject: [PATCH 4/5] [pilight] Additional refactoring to resolve compiler warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Niklas Dörfler --- bundles/org.openhab.binding.pilight/pom.xml | 18 ------ .../pilight/internal/PilightConnector.java | 38 ++++++++----- .../PilightBridgeDiscoveryService.java | 24 ++++---- .../PilightDeviceDiscoveryService.java | 56 +++++++++++-------- .../handler/PilightBridgeHandler.java | 2 + .../handler/PilightGenericHandler.java | 9 ++- .../main/resources/OH-INF/thing/devices.xml | 16 +----- 7 files changed, 79 insertions(+), 84 deletions(-) diff --git a/bundles/org.openhab.binding.pilight/pom.xml b/bundles/org.openhab.binding.pilight/pom.xml index 54e96f45dc702..1acde190f7394 100644 --- a/bundles/org.openhab.binding.pilight/pom.xml +++ b/bundles/org.openhab.binding.pilight/pom.xml @@ -14,22 +14,4 @@ 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/java/org/openhab/binding/pilight/internal/PilightConnector.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightConnector.java index 264dc58f42374..75fca4f9cf4e2 100644 --- 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 @@ -78,30 +78,33 @@ public void run() { while (!Thread.currentThread().isInterrupted()) { try { - final Socket socket = this.socket; + final @Nullable Socket socket = this.socket; if (socket != null && !socket.isClosed()) { try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) { String line = in.readLine(); while (!Thread.currentThread().isInterrupted() && line != null) { if (!line.isEmpty()) { logger.trace("Received from pilight: {}", line); + final ObjectMapper inputMapper = this.inputMapper; if (line.startsWith("{\"message\":\"config\"")) { - callback.configReceived(inputMapper.readValue(line, Message.class).getConfig()); + final @Nullable Message message = inputMapper.readValue(line, Message.class); + callback.configReceived(message.getConfig()); } else if (line.startsWith("{\"message\":\"values\"")) { - AllStatus status = inputMapper.readValue(line, AllStatus.class); + final @Nullable AllStatus status = inputMapper.readValue(line, AllStatus.class); callback.statusReceived(status.getValues()); } else if (line.startsWith("{\"version\":")) { - Version version = inputMapper.readValue(line, Version.class); + final @Nullable Version version = inputMapper.readValue(line, Version.class); callback.versionReceived(version); } else if (line.startsWith("{\"status\":")) { - Response response = inputMapper.readValue(line, Response.class); + // currently unused } else if (line.equals("1")) { throw new IOException("Connection to pilight lost"); } else { - Status status = inputMapper.readValue(line, Status.class); + final @Nullable Status status = inputMapper.readValue(line, Status.class); callback.statusReceived(Collections.singletonList(status)); } } + line = in.readLine(); } } @@ -151,13 +154,13 @@ public void close() { } private void disconnect() { - final PrintStream printStream = this.printStream; + final @Nullable PrintStream printStream = this.printStream; if (printStream != null) { printStream.close(); this.printStream = null; } - final Socket socket = this.socket; + final @Nullable Socket socket = this.socket; if (socket != null) { try { socket.close(); @@ -169,7 +172,7 @@ private void disconnect() { } private boolean isConnected() { - final Socket socket = this.socket; + final @Nullable Socket socket = this.socket; return socket != null && !socket.isClosed(); } @@ -195,7 +198,7 @@ private void connect() throws InterruptedException { PrintStream printStream = new PrintStream(socket.getOutputStream(), true); printStream.println(outputMapper.writeValueAsString(identification)); - Response response = inputMapper.readValue(socket.getInputStream(), Response.class); + final @Nullable Response response = inputMapper.readValue(socket.getInputStream(), Response.class); if (response.getStatus().equals(Response.SUCCESS)) { logger.debug("Established connection to pilight server at {}:{}", config.getIpAddress(), @@ -209,7 +212,10 @@ private void connect() throws InterruptedException { logger.debug("pilight client not accepted: {}", response.getStatus()); } } catch (IOException e) { - this.printStream.close(); + final @Nullable PrintStream printStream = this.printStream; + if (printStream != null) { + printStream.close(); + } logger.debug("connect failed: {}", e.getMessage()); callback.updateThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } @@ -225,14 +231,18 @@ private void connect() throws InterruptedException { */ public void sendAction(Action action) { delayedActionQueue.add(action); + final @Nullable ScheduledFuture delayedActionWorkerFuture = this.delayedActionWorkerFuture; if (delayedActionWorkerFuture == null || delayedActionWorkerFuture.isCancelled()) { - delayedActionWorkerFuture = scheduler.scheduleWithFixedDelay(() -> { + this.delayedActionWorkerFuture = scheduler.scheduleWithFixedDelay(() -> { if (!delayedActionQueue.isEmpty()) { doSendAction(delayedActionQueue.poll()); } else { - delayedActionWorkerFuture.cancel(false); - delayedActionWorkerFuture = null; + final @Nullable ScheduledFuture workerFuture = this.delayedActionWorkerFuture; + if (workerFuture != null) { + workerFuture.cancel(false); + } + this.delayedActionWorkerFuture = null; } }, 0, config.getDelay(), TimeUnit.MILLISECONDS); } 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 index 7be850b85545b..67cb46bd87b6e 100644 --- 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 @@ -18,7 +18,6 @@ import java.util.*; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -84,39 +83,40 @@ protected void startScan() { ssdp.send(sendPack); ssdp.setSoTimeout(SSDP_WAIT_TIMEOUT); - final AtomicBoolean loop = new AtomicBoolean(true); - while (loop.get()) { + boolean loop = true; + while (loop) { DatagramPacket recvPack = new DatagramPacket(new byte[1024], 1024); ssdp.receive(recvPack); byte[] recvData = recvPack.getData(); final Scanner scanner = new Scanner(new ByteArrayInputStream(recvData), StandardCharsets.UTF_8); - scanner.findAll("Location:([0-9.]+):(.*)").forEach(matchResult -> { + loop = scanner.findAll("Location:([0-9.]+):(.*)").peek(matchResult -> { final String server = matchResult.group(1); final Integer port = Integer.parseInt(matchResult.group(2)); + final String bridgeName = server.replace(".", "") + "" + port; logger.debug("Found pilight daemon at {}:{}", server, port); Map properties = new HashMap<>(); properties.put(PilightBindingConstants.PROPERTY_IP_ADDRESS, server); properties.put(PilightBindingConstants.PROPERTY_PORT, port); + properties.put(PilightBindingConstants.PROPERTY_NAME, bridgeName); - ThingUID uid = new ThingUID(PilightBindingConstants.THING_TYPE_BRIDGE, - server.replace(".", "") + "" + port); + ThingUID uid = new ThingUID(PilightBindingConstants.THING_TYPE_BRIDGE, bridgeName); DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties) + .withRepresentationProperty(PilightBindingConstants.PROPERTY_NAME) .withLabel("Pilight Bridge (" + server + ")").build(); thingDiscovered(result); - loop.set(false); - }); + }).count() == 0; } } } } } catch (IOException e) { - if (!e.getMessage().equals("Receive timed out")) { + if (e.getMessage() != null && !"Receive timed out".equals(e.getMessage())) { logger.warn("Unable to enumerate the local network interfaces {}", e.getMessage()); } } @@ -131,8 +131,9 @@ protected synchronized void stopScan() { @Override protected void startBackgroundDiscovery() { logger.debug("Start Pilight device background discovery"); + final @Nullable ScheduledFuture backgroundDiscoveryJob = this.backgroundDiscoveryJob; if (backgroundDiscoveryJob == null || backgroundDiscoveryJob.isCancelled()) { - backgroundDiscoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 5, + this.backgroundDiscoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 5, AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL_SEC, TimeUnit.SECONDS); } } @@ -140,9 +141,10 @@ protected void startBackgroundDiscovery() { @Override protected void stopBackgroundDiscovery() { logger.debug("Stop Pilight device background discovery"); + final @Nullable ScheduledFuture backgroundDiscoveryJob = this.backgroundDiscoveryJob; if (backgroundDiscoveryJob != null) { backgroundDiscoveryJob.cancel(true); - backgroundDiscoveryJob = null; + this.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 index 1b59ffb80ae8e..c735a976e36a5 100644 --- 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 @@ -29,12 +29,10 @@ 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; @@ -45,7 +43,6 @@ * @author Niklas Dörfler - Initial contribution */ @NonNullByDefault -@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.pilight") public class PilightDeviceDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService { private static final Set SUPPORTED_THING_TYPES_UIDS = PilightHandlerFactory.SUPPORTED_THING_TYPES_UIDS; @@ -79,7 +76,7 @@ protected void startScan() { config.getDevices().forEach((deviceId, device) -> { if (this.pilightBridgeHandler != null) { final Optional status = allStatus.stream() - .filter(s -> deviceId.equals(s.getDevices().get(0))).findFirst(); + .filter(s -> s.getDevices().contains(deviceId)).findFirst(); final ThingTypeUID thingTypeUID; final String typeString; @@ -106,29 +103,37 @@ protected void startScan() { typeString = "Generic"; } - final ThingUID thingUID = new ThingUID(thingTypeUID, - this.pilightBridgeHandler.getThing().getUID(), deviceId); + final @Nullable PilightBridgeHandler pilightBridgeHandler = this.pilightBridgeHandler; + if (pilightBridgeHandler != null) { + final ThingUID thingUID = new ThingUID(thingTypeUID, + pilightBridgeHandler.getThing().getUID(), deviceId); - final Map properties = new HashMap<>(); - properties.put(PROPERTY_NAME, deviceId); + final Map properties = new HashMap<>(); + properties.put(PROPERTY_NAME, deviceId); - DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID) - .withThingType(thingTypeUID).withProperties(properties).withBridge(bridgeUID) - .withRepresentationProperty(deviceId) - .withLabel("Pilight " + typeString + " Device '" + deviceId + "'").build(); + DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID) + .withThingType(thingTypeUID).withProperties(properties).withBridge(bridgeUID) + .withRepresentationProperty(PROPERTY_NAME) + .withLabel("Pilight " + typeString + " Device '" + deviceId + "'").build(); - thingDiscovered(discoveryResult); + thingDiscovered(discoveryResult); + } } }); }); - pilightBridgeHandler.refreshConfigAndStatus(); + final @Nullable PilightBridgeHandler pilightBridgeHandler = this.pilightBridgeHandler; + if (pilightBridgeHandler != null) { + pilightBridgeHandler.refreshConfigAndStatus(); + } } } @Override protected synchronized void stopScan() { super.stopScan(); + configFuture.cancel(true); + statusFuture.cancel(true); if (bridgeUID != null) { removeOlderResults(getTimestampOfLastScan(), bridgeUID); } @@ -137,8 +142,9 @@ protected synchronized void stopScan() { @Override protected void startBackgroundDiscovery() { logger.debug("Start Pilight device background discovery"); + final @Nullable ScheduledFuture backgroundDiscoveryJob = this.backgroundDiscoveryJob; if (backgroundDiscoveryJob == null || backgroundDiscoveryJob.isCancelled()) { - backgroundDiscoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 20, + this.backgroundDiscoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 20, AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL_SEC, TimeUnit.SECONDS); } } @@ -146,17 +152,21 @@ protected void startBackgroundDiscovery() { @Override protected void stopBackgroundDiscovery() { logger.debug("Stop Pilight device background discovery"); + final @Nullable ScheduledFuture backgroundDiscoveryJob = this.backgroundDiscoveryJob; if (backgroundDiscoveryJob != null) { backgroundDiscoveryJob.cancel(true); - backgroundDiscoveryJob = null; + this.backgroundDiscoveryJob = null; } } @Override public void setThingHandler(final ThingHandler handler) { if (handler instanceof PilightBridgeHandler) { - pilightBridgeHandler = (PilightBridgeHandler) handler; - bridgeUID = pilightBridgeHandler.getThing().getUID(); + this.pilightBridgeHandler = (PilightBridgeHandler) handler; + final @Nullable PilightBridgeHandler pilightBridgeHandler = this.pilightBridgeHandler; + if (pilightBridgeHandler != null) { + bridgeUID = pilightBridgeHandler.getThing().getUID(); + } } } @@ -168,6 +178,7 @@ public void setThingHandler(final ThingHandler handler) { @Override public void activate() { super.activate(null); + final @Nullable PilightBridgeHandler pilightBridgeHandler = this.pilightBridgeHandler; if (pilightBridgeHandler != null) { pilightBridgeHandler.registerDiscoveryListener(this); } @@ -179,6 +190,7 @@ public void deactivate() { removeOlderResults(getTimestampOfLastScan(), bridgeUID); } + final @Nullable PilightBridgeHandler pilightBridgeHandler = this.pilightBridgeHandler; if (pilightBridgeHandler != null) { pilightBridgeHandler.unregisterDiscoveryListener(); } @@ -192,9 +204,7 @@ public void deactivate() { * @param config config to get */ public void setConfig(Config config) { - if (!configFuture.isDone()) { - configFuture.complete(config); - } + configFuture.complete(config); } /** @@ -203,9 +213,7 @@ public void setConfig(Config config) { * @param status list of status objects */ public void setStatus(List status) { - if (!statusFuture.isDone()) { - statusFuture.complete(status); - } + statusFuture.complete(status); } @Override 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 index 3f0651db23459..dfcfd9fdfd087 100644 --- 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 @@ -73,6 +73,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { public void initialize() { PilightBridgeConfiguration config = getConfigAs(PilightBridgeConfiguration.class); + final @Nullable PilightDeviceDiscoveryService discoveryService = this.discoveryService; PilightConnector connector = new PilightConnector(config, new IPilightCallback() { @Override public void updateThingStatus(ThingStatus status, ThingStatusDetail statusDetail, @@ -224,6 +225,7 @@ private void processConfig(Config config) { } } + final @Nullable PilightDeviceDiscoveryService discoveryService = this.discoveryService; if (discoveryService != null) { discoveryService.setConfig(config); } 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 index d66af6605cac1..21adaa309dd91 100644 --- 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 @@ -33,6 +33,7 @@ import org.openhab.core.thing.type.ChannelTypeUID; import org.openhab.core.types.Command; import org.openhab.core.types.State; +import org.openhab.core.types.StateDescription; import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -120,14 +121,16 @@ private void initializeReadOnlyChannels() { logger.debug("initializeReadOnly {} {}", channelType, channelType.getState()); } - if (channelType != null && channelType.getState() != null) { - channelReadOnlyMap.putIfAbsent(channel.getUID(), channelType.getState().isReadOnly()); + if (channelType != null) { + final @Nullable StateDescription state = channelType.getState(); + if (state != null) { + channelReadOnlyMap.putIfAbsent(channel.getUID(), state.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/resources/OH-INF/thing/devices.xml b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/devices.xml index 1dad29d3c104b..c55db4011e493 100644 --- 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 @@ -13,7 +13,7 @@ Pilight Switch - + @@ -43,7 +43,7 @@ Pilight Dimmer - + @@ -67,18 +67,6 @@ - - Dimmer - - Dim level of pilight dimmer. - - - - Switch - - Power ON/OFF the pilight switch. - - String From f450f25f99c8a6a01f0aea02e3a2323099a338d9 Mon Sep 17 00:00:00 2001 From: Fabian Wolter Date: Wed, 17 Feb 2021 19:59:24 +0100 Subject: [PATCH 5/5] Update bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/bridge.xml Signed-off-by: Fabian Wolter --- .../src/main/resources/OH-INF/thing/bridge.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 4ef6ce98da745..e52c70a6021bb 100644 --- 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 @@ -26,7 +26,7 @@ 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