/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
<artifactId>org.openhab.binding.phc</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.openhab.addons.bundles</groupId>
+ <artifactId>org.openhab.binding.pilight</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.pioneeravr</artifactId>
--- /dev/null
+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
--- /dev/null
+# 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]" <temperature> { channel="pilight:generic:raspi:weather:temperature" }
+Number weather_humidity "Feuchtigkeit [%.0f %%]" <humidity> { 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
+```
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.openhab.addons.bundles</groupId>
+ <artifactId>org.openhab.addons.reactor.bundles</artifactId>
+ <version>3.1.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>org.openhab.binding.pilight</artifactId>
+
+ <name>openHAB Add-ons :: Bundles :: Pilight Binding</name>
+
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.pilight-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
+ <repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
+
+ <feature name="openhab-binding-pilight" description="Pilight Binding" version="${project.version}">
+ <feature>openhab-runtime-base</feature>
+ <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.pilight/${project.version}</bundle>
+ </feature>
+</features>
--- /dev/null
+/**
+ * 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<Status> 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);
+}
--- /dev/null
+/**
+ * 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";
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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<Action> 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());
+ }
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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<ThingTypeUID> 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;
+ }
+}
--- /dev/null
+/**
+ * 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<ThingTypeUID> getSupportedThingTypeUIDs() {
+ return Collections.singleton(PilightBindingConstants.THING_TYPE_BRIDGE);
+ }
+
+ @Override
+ protected void startScan() {
+ logger.debug("Pilight bridge discovery scan started");
+ removeOlderResults(getTimestampOfLastScan());
+ try {
+ List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
+ for (NetworkInterface nic : interfaces) {
+ Enumeration<InetAddress> 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<String, Object> 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;
+ }
+ }
+}
--- /dev/null
+/**
+ * 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<ThingTypeUID> 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<Config> configFuture;
+ private CompletableFuture<List<Status>> 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> 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<String, Object> 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> status) {
+ statusFuture.complete(status);
+ }
+
+ @Override
+ public Set<ThingTypeUID> getSupportedThingTypes() {
+ return SUPPORTED_THING_TYPES_UIDS;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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<Status> values = new ArrayList<>();
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public List<Status> getValues() {
+ return values;
+ }
+
+ public void setValues(List<Status> values) {
+ this.values = values;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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<String, Device> devices;
+
+ public Map<String, Device> getDevices() {
+ return devices;
+ }
+
+ public void setDevices(Map<String, Device> devices) {
+ this.devices = devices;
+ }
+}
--- /dev/null
+/**
+ * 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<String> protocol;
+
+ private String state;
+
+ private Integer dimlevel = null;
+
+ // @SerializedName("dimlevel-maximum")
+ private Integer dimlevelMaximum = null;
+
+ private Integer dimlevelMinimum = null;
+
+ private List<Map<String, String>> id;
+
+ private Map<String, String> 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<String> getProtocol() {
+ return protocol;
+ }
+
+ public void setProtocol(List<String> 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<Map<String, String>> getId() {
+ return id;
+ }
+
+ public void setId(List<Map<String, String>> id) {
+ this.id = id;
+ }
+
+ public void setProperties(Map<String, String> properties) {
+ this.properties = properties;
+ }
+
+ public Map<String, String> getProperties() {
+ return properties;
+ }
+
+ @JsonAnySetter
+ public void set(String name, Object value) {
+ properties.put(name, value.toString());
+ }
+}
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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<String> devices = new ArrayList<>();
+
+ private Map<String, String> 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<String> getDevices() {
+ return devices;
+ }
+
+ public void setDevices(List<String> devices) {
+ this.devices = devices;
+ }
+
+ public Map<String, String> getValues() {
+ return values;
+ }
+
+ public void setValues(Map<String, String> values) {
+ this.values = values;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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<Status> 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<Class<? extends ThingHandlerService>> 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);
+ }
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+ }
+}
--- /dev/null
+/**
+ * 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<ChannelUID, Boolean> 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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<Boolean> {
+
+ @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);
+ }
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<binding:binding id="pilight" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
+
+ <name>Pilight Binding</name>
+ <description>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.</description>
+
+</binding:binding>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<config-description:config-descriptions
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
+ https://openhab.org/schemas/config-description-1.0.0.xsd">
+
+ <config-description uri="thing-type:pilight:device">
+ <parameter name="name" type="text" required="true">
+ <label>Name of Device</label>
+ <description>The name of the pilight device.</description>
+ </parameter>
+ </config-description>
+
+</config-description:config-descriptions>
--- /dev/null
+# 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
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="pilight"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <bridge-type id="bridge">
+ <label>Pilight Bridge</label>
+ <description>Pilight Bridge which connects to a Pilight instance.</description>
+
+ <properties>
+ <property name="firmwareVersion">-</property>
+ </properties>
+
+ <config-description>
+ <parameter name="ipAddress" type="text" required="true">
+ <label>Network Address</label>
+ <description>The IP or host name of the Pilight instance.</description>
+ <context>network-address</context>
+ </parameter>
+ <parameter name="port" type="integer" required="true" min="1" max="65335">
+ <label>Port</label>
+ <description>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.
+ </description>
+ <default>5000</default>
+ </parameter>
+ <parameter name="delay" type="integer" required="false" min="1" max="65335">
+ <label>Delay between Commands</label>
+ <description>Delay (in millisecond) between consecutive commands. Recommended value without band pass filter: 1000.
+ Recommended value with band pass filter: somewhere between 200-500.</description>
+ <default>500</default>
+ <advanced>true</advanced>
+ </parameter>
+ </config-description>
+ </bridge-type>
+
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="pilight"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <thing-type id="switch">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"/>
+ </supported-bridge-type-refs>
+
+ <label>Pilight Switch</label>
+ <description>Pilight Switch</description>
+
+ <channels>
+ <channel id="state" typeId="system.power"/>
+ </channels>
+
+ <config-description-ref uri="thing-type:pilight:device"/>
+ </thing-type>
+
+ <thing-type id="contact">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"/>
+ </supported-bridge-type-refs>
+
+ <label>Pilight Contact</label>
+ <description>Pilight Contact</description>
+
+ <channels>
+ <channel id="state" typeId="contact-state"/>
+ </channels>
+
+ <config-description-ref uri="thing-type:pilight:device"/>
+ </thing-type>
+
+ <thing-type id="dimmer">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"/>
+ </supported-bridge-type-refs>
+
+ <label>Pilight Dimmer</label>
+ <description>Pilight Dimmer</description>
+
+ <channels>
+ <channel id="dimlevel" typeId="system.brightness"/>
+ </channels>
+
+ <config-description-ref uri="thing-type:pilight:device"/>
+ </thing-type>
+
+ <thing-type id="generic" extensible="string,number">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"/>
+ </supported-bridge-type-refs>
+
+ <label>Pilight Generic Device</label>
+ <description>Pilight Generic Device</description>
+
+ <config-description-ref uri="thing-type:pilight:device"/>
+ </thing-type>
+
+ <channel-type id="contact-state">
+ <item-type>Contact</item-type>
+ <label>State of Contact</label>
+ <description>State of Pilight Contact.</description>
+ <state readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="string">
+ <item-type>String</item-type>
+ <label>Text Value</label>
+ <state readOnly="true"/>
+ <config-description>
+ <parameter name="property" type="text">
+ <label>Property</label>
+ <description>The Property of the Device.</description>
+ <required>true</required>
+ </parameter>
+ </config-description>
+ </channel-type>
+
+ <channel-type id="number">
+ <item-type>Number</item-type>
+ <label>Number Value</label>
+ <state readOnly="true"/>
+ <config-description>
+ <parameter name="property" type="text">
+ <label>Property</label>
+ <description>The Property of the Device.</description>
+ <required>true</required>
+ </parameter>
+ </config-description>
+ </channel-type>
+
+</thing:thing-descriptions>
<module>org.openhab.binding.paradoxalarm</module>
<module>org.openhab.binding.pentair</module>
<module>org.openhab.binding.phc</module>
+ <module>org.openhab.binding.pilight</module>
<module>org.openhab.binding.pioneeravr</module>
<module>org.openhab.binding.pixometer</module>
<module>org.openhab.binding.pjlinkdevice</module>