[pilight] Pilight Binding initial contribution (#9744)

Signed-off-by: Niklas Dörfler <niklas@doerfler-el.de>
This commit is contained in:
Niklas Dörfler 2021-02-17 19:59:54 +01:00 committed by GitHub
parent 2b3e08de43
commit c0cec8028c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 2937 additions and 0 deletions

View File

@ -208,6 +208,7 @@
/bundles/org.openhab.binding.paradoxalarm/ @theater
/bundles/org.openhab.binding.pentair/ @jsjames
/bundles/org.openhab.binding.phc/ @gnlpfjh
/bundles/org.openhab.binding.pilight/ @stefanroellin @niklasdoerfler
/bundles/org.openhab.binding.pioneeravr/ @Stratehm
/bundles/org.openhab.binding.pixometer/ @Confectrician
/bundles/org.openhab.binding.pjlinkdevice/ @nils

View File

@ -1026,6 +1026,11 @@
<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>

View File

@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@ -0,0 +1,119 @@
# pilight Binding
The pilight binding allows openHAB to communicate with a [pilight](http://www.pilight.org/) instance running pilight
version 6.0 or greater.
> pilight is a free open source full fledge domotica solution that runs on a Raspberry Pi, HummingBoard, BananaPi,
> Radxa, but also on *BSD and various linuxes (tested on Arch, Ubuntu and Debian). It's open source and freely available
> for anyone. pilight works with a great deal of devices and is frequency independent. Therefor, it can control devices
> working at 315Mhz, 433Mhz, 868Mhz etc. Support for these devices are dependent on community, because we as developers
> don't own them all.
pilight is a cheap way to control 'Click On Click Off' devices. It started as an application for the Raspberry Pi (using
the GPIO interface) but it's also possible now to connect it to any other PC using an Arduino Nano. You will need a
cheap 433Mhz transceiver in both cases. See the [Pilight manual](https://manual.pilight.org/electronics/wiring.html) for
more information.
## Supported Things
| Thing | Type | Description |
|-----------|--------|----------------------------------------------------------------------------|
| `bridge` | Bridge | Pilight bridge required for the communication with the pilight daemon. |
| `contact` | Thing | Pilight contact (read-only). |
| `dimmer` | Thing | Pilight dimmer. |
| `switch` | Thing | Pilight switch. |
| `generic` | Thing | Pilight generic device for which you have to add the channels dynamically. |
## Binding Configuration
### `bridge` Thing
A `bridge` is required for the communication with a pilight daemon. Multiple pilight instances are supported by creating
different pilight `bridge` things.
The `bridge` requires the following configuration parameters:
| Parameter Label | Parameter ID | Description | Required |
|-----------------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|
| IP Address | ipAddress | Host name or IP address of the pilight daemon | yes |
| Port | port | Port number on which the pilight daemon is listening. Default: 5000 | yes |
| Delay | delay | Delay (in millisecond) between consecutive commands. Recommended value without band pass filter: 1000. Recommended value with band pass filter: somewhere between 200-500. Default: 500 | no |
Important: you must explicitly configure the port in the pilight daemon config or otherwise a random port will be used
and the binding will not be able to connect.
### `contact`, `dimmer`, `switch`, `generic` Things
These things have all one required parameter:
| Parameter Label | Parameter ID | Description | Required |
|-----------------|--------------|------------------------|----------|
| Name | name | Name of pilight device | yes |
## Channels
The `bridge` thing has no channels.
The `contact`, `dimmer` and `switch` things all have one channel:
| Thing | Channel | Type | Description |
|-----------|----------|---------|-------------------------|
| `contact` | state | Contact | State of the contact |
| `dimmer` | dimlevel | Dimmer | Dim level of the dimmer |
| `switch` | state | Switch | State of the switch |
The `generic` thing has no fixed channels, so you have to add them manually. Currently, only String and Number channels
are supported.
## Auto Discovery
### Bridge Auto Discovery
The pilight daemon implements a SSDP interface, which can be used to search for running pilight daemon instances by
sending a SSDP request via multicast udp (this mechanism may only work if
the [standalone mode](https://manual.pilight.org/configuration/settings.html#standalone) in the pilight daemon is
disabled. After loading the binding this bridge discovery is automatically run and scheduled to scan for bridges every
10 minutes.
### Device Auto Discovery
After a `bridge` thing has been configured in openHAB, it automatically establishes a connection between pilight daemon
and openHAB. As soon as the bridge is connected, the devices configured in the pilight daemon are automatically found
via autodiscovery in background (or via a manually triggered discovery) and are displayed in the inbox to easily create
things from them.
## Full Example
things/pilight.things
```
Bridge pilight:bridge:raspi "Pilight Daemon raspi" [ ipAddress="192.168.1.1", port=5000 ] {
Thing switch office "Office" [ name="office" ]
Thing dimmer piano "Piano" [ name="piano" ]
Thing generic weather "Weather" [ name="weather" ] {
Channels:
State Number : temperature [ property="temperature"]
State Number : humidity [ property="humidity"]
}
}
```
items/pilight.items
```
Switch office_switch "Büro" { channel="pilight:switch:raspi:office:state" }
Dimmer piano_light "Klavier [%.0f %%]" { channel="pilight:dimmer:raspi:piano:dimlevel" }
Number weather_temperature "Aussentemperatur [%.1f °C]" <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
```

View File

@ -0,0 +1,17 @@
<?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>

View File

@ -0,0 +1,9 @@
<?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>

View File

@ -0,0 +1,64 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pilight.internal.dto.Config;
import org.openhab.binding.pilight.internal.dto.Status;
import org.openhab.binding.pilight.internal.dto.Version;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
/**
* Callback interface to signal any listeners that an update was received from pilight
*
* @author Jeroen Idserda - Initial contribution
* @author Stefan Röllin - Port to openHAB 2 pilight binding
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
@NonNullByDefault
public interface IPilightCallback {
/**
* Update thing status
*
* @param status status of thing
* @param statusDetail status detail of thing
* @param description description of thing status
*/
void updateThingStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description);
/**
* Update for one or more device received.
*
* @param allStatus list of Object containing list of devices that were updated and their current state
*/
void statusReceived(List<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);
}

View File

@ -0,0 +1,45 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link PilightBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Stefan Röllin - Initial contribution
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
@NonNullByDefault
public class PilightBindingConstants {
public static final String BINDING_ID = "pilight";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
public static final ThingTypeUID THING_TYPE_CONTACT = new ThingTypeUID(BINDING_ID, "contact");
public static final ThingTypeUID THING_TYPE_DIMMER = new ThingTypeUID(BINDING_ID, "dimmer");
public static final ThingTypeUID THING_TYPE_SWITCH = new ThingTypeUID(BINDING_ID, "switch");
public static final ThingTypeUID THING_TYPE_GENERIC = new ThingTypeUID(BINDING_ID, "generic");
// List of property names
public static final String PROPERTY_IP_ADDRESS = "ipAddress";
public static final String PROPERTY_PORT = "port";
public static final String PROPERTY_NAME = "name";
// List of all Channel ids
public static final String CHANNEL_STATE = "state";
public static final String CHANNEL_DIMLEVEL = "dimlevel";
}

View File

@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link PilightBridgeConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Stefan Röllin - Initial contribution
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
@NonNullByDefault
public class PilightBridgeConfiguration {
private String ipAddress = "";
private int port = 0;
private int delay = 500;
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public int getDelay() {
return delay;
}
public void setDelay(Integer delay) {
this.delay = delay;
}
}

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link PilightChannelConfiguration} class contains fields mapping channel configuration parameters.
*
* @author Stefan Röllin - Initial contribution
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
@NonNullByDefault
public class PilightChannelConfiguration {
private String property = "";
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}

View File

@ -0,0 +1,264 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal;
import java.io.*;
import java.net.Socket;
import java.util.Collections;
import java.util.concurrent.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pilight.internal.dto.*;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.MappingJsonFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* This class listens for updates from the pilight daemon. It is also responsible for requesting
* and propagating the current pilight configuration.
*
* @author Jeroen Idserda - Initial contribution
* @author Stefan Röllin - Port to openHAB 2 pilight binding
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*
*/
@NonNullByDefault
public class PilightConnector implements Runnable, Closeable {
private static final int RECONNECT_DELAY_MSEC = 10 * 1000; // 10 seconds
private final Logger logger = LoggerFactory.getLogger(PilightConnector.class);
private final PilightBridgeConfiguration config;
private final IPilightCallback callback;
private final ObjectMapper inputMapper = new ObjectMapper(
new MappingJsonFactory().configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false));
private final ObjectMapper outputMapper = new ObjectMapper(
new MappingJsonFactory().configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false))
.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
private @Nullable Socket socket;
private @Nullable PrintStream printStream;
private final ScheduledExecutorService scheduler;
private final ConcurrentLinkedQueue<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());
}
}
}

View File

@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link PilightDeviceConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Stefan Röllin - Initial contribution
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
@NonNullByDefault
public class PilightDeviceConfiguration {
private String name = "";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,89 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal;
import static org.openhab.binding.pilight.internal.PilightBindingConstants.*;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pilight.internal.handler.PilightBridgeHandler;
import org.openhab.binding.pilight.internal.handler.PilightContactHandler;
import org.openhab.binding.pilight.internal.handler.PilightDimmerHandler;
import org.openhab.binding.pilight.internal.handler.PilightGenericHandler;
import org.openhab.binding.pilight.internal.handler.PilightSwitchHandler;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.openhab.core.thing.type.ChannelTypeRegistry;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link PilightHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Stefan Röllin - Initial contribution
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
@NonNullByDefault
@Component(configurationPid = "binding.pilight", service = ThingHandlerFactory.class)
public class PilightHandlerFactory extends BaseThingHandlerFactory {
public static final Set<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;
}
}

View File

@ -0,0 +1,150 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.discovery;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pilight.internal.PilightBindingConstants;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PilightBridgeDiscoveryService} is responsible for discovering new pilight daemons on the network
* by sending a ssdp multicast request via udp.
*
* @author Niklas Dörfler - Initial contribution
*/
@NonNullByDefault
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.pilight")
public class PilightBridgeDiscoveryService extends AbstractDiscoveryService {
private static final int AUTODISCOVERY_SEARCH_TIME_SEC = 5;
private static final int AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL_SEC = 60 * 10;
private static final String SSDP_DISCOVERY_REQUEST_MESSAGE = "M-SEARCH * HTTP/1.1\r\n"
+ "Host:239.255.255.250:1900\r\n" + "ST:urn:schemas-upnp-org:service:pilight:1\r\n"
+ "Man:\"ssdp:discover\"\r\n" + "MX:3\r\n\r\n";
public static final String SSDP_MULTICAST_ADDRESS = "239.255.255.250";
public static final int SSDP_PORT = 1900;
public static final int SSDP_WAIT_TIMEOUT = 2000; // in milliseconds
private final Logger logger = LoggerFactory.getLogger(PilightBridgeDiscoveryService.class);
private @Nullable ScheduledFuture<?> backgroundDiscoveryJob;
public PilightBridgeDiscoveryService() throws IllegalArgumentException {
super(getSupportedThingTypeUIDs(), AUTODISCOVERY_SEARCH_TIME_SEC, true);
}
public static Set<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;
}
}
}

View File

@ -0,0 +1,223 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.discovery;
import static org.openhab.binding.pilight.internal.PilightBindingConstants.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pilight.internal.PilightHandlerFactory;
import org.openhab.binding.pilight.internal.dto.Config;
import org.openhab.binding.pilight.internal.dto.DeviceType;
import org.openhab.binding.pilight.internal.dto.Status;
import org.openhab.binding.pilight.internal.handler.PilightBridgeHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PilightDeviceDiscoveryService} discovers pilight devices after a bridge thing has been created and
* connected to the pilight daemon. Things are discovered periodically in the background or after a manual trigger.
*
* @author Niklas Dörfler - Initial contribution
*/
@NonNullByDefault
public class PilightDeviceDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
private static final Set<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;
}
}

View File

@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.dto;
/**
* This message is sent when we want to change the state of a device or request the
* current configuration in pilight.
*
* @author Jeroen Idserda - Initial contribution
* @author Stefan Röllin - Port to openHAB 2 pilight binding
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
public class Action {
public static final String ACTION_SEND = "send";
public static final String ACTION_CONTROL = "control";
public static final String ACTION_REQUEST_CONFIG = "request config";
public static final String ACTION_REQUEST_VALUES = "request values";
private String action;
private Code code;
private Options options;
public Action(String action) {
this.action = action;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public Code getCode() {
return code;
}
public void setCode(Code code) {
this.code = code;
}
public Options getOptions() {
return options;
}
public void setOptions(Options options) {
this.options = options;
}
}

View File

@ -0,0 +1,45 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.dto;
import java.util.ArrayList;
import java.util.List;
/**
* All status messages.
*
* @author Stefan Röllin - Initial contribution
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
public class AllStatus {
private String message;
private List<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;
}
}

View File

@ -0,0 +1,60 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.dto;
/**
* Part of the {@link Action} message that is sent to pilight.
* This contains the desired state for a single device.
*
* {@link http://www.pilight.org/development/api/#sender}
*
* @author Jeroen Idserda - Initial contribution
* @author Stefan Röllin - Port to openHAB 2 pilight binding
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
public class Code {
public static final String STATE_ON = "on";
public static final String STATE_OFF = "off";
private String device;
private String state;
private Values values;
public String getDevice() {
return device;
}
public void setDevice(String device) {
this.device = device;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Values getValues() {
return values;
}
public void setValues(Values values) {
this.values = values;
}
}

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.dto;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* pilight configuration object
*
* {@link http://www.pilight.org/development/api/#controller}
*
* @author Jeroen Idserda - Initial contribution
* @author Stefan Röllin - Port to openHAB 2 pilight binding
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class Config {
private Map<String, Device> devices;
public Map<String, Device> getDevices() {
return devices;
}
public void setDevices(Map<String, Device> devices) {
this.devices = devices;
}
}

View File

@ -0,0 +1,140 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.dto;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Class describing a device in pilight
*
* @author Jeroen Idserda - Initial contribution
* @author Stefan Röllin - Port to openHAB 2 pilight binding
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class Device {
private String uuid;
private String origin;
private String timestamp;
private List<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());
}
}

View File

@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.dto;
/**
* Different types of devices in pilight
*
* @author Jeroen Idserda - Initial contribution
* @author Stefan Röllin - Port to openHAB 2 pilight binding
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
public class DeviceType {
public static final Integer SERVER = -1;
public static final Integer SWITCH = 1;
public static final Integer DIMMER = 2;
public static final Integer VALUE = 3;
public static final Integer CONTACT = 6;
}

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.dto;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* This object is sent to pilight right after the initial connection. It describes what kind of client we want to be.
*
* @author Jeroen Idserda - Initial contribution
* @author Stefan Röllin - Port to openHAB 2 pilight binding
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
@NonNullByDefault
public class Identification {
public static final String ACTION_IDENTIFY = "identify";
private String action;
private Options options = new Options();
public Identification() {
this.action = ACTION_IDENTIFY;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public Options getOptions() {
return options;
}
public void setOptions(Options options) {
this.options = options;
}
}

View File

@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.dto;
/**
* Wrapper for the {@code Config} object
*
* @author Jeroen Idserda - Initial contribution
* @author Stefan Röllin - Port to openHAB 2 pilight binding
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
public class Message {
private Config config;
private String message;
public Config getConfig() {
return config;
}
public void setConfig(Config config) {
this.config = config;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@ -0,0 +1,116 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.dto;
import org.openhab.binding.pilight.internal.serializers.BooleanToIntegerSerializer;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
/**
* Options that can be set as a pilight client.
*
* @author Jeroen Idserda - Initial contribution
* @author Stefan Röllin - Port to openHAB 2 pilight binding
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
public class Options {
public static final String MEDIA_ALL = "all";
public static final String MEDIA_WEB = "web";
public static final String MEDIA_MOBILE = "mobile";
public static final String MEDIA_DESKTOP = "desktop";
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonSerialize(using = BooleanToIntegerSerializer.class)
private Boolean core;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonSerialize(using = BooleanToIntegerSerializer.class)
private Boolean receiver;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonSerialize(using = BooleanToIntegerSerializer.class)
private Boolean config;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonSerialize(using = BooleanToIntegerSerializer.class)
private Boolean forward;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonSerialize(using = BooleanToIntegerSerializer.class)
private Boolean stats;
private String uuid;
private String media;
public Boolean getCore() {
return core;
}
public void setCore(Boolean core) {
this.core = core;
}
public Boolean getReceiver() {
return receiver;
}
public void setReceiver(Boolean receiver) {
this.receiver = receiver;
}
public Boolean getConfig() {
return config;
}
public void setConfig(Boolean config) {
this.config = config;
}
public Boolean getForward() {
return forward;
}
public void setForward(Boolean forward) {
this.forward = forward;
}
public Boolean getStats() {
return stats;
}
public void setStats(Boolean stats) {
this.stats = stats;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getMedia() {
return media;
}
public void setMedia(String media) {
this.media = media;
}
}

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.dto;
/**
* Response to a connection or state change request
*
* @author Jeroen Idserda - Initial contribution
* @author Stefan Röllin - Port to openHAB 2 pilight binding
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
public class Response {
public static final String SUCCESS = "success";
public static final String FAILURE = "failure";
private String status;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public boolean isSuccess() {
return SUCCESS.equals(status);
}
}

View File

@ -0,0 +1,81 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.dto;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A Status message is received when a device in pilight changes state.
*
* @author Jeroen Idserda - Initial contribution
* @author Stefan Röllin - Port to openHAB 2 pilight binding
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
public class Status {
private String origin;
private Integer type;
private String uuid;
private List<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;
}
}

View File

@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.dto;
/**
* Describes the specific properties of a device
*
* @author Jeroen Idserda - Initial contribution
* @author Stefan Röllin - Port to openHAB 2 pilight binding
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
public class Values {
private Integer dimlevel;
public Integer getDimlevel() {
return dimlevel;
}
public void setDimlevel(Integer dimlevel) {
this.dimlevel = dimlevel;
}
}

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* pilight version information object
*
* {@link http://www.pilight.org/development/api/#controller}
*
* @author Niklas Dörfler - Initial contribution
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class Version {
private String version;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}

View File

@ -0,0 +1,127 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pilight.internal.PilightDeviceConfiguration;
import org.openhab.binding.pilight.internal.dto.Action;
import org.openhab.binding.pilight.internal.dto.Config;
import org.openhab.binding.pilight.internal.dto.Device;
import org.openhab.binding.pilight.internal.dto.Status;
import org.openhab.core.thing.*;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.BridgeHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PilightBaseHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Stefan Röllin - Initial contribution
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
@NonNullByDefault
public abstract class PilightBaseHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(PilightBaseHandler.class);
private String name = "";
public PilightBaseHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
refreshConfigAndStatus();
return;
}
@Nullable
Action action = createUpdateCommand(channelUID, command);
if (action != null) {
sendAction(action);
}
}
@Override
public void initialize() {
PilightDeviceConfiguration config = getConfigAs(PilightDeviceConfiguration.class);
name = config.getName();
refreshConfigAndStatus();
}
public void updateFromStatusIfMatches(Status status) {
if (status.getDevices() != null && !status.getDevices().isEmpty()) {
if (name.equals(status.getDevices().get(0))) {
if (!ThingStatus.ONLINE.equals(getThing().getStatus())) {
updateStatus(ThingStatus.ONLINE);
}
updateFromStatus(status);
}
}
}
public void updateFromConfigIfMatches(Config config) {
Device device = config.getDevices().get(getName());
if (device != null) {
updateFromConfigDevice(device);
}
}
abstract void updateFromStatus(Status status);
abstract void updateFromConfigDevice(Device device);
abstract @Nullable Action createUpdateCommand(ChannelUID channelUID, Command command);
protected String getName() {
return name;
}
private void sendAction(Action action) {
final @Nullable PilightBridgeHandler handler = getPilightBridgeHandler();
if (handler != null) {
handler.sendAction(action);
} else {
logger.warn("No pilight bridge handler found to send action.");
}
}
private void refreshConfigAndStatus() {
final @Nullable PilightBridgeHandler handler = getPilightBridgeHandler();
if (handler != null) {
handler.refreshConfigAndStatus();
} else {
logger.warn("No pilight bridge handler found to refresh config and status.");
}
}
private @Nullable PilightBridgeHandler getPilightBridgeHandler() {
final @Nullable Bridge bridge = getBridge();
if (bridge != null) {
@Nullable
BridgeHandler handler = bridge.getHandler();
if (handler instanceof PilightBridgeHandler) {
return (PilightBridgeHandler) handler;
}
}
return null;
}
}

View File

@ -0,0 +1,233 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.handler;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pilight.internal.IPilightCallback;
import org.openhab.binding.pilight.internal.PilightBridgeConfiguration;
import org.openhab.binding.pilight.internal.PilightConnector;
import org.openhab.binding.pilight.internal.discovery.PilightDeviceDiscoveryService;
import org.openhab.binding.pilight.internal.dto.*;
import org.openhab.core.common.NamedThreadFactory;
import org.openhab.core.thing.*;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PilightBridgeHandler} is responsible dispatching commands for the child
* things to the Pilight daemon and sending status updates to the child things.
*
* @author Stefan Röllin - Initial contribution
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
@NonNullByDefault
public class PilightBridgeHandler extends BaseBridgeHandler {
private static final int REFRESH_CONFIG_MSEC = 500;
private final Logger logger = LoggerFactory.getLogger(PilightBridgeHandler.class);
private @Nullable PilightConnector connector = null;
private @Nullable ScheduledFuture<?> refreshJob = null;
private @Nullable PilightDeviceDiscoveryService discoveryService = null;
private final ExecutorService connectorExecutor = Executors
.newSingleThreadExecutor(new NamedThreadFactory(getThing().getUID().getAsString(), true));
public PilightBridgeHandler(Bridge bridge) {
super(bridge);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("Pilight Bridge is read-only and does not handle commands.");
}
@Override
public void initialize() {
PilightBridgeConfiguration config = getConfigAs(PilightBridgeConfiguration.class);
final @Nullable PilightDeviceDiscoveryService discoveryService = this.discoveryService;
PilightConnector connector = new PilightConnector(config, new IPilightCallback() {
@Override
public void updateThingStatus(ThingStatus status, ThingStatusDetail statusDetail,
@Nullable String description) {
updateStatus(status, statusDetail, description);
if (status == ThingStatus.ONLINE) {
refreshConfigAndStatus();
}
}
@Override
public void statusReceived(List<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);
}
}
}

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.handler;
import static org.openhab.binding.pilight.internal.PilightBindingConstants.CHANNEL_STATE;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pilight.internal.dto.Action;
import org.openhab.binding.pilight.internal.dto.Device;
import org.openhab.binding.pilight.internal.dto.Status;
import org.openhab.binding.pilight.internal.types.PilightContactType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PilightContactHandler} is responsible for handling a pilight contact.
*
* @author Stefan Röllin - Initial contribution
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
@NonNullByDefault
public class PilightContactHandler extends PilightBaseHandler {
private final Logger logger = LoggerFactory.getLogger(PilightContactHandler.class);
public PilightContactHandler(Thing thing) {
super(thing);
}
@Override
protected void updateFromStatus(Status status) {
String state = status.getValues().get("state");
if (state != null) {
updateState(CHANNEL_STATE, PilightContactType.valueOf(state.toUpperCase()).toOpenClosedType());
}
}
@Override
void updateFromConfigDevice(Device device) {
}
@Override
protected @Nullable Action createUpdateCommand(ChannelUID channelUID, Command command) {
logger.warn("A contact is a read only device");
return null;
}
}

View File

@ -0,0 +1,126 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.handler;
import static org.openhab.binding.pilight.internal.PilightBindingConstants.CHANNEL_DIMLEVEL;
import java.math.BigDecimal;
import java.math.RoundingMode;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pilight.internal.dto.Action;
import org.openhab.binding.pilight.internal.dto.Code;
import org.openhab.binding.pilight.internal.dto.Device;
import org.openhab.binding.pilight.internal.dto.Status;
import org.openhab.binding.pilight.internal.dto.Values;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PilightDimmerHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Stefan Röllin - Initial contribution
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
@NonNullByDefault
public class PilightDimmerHandler extends PilightBaseHandler {
private static final int MAX_DIM_LEVEL_DEFAULT = 15;
private static final BigDecimal BIG_DECIMAL_100 = new BigDecimal(100);
private final Logger logger = LoggerFactory.getLogger(PilightDimmerHandler.class);
private int maxDimLevel = MAX_DIM_LEVEL_DEFAULT;
public PilightDimmerHandler(Thing thing) {
super(thing);
}
@Override
protected void updateFromStatus(Status status) {
BigDecimal dimLevel = BigDecimal.ZERO;
String dimLevelAsString = status.getValues().get("dimlevel");
if (dimLevelAsString != null) {
dimLevel = getPercentageFromDimLevel(dimLevelAsString);
} else {
// Dimmer items can can also be switched on or off in pilight.
// When this happens, the dimmer value is not reported. At least we know it's on or off.
String stateAsString = status.getValues().get("state");
if (stateAsString != null) {
State state = OnOffType.valueOf(stateAsString.toUpperCase());
dimLevel = state.equals(OnOffType.ON) ? BIG_DECIMAL_100 : BigDecimal.ZERO;
}
}
State state = new PercentType(dimLevel);
updateState(CHANNEL_DIMLEVEL, state);
}
@Override
protected void updateFromConfigDevice(Device device) {
Integer max = device.getDimlevelMaximum();
if (max != null) {
maxDimLevel = max;
}
}
@Override
protected @Nullable Action createUpdateCommand(ChannelUID unused, Command command) {
Code code = new Code();
code.setDevice(getName());
if (command instanceof OnOffType) {
code.setState(command.equals(OnOffType.ON) ? Code.STATE_ON : Code.STATE_OFF);
} else if (command instanceof PercentType) {
setDimmerValue((PercentType) command, code);
} else {
logger.warn("Only OnOffType and PercentType are supported by a dimmer.");
return null;
}
Action action = new Action(Action.ACTION_CONTROL);
action.setCode(code);
return action;
}
private BigDecimal getPercentageFromDimLevel(String string) {
return new BigDecimal(string).setScale(2).divide(new BigDecimal(maxDimLevel), RoundingMode.HALF_UP)
.multiply(BIG_DECIMAL_100);
}
private void setDimmerValue(PercentType percent, Code code) {
if (PercentType.ZERO.equals(percent)) {
// pilight is not responding to commands that set both the dimlevel to 0 and state to off.
// So, we're only updating the state for now
code.setState(Code.STATE_OFF);
} else {
BigDecimal dimlevel = percent.toBigDecimal().setScale(2).divide(BIG_DECIMAL_100, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(maxDimLevel)).setScale(0, RoundingMode.HALF_UP);
Values values = new Values();
values.setDimlevel(dimlevel.intValue());
code.setValues(values);
code.setState(dimlevel.compareTo(BigDecimal.ZERO) == 1 ? Code.STATE_ON : Code.STATE_OFF);
}
}
}

View File

@ -0,0 +1,138 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.handler;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pilight.internal.PilightChannelConfiguration;
import org.openhab.binding.pilight.internal.dto.Action;
import org.openhab.binding.pilight.internal.dto.Device;
import org.openhab.binding.pilight.internal.dto.Status;
import org.openhab.core.library.CoreItemFactory;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.type.ChannelType;
import org.openhab.core.thing.type.ChannelTypeRegistry;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.StateDescription;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PilightGenericHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Stefan Röllin - Initial contribution
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
@NonNullByDefault
public class PilightGenericHandler extends PilightBaseHandler {
private final Logger logger = LoggerFactory.getLogger(PilightGenericHandler.class);
private final ChannelTypeRegistry channelTypeRegistry;
private final Map<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;
}
}

View File

@ -0,0 +1,73 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.handler;
import static org.openhab.binding.pilight.internal.PilightBindingConstants.CHANNEL_STATE;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pilight.internal.dto.Action;
import org.openhab.binding.pilight.internal.dto.Code;
import org.openhab.binding.pilight.internal.dto.Device;
import org.openhab.binding.pilight.internal.dto.Status;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PilightSwitchHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Stefan Röllin - Initial contribution
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
@NonNullByDefault
public class PilightSwitchHandler extends PilightBaseHandler {
private final Logger logger = LoggerFactory.getLogger(PilightSwitchHandler.class);
public PilightSwitchHandler(Thing thing) {
super(thing);
}
@Override
protected void updateFromStatus(Status status) {
String state = status.getValues().get("state");
if (state != null) {
updateState(CHANNEL_STATE, OnOffType.valueOf(state.toUpperCase()));
}
}
@Override
void updateFromConfigDevice(Device device) {
}
@Override
protected @Nullable Action createUpdateCommand(ChannelUID unused, Command command) {
if (command instanceof OnOffType) {
Code code = new Code();
code.setDevice(getName());
code.setState(command.equals(OnOffType.ON) ? Code.STATE_ON : Code.STATE_OFF);
Action action = new Action(Action.ACTION_CONTROL);
action.setCode(code);
return action;
}
logger.warn("A pilight switch only accepts OnOffType commands.");
return null;
}
}

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.serializers;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
/**
* Serializer to map boolean values to an integer (1 and 0).
*
* @author Jeroen Idserda - Initial contribution
* @author Stefan Röllin - Port to openHAB 2 pilight binding
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
@NonNullByDefault
public class BooleanToIntegerSerializer extends JsonSerializer<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);
}
}
}

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pilight.internal.types;
import org.openhab.core.library.types.OpenClosedType;
/**
* Enum to represent the state of a contact sensor in pilight
*
* @author Jeroen Idserda - Initial contribution
* @author Stefan Röllin - Port to openHAB 2 pilight binding
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
public enum PilightContactType {
OPENED,
CLOSED;
public OpenClosedType toOpenClosedType() {
return this.equals(PilightContactType.OPENED) ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
}
}

View File

@ -0,0 +1,11 @@
<?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>

View File

@ -0,0 +1,15 @@
<?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>

View File

@ -0,0 +1,38 @@
# binding
binding.pilight.name = Pilight Binding
binding.pilight.description = Das pilight-Binding ermöglicht openHAB die Kommunikation mit einer pilight-Instanz. Pilight ist ein Dienst, der verwendet wird, um 'Click On Click Off'-Geräte wie bspw. 433 MHz Funksteckdosen auf kostengünstige Weise zu steuern, z.B. durch Verwendung eines Raspberry Pi mit entsprechendem 433 MHz Sender.
# thing types
thing-type.pilight.bridge.label = Pilight Bridge
thing-type.pilight.bridge.description = Verbindung zwischen openHAB und einem pilight Daemon.
thing-type.pilight.contact.label = Pilight Kontakt
thing-type.pilight.contact.description = Pilight Kontakt
thing-type.pilight.dimmer.label = Pilight Dimmer
thing-type.pilight.dimmer.description = Pilight Dimmer
thing-type.pilight.switch.label = Pilight Schalter
thing-type.pilight.switch.description = Pilight Schalter
thing-type.pilight.generic.label = Generisches pilight Gerät
thing-type.pilight.generic.description = Gerät bei dem die Kanäle dynamisch hinzugefügt werden.
# thing type config description
thing-type.config.pilight.bridge.ipAddress.label = IP-Adresse
thing-type.config.pilight.bridge.ipAddress.description = Lokale IP-Adresse oder Hostname des pilight Daemons.
thing-type.config.pilight.bridge.port.label = Port
thing-type.config.pilight.bridge.port.description = Port des pilight Daemons.
thing-type.config.pilight.bridge.delay.label = Verzögerung
thing-type.config.pilight.bridge.delay.description = Verzögerung (in Millisekunden) zwischen zwei Kommandos. Empfohlener Wert ohne Bandpassfilter: 1000 und mit Bandpassfilter zwischen 200 und 500.
thing-type.config.pilight.device.name.label = Name
thing-type.config.pilight.device.name.description = Name des pilight Geräts
# channel types
channel-type.pilight.contact-state.label = Status
channel-type.pilight.contact-state.description = Status des pilight Kontakts
channel-type.pilight.switch-state.label = Status
channel-type.pilight.switch-state.description = Status des pilight Schalters
channel-type.pilight.dimlevel.label = Dimmerwert
channel-type.pilight.dimlevel.description = Wert des pilight Dimmers

View File

@ -0,0 +1,39 @@
<?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>

View File

@ -0,0 +1,96 @@
<?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>

View File

@ -239,6 +239,7 @@
<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>