diff --git a/CODEOWNERS b/CODEOWNERS index e08a8885edf26..5a94b11062ae5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -208,6 +208,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 37bf387899738..c7dbcefbccc2c 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1026,6 +1026,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..84d35ac52ac16 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/README.md @@ -0,0 +1,119 @@ +# 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 + +### `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 all 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, 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. + +### 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. + +## Full Example + +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..1acde190f7394 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/pom.xml @@ -0,0 +1,17 @@ + + + + 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 + + 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..38a968d3f2ba8 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + 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..85dce4004946f --- /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 int port = 0; + private int 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 int 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..75fca4f9cf4e2 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightConnector.java @@ -0,0 +1,264 @@ +/** + * 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.*; +import java.net.Socket; +import java.util.Collections; +import java.util.concurrent.*; + +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 implements Runnable, Closeable { + + private static final int 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 @Nullable Socket socket; + private @Nullable PrintStream printStream; + + private final ScheduledExecutorService scheduler; + private final ConcurrentLinkedQueue delayedActionQueue = new ConcurrentLinkedQueue<>(); + private @Nullable ScheduledFuture delayedActionWorkerFuture; + + public PilightConnector(final PilightBridgeConfiguration config, final IPilightCallback callback, + final ScheduledExecutorService scheduler) { + this.config = config; + this.callback = callback; + this.scheduler = scheduler; + } + + @Override + public void run() { + try { + connect(); + + while (!Thread.currentThread().isInterrupted()) { + try { + 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\"")) { + final @Nullable Message message = inputMapper.readValue(line, Message.class); + callback.configReceived(message.getConfig()); + } else if (line.startsWith("{\"message\":\"values\"")) { + final @Nullable AllStatus status = inputMapper.readValue(line, AllStatus.class); + callback.statusReceived(status.getValues()); + } else if (line.startsWith("{\"version\":")) { + final @Nullable Version version = inputMapper.readValue(line, Version.class); + callback.versionReceived(version); + } else if (line.startsWith("{\"status\":")) { + // currently unused + } else if (line.equals("1")) { + throw new IOException("Connection to pilight lost"); + } else { + final @Nullable Status status = inputMapper.readValue(line, Status.class); + callback.statusReceived(Collections.singletonList(status)); + } + } + + line = in.readLine(); + } + } + } + } catch (IOException e) { + if (!Thread.currentThread().isInterrupted()) { + logger.debug("Error in pilight listener thread: {}", e.getMessage()); + } + } + + logger.debug("Disconnected from pilight server at {}:{}", config.getIpAddress(), config.getPort()); + + 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(); + } + } + + /** + * Tells the connector to refresh the configuration + */ + public void refreshConfig() { + doSendAction(new Action(Action.ACTION_REQUEST_CONFIG)); + } + + /** + * Tells the connector to refresh the status of all devices + */ + public void refreshStatus() { + doSendAction(new Action(Action.ACTION_REQUEST_VALUES)); + } + + /** + * Stops the listener + */ + public void close() { + disconnect(); + Thread.currentThread().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.getMessage()); + } + this.socket = null; + } + } + + private boolean isConnected() { + final @Nullable Socket socket = this.socket; + return socket != null && !socket.isClosed(); + } + + private void connect() throws InterruptedException { + 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)); + + 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(), + 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) { + 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()); + } + + delay = RECONNECT_DELAY_MSEC; + } + } + + /** + * send action to pilight daemon + * + * @param action action to send + */ + public void sendAction(Action action) { + delayedActionQueue.add(action); + final @Nullable ScheduledFuture delayedActionWorkerFuture = this.delayedActionWorkerFuture; + + if (delayedActionWorkerFuture == null || delayedActionWorkerFuture.isCancelled()) { + this.delayedActionWorkerFuture = scheduler.scheduleWithFixedDelay(() -> { + if (!delayedActionQueue.isEmpty()) { + doSendAction(delayedActionQueue.poll()); + } else { + final @Nullable ScheduledFuture workerFuture = this.delayedActionWorkerFuture; + if (workerFuture != null) { + workerFuture.cancel(false); + } + this.delayedActionWorkerFuture = null; + } + }, 0, config.getDelay(), TimeUnit.MILLISECONDS); + } + } + + 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.getMessage()); + } + } else { + logger.debug("Cannot send action '{}', not connected to pilight!", action.getAction()); + } + } +} 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..366b2e4b21de8 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightHandlerFactory.java @@ -0,0 +1,89 @@ +/** + * 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 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 = Set.of(THING_TYPE_BRIDGE, THING_TYPE_CONTACT, + THING_TYPE_DIMMER, THING_TYPE_GENERIC, THING_TYPE_SWITCH); + + 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..67cb46bd87b6e --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightBridgeDiscoveryService.java @@ -0,0 +1,150 @@ +/** + * 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 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_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" + + "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_SEC, 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(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) { + 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); + 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, bridgeName); + + DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties) + .withRepresentationProperty(PilightBindingConstants.PROPERTY_NAME) + .withLabel("Pilight Bridge (" + server + ")").build(); + + thingDiscovered(result); + }).count() == 0; + } + } + } + } + } catch (IOException e) { + if (e.getMessage() != null && !"Receive timed out".equals(e.getMessage())) { + 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"); + final @Nullable ScheduledFuture backgroundDiscoveryJob = this.backgroundDiscoveryJob; + if (backgroundDiscoveryJob == null || backgroundDiscoveryJob.isCancelled()) { + this.backgroundDiscoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 5, + AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL_SEC, TimeUnit.SECONDS); + } + } + + @Override + protected void stopBackgroundDiscovery() { + logger.debug("Stop Pilight device background discovery"); + final @Nullable ScheduledFuture backgroundDiscoveryJob = this.backgroundDiscoveryJob; + if (backgroundDiscoveryJob != null) { + backgroundDiscoveryJob.cancel(true); + 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 new file mode 100644 index 0000000000000..c735a976e36a5 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightDeviceDiscoveryService.java @@ -0,0 +1,223 @@ +/** + * 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.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.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 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 +public class PilightDeviceDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService { + + private static final Set SUPPORTED_THING_TYPES_UIDS = PilightHandlerFactory.SUPPORTED_THING_TYPES_UIDS; + + 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); + + private @Nullable PilightBridgeHandler pilightBridgeHandler; + private @Nullable ThingUID bridgeUID; + + private @Nullable ScheduledFuture backgroundDiscoveryJob; + private CompletableFuture configFuture; + private CompletableFuture> statusFuture; + + public PilightDeviceDiscoveryService() { + super(SUPPORTED_THING_TYPES_UIDS, AUTODISCOVERY_SEARCH_TIME_SEC); + configFuture = new CompletableFuture<>(); + statusFuture = new CompletableFuture<>(); + } + + @Override + protected void startScan() { + if (pilightBridgeHandler != null) { + configFuture = new CompletableFuture<>(); + statusFuture = new CompletableFuture<>(); + + configFuture.thenAcceptBoth(statusFuture, (config, allStatus) -> { + removeOlderResults(getTimestampOfLastScan(), bridgeUID); + config.getDevices().forEach((deviceId, device) -> { + if (this.pilightBridgeHandler != null) { + final Optional status = allStatus.stream() + .filter(s -> s.getDevices().contains(deviceId)).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 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); + + DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID) + .withThingType(thingTypeUID).withProperties(properties).withBridge(bridgeUID) + .withRepresentationProperty(PROPERTY_NAME) + .withLabel("Pilight " + typeString + " Device '" + deviceId + "'").build(); + + thingDiscovered(discoveryResult); + } + } + }); + }); + + 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); + } + } + + @Override + protected void startBackgroundDiscovery() { + logger.debug("Start Pilight device background discovery"); + final @Nullable ScheduledFuture backgroundDiscoveryJob = this.backgroundDiscoveryJob; + if (backgroundDiscoveryJob == null || backgroundDiscoveryJob.isCancelled()) { + this.backgroundDiscoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 20, + AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL_SEC, TimeUnit.SECONDS); + } + } + + @Override + protected void stopBackgroundDiscovery() { + logger.debug("Stop Pilight device background discovery"); + final @Nullable ScheduledFuture backgroundDiscoveryJob = this.backgroundDiscoveryJob; + if (backgroundDiscoveryJob != null) { + backgroundDiscoveryJob.cancel(true); + this.backgroundDiscoveryJob = null; + } + } + + @Override + public void setThingHandler(final ThingHandler handler) { + if (handler instanceof PilightBridgeHandler) { + this.pilightBridgeHandler = (PilightBridgeHandler) handler; + final @Nullable PilightBridgeHandler pilightBridgeHandler = this.pilightBridgeHandler; + if (pilightBridgeHandler != null) { + bridgeUID = pilightBridgeHandler.getThing().getUID(); + } + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return pilightBridgeHandler; + } + + @Override + public void activate() { + super.activate(null); + final @Nullable PilightBridgeHandler pilightBridgeHandler = this.pilightBridgeHandler; + if (pilightBridgeHandler != null) { + pilightBridgeHandler.registerDiscoveryListener(this); + } + } + + @Override + public void deactivate() { + if (bridgeUID != null) { + removeOlderResults(getTimestampOfLastScan(), bridgeUID); + } + + final @Nullable PilightBridgeHandler pilightBridgeHandler = this.pilightBridgeHandler; + 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) { + 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) { + 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..aeb1e4845a1fe --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBaseHandler.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 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 abstract 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) { + 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(); + + refreshConfigAndStatus(); + } + + public void updateFromStatusIfMatches(Status status) { + 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); + } + } + } + + public void updateFromConfigIfMatches(Config config) { + Device device = config.getDevices().get(getName()); + if (device != null) { + updateFromConfigDevice(device); + } + } + + abstract void updateFromStatus(Status status); + + abstract void updateFromConfigDevice(Device device); + + abstract @Nullable Action createUpdateCommand(ChannelUID channelUID, Command command); + + 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..dfcfd9fdfd087 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBridgeHandler.java @@ -0,0 +1,233 @@ +/** + * 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.ExecutorService; +import java.util.concurrent.Executors; +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.common.NamedThreadFactory; +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 int 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; + + private final ExecutorService connectorExecutor = Executors + .newSingleThreadExecutor(new NamedThreadFactory(getThing().getUID().getAsString(), true)); + + 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); + + final @Nullable PilightDeviceDiscoveryService discoveryService = this.discoveryService; + 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(Thing.PROPERTY_FIRMWARE_VERSION, version.getVersion()); + } + }, scheduler); + + updateStatus(ThingStatus.UNKNOWN); + + connectorExecutor.execute(connector); + 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; + } + + connectorExecutor.shutdown(); + } + + /** + * 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() { + 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); + } + } + + 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/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..d4d26e641c123 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightContactHandler.java @@ -0,0 +1,61 @@ +/** + * 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.Device; +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 + void updateFromConfigDevice(Device device) { + } + + @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..a0d18d9c822ee --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightDimmerHandler.java @@ -0,0 +1,126 @@ +/** + * 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 static final BigDecimal BIG_DECIMAL_100 = new BigDecimal(100); + + 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) ? BIG_DECIMAL_100 : BigDecimal.ZERO; + } + } + + State state = new PercentType(dimLevel); + updateState(CHANNEL_DIMLEVEL, state); + } + + @Override + protected void updateFromConfigDevice(Device device) { + Integer max = device.getDimlevelMaximum(); + + 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(BIG_DECIMAL_100); + } + + private void setDimmerValue(PercentType percent, Code code) { + 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(BIG_DECIMAL_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..21adaa309dd91 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightGenericHandler.java @@ -0,0 +1,138 @@ +/** + * 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.Device; +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.StateDescription; +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 + void updateFromConfigDevice(Device device) { + } + + @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) { + final @Nullable StateDescription state = channelType.getState(); + if (state != null) { + channelReadOnlyMap.putIfAbsent(channel.getUID(), state.isReadOnly()); + } + } + } + } + } + + 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..179d6d2f83c59 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightSwitchHandler.java @@ -0,0 +1,73 @@ +/** + * 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.Device; +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 + void updateFromConfigDevice(Device device) { + } + + @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..2540db4c312a0 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/binding/binding.xml @@ -0,0 +1,11 @@ + + + + Pilight Binding + 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/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..b2b8e8ed5f750 --- /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 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 +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 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 + +# 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..e52c70a6021bb --- /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..c55db4011e493 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/devices.xml @@ -0,0 +1,96 @@ + + + + + + + + + + Pilight Switch + + + + + + + + + + + + + + + Pilight Contact + + + + + + + + + + + + + + + Pilight Dimmer + + + + + + + + + + + + + + + Pilight Generic Device + + + + + + Contact + + State of Pilight Contact. + + + + + 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 7132c692ac7e0..909b8d78d80eb 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -239,6 +239,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