[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
42 changed files with 2937 additions and 0 deletions

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>