added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.wifiled-${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-wifiled" description="WiFi LED Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.wifiled/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2020 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.wifiled.internal;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link WiFiLEDBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Osman Basha - Initial contribution
*/
@NonNullByDefault
public class WiFiLEDBindingConstants {
public static final String BINDING_ID = "wifiled";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_WIFILED = new ThingTypeUID(BINDING_ID, "wifiled");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_WIFILED);
// List of all Channel IDs
public static final String CHANNEL_POWER = "power";
public static final String CHANNEL_COLOR = "color";
public static final String CHANNEL_WHITE = "white";
public static final String CHANNEL_WHITE2 = "white2";
public static final String CHANNEL_PROGRAM = "program";
public static final String CHANNEL_PROGRAM_SPEED = "programSpeed";
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2020 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.wifiled.internal;
import static org.openhab.binding.wifiled.internal.WiFiLEDBindingConstants.THING_TYPE_WIFILED;
import java.util.Collections;
import java.util.Set;
import org.openhab.binding.wifiled.internal.handler.WiFiLEDHandler;
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.osgi.service.component.annotations.Component;
/**
* The {@link WiFiLEDHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Osman Basha - Initial contribution
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.wifiled")
public class WiFiLEDHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_WIFILED);
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_TYPE_WIFILED)) {
return new WiFiLEDHandler(thing);
}
return null;
}
}

View File

@@ -0,0 +1,86 @@
/**
* Copyright (c) 2010-2020 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.wifiled.internal.configuration;
/**
* The {@link WiFiLEDConfig} class holds the configuration properties of the thing.
*
* @author Osman Basha - Initial contribution
* @author Stefan Endrullis - Initial contribution
*/
public class WiFiLEDConfig {
private String ip;
private Integer port;
private Integer pollingPeriod;
private String protocol;
private String driver;
private Integer fadeDurationInMs;
private Integer fadeSteps;
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public Integer getPollingPeriod() {
return pollingPeriod;
}
public void setPollingPeriod(Integer pollingPeriod) {
this.pollingPeriod = pollingPeriod;
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
public Integer getFadeDurationInMs() {
return fadeDurationInMs;
}
public void setFadeDurationInMs(Integer fadeDurationInMs) {
this.fadeDurationInMs = fadeDurationInMs;
}
public Integer getFadeSteps() {
return fadeSteps;
}
public void setFadeSteps(Integer fadeSteps) {
this.fadeSteps = fadeSteps;
}
}

View File

@@ -0,0 +1,142 @@
/**
* Copyright (c) 2010-2020 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.wifiled.internal.discovery;
import static org.openhab.binding.wifiled.internal.WiFiLEDBindingConstants.*;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.wifiled.internal.handler.AbstractWiFiLEDDriver;
import org.openhab.binding.wifiled.internal.handler.ClassicWiFiLEDDriver;
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 WiFiLEDDiscoveryService} class implements a service
* for discovering supported WiFi LED Devices.
*
* @author Osman Basha - Initial contribution
*/
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.wifiled")
public class WiFiLEDDiscoveryService extends AbstractDiscoveryService {
private static final int DEFAULT_BROADCAST_PORT = 48899;
private static final String DISCOVER_MESSAGE = "HF-A11ASSISTHREAD";
private Logger logger = LoggerFactory.getLogger(WiFiLEDDiscoveryService.class);
public WiFiLEDDiscoveryService() {
super(SUPPORTED_THING_TYPES_UIDS, 15, true);
}
@Override
public Set<ThingTypeUID> getSupportedThingTypes() {
return SUPPORTED_THING_TYPES_UIDS;
}
@Override
protected void startBackgroundDiscovery() {
logger.debug("Start WiFi LED background discovery");
scheduler.schedule(() -> discover(), 0, TimeUnit.SECONDS);
}
@Override
public void startScan() {
logger.debug("Start WiFi LED scan");
discover();
}
private synchronized void discover() {
logger.debug("Try to discover all WiFi LED devices");
try (DatagramSocket socket = new DatagramSocket(DEFAULT_BROADCAST_PORT)) {
socket.setBroadcast(true);
socket.setSoTimeout(5000);
InetAddress inetAddress = InetAddress.getByName("255.255.255.255");
// send discover
byte[] discover = DISCOVER_MESSAGE.getBytes();
DatagramPacket packet = new DatagramPacket(discover, discover.length, inetAddress, DEFAULT_BROADCAST_PORT);
socket.send(packet);
logger.debug("Discover message sent: '{}'", DISCOVER_MESSAGE);
// wait for responses
while (true) {
byte[] rxbuf = new byte[256];
packet = new DatagramPacket(rxbuf, rxbuf.length);
try {
socket.receive(packet);
} catch (SocketTimeoutException e) {
logger.trace("Timeout exceeded. Discovery process ended.");
break;
}
byte[] data = packet.getData();
String s = bytesToString(data);
logger.debug("Discovery response received: '{}' [{}] ", s, ClassicWiFiLEDDriver.bytesToHex(data));
// 192.168.178.25,ACCF23489C9A,HF-LPB100-ZJ200
// ^-IP..........,^-MAC.......,^-HOSTNAME.....
String[] ss = s.split(",");
if (ss.length < 3) {
logger.debug("Ignoring unparseable discovery response: '{}'", s);
continue;
}
String ip = ss[0];
String mac = ss[1];
String name = ss[2];
logger.debug("Adding a new WiFi LED with IP '{}' and MAC '{}' to inbox", ip, mac);
Map<String, Object> properties = new HashMap<>();
properties.put("ip", ip);
properties.put("protocol", AbstractWiFiLEDDriver.Protocol.LD382A);
ThingUID uid = new ThingUID(THING_TYPE_WIFILED, mac);
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(name)
.build();
thingDiscovered(result);
logger.debug("Thing discovered '{}'", result);
}
} catch (IOException e) {
logger.debug("Device discovery encountered an I/O Exception: {}", e.getMessage(), e);
}
}
private static String bytesToString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte aByte : bytes) {
if (aByte == 0) {
break;
}
sb.append((char) (aByte & 0xFF));
}
return sb.toString();
}
}

View File

@@ -0,0 +1,216 @@
/**
* Copyright (c) 2010-2020 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.wifiled.internal.handler;
import static org.openhab.binding.wifiled.internal.handler.ClassicWiFiLEDDriver.bytesToHex;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract WiFi LED driver.
*
* @author Osman Basha - Initial contribution
* @author Stefan Endrullis
* @author Ries van Twisk
*/
public abstract class AbstractWiFiLEDDriver {
public enum Protocol {
LD382,
LD382A,
LD686
}
public enum Driver {
CLASSIC,
FADING
}
public static final Integer DEFAULT_PORT = 5577;
protected static final int DEFAULT_SOCKET_TIMEOUT = 5000;
protected Logger logger = LoggerFactory.getLogger(AbstractWiFiLEDDriver.class);
protected String host;
protected int port;
protected Protocol protocol;
public AbstractWiFiLEDDriver(String host, int port, Protocol protocol) {
this.host = host;
this.port = port;
this.protocol = protocol;
}
/**
* Allow to cleanup the driver
*/
public abstract void shutdown();
public abstract void setColor(HSBType color) throws IOException;
public abstract void setBrightness(PercentType brightness) throws IOException;
public abstract void incBrightness(int step) throws IOException;
public void decBrightness(int step) throws IOException {
incBrightness(-step);
}
public abstract void setWhite(PercentType white) throws IOException;
public abstract void incWhite(int step) throws IOException;
public void decWhite(int step) throws IOException {
incWhite(-step);
}
public abstract void setWhite2(PercentType white2) throws IOException;
public abstract void incWhite2(int step) throws IOException;
public void decWhite2(int step) throws IOException {
incWhite2(-step);
}
public abstract void setProgram(StringType program) throws IOException;
public abstract void setProgramSpeed(PercentType speed) throws IOException;
public abstract void incProgramSpeed(int step) throws IOException;
public void decProgramSpeed(int step) throws IOException {
incProgramSpeed(-step);
}
public abstract void setPower(OnOffType command) throws IOException;
public void init() throws IOException {
getLEDState();
}
public abstract LEDStateDTO getLEDStateDTO() throws IOException;
protected synchronized LEDState getLEDState() throws IOException {
try (Socket socket = new Socket(host, port);
DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
DataInputStream inputStream = new DataInputStream(socket.getInputStream())) {
logger.debug("Connected to '{}'", socket);
socket.setSoTimeout(DEFAULT_SOCKET_TIMEOUT);
byte[] data = { (byte) 0x81, (byte) 0x8A, (byte) 0x8B, (byte) 0x96 };
outputStream.write(data);
logger.debug("Data sent: '{}'", bytesToHex(data));
byte[] statusBytes = new byte[14];
inputStream.readFully(statusBytes);
logger.debug("Data read: '{}'", bytesToHex(statusBytes));
// Example response (14 Bytes):
// 0x81 0x04 0x23 0x26 0x21 0x10 0x45 0x00 0x00 0x00 0x03 0x00 0x00 0x47
// ..........^--- On/Off.........R....G....B....WW........CW
// ...............^-- PGM...^---SPEED...............
int state = statusBytes[2] & 0xFF; // On/Off
int program = statusBytes[3] & 0xFF;
int programSpeed = statusBytes[5] & 0xFF;
// On factory default the controller can be configured
// with a value of 255 but max should be 31.
if (programSpeed > 31) {
programSpeed = 31;
}
int red = statusBytes[6] & 0xFF;
int green = statusBytes[7] & 0xFF;
int blue = statusBytes[8] & 0xFF;
int white = statusBytes[9] & 0xFF;
int white2 = protocol == Protocol.LD686 ? statusBytes[11] & 0xFF : 0;
logger.debug("RGBW: {},{},{},{}, {}", red, green, blue, white, white2);
return new LEDState(state, program, programSpeed, red, green, blue, white, white2);
}
}
protected void sendRaw(byte[] data) throws IOException {
sendRaw(data, 100);
}
protected synchronized void sendRaw(byte[] data, int delay) throws IOException {
try (Socket socket = new Socket(host, port);
DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream())) {
logger.debug("Connected to '{}'", socket);
socket.setSoTimeout(DEFAULT_SOCKET_TIMEOUT);
sendRaw(data, outputStream);
if (delay > 0) {
Thread.sleep(delay);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
protected void sendRaw(byte[] data, DataOutputStream outputStream) throws IOException {
byte[] dataWithCS;
// append 0x0F (if dev.type LD382A)
if (protocol == Protocol.LD382A || protocol == Protocol.LD686) {
dataWithCS = new byte[data.length + 2];
dataWithCS[dataWithCS.length - 2] = 0x0F;
} else {
dataWithCS = new byte[data.length + 1];
}
// append checksum
System.arraycopy(data, 0, dataWithCS, 0, data.length);
int cs = 0;
for (int i = 0; i < dataWithCS.length - 1; i++) {
cs += dataWithCS[i];
}
cs = cs & 0xFF;
dataWithCS[dataWithCS.length - 1] = (byte) cs;
outputStream.write(dataWithCS);
logger.debug("RAW data sent: '{}'", bytesToHex(dataWithCS));
}
protected byte[] getBytesForColor(byte r, byte g, byte b, byte w, byte w2) {
byte[] bytes;
if (protocol == Protocol.LD382 || protocol == Protocol.LD382A) {
bytes = new byte[] { 0x31, r, g, b, w, 0x00 };
} else if (protocol == Protocol.LD686) {
bytes = new byte[] { 0x31, r, g, b, w, w2, 0x00 };
} else {
throw new UnsupportedOperationException("Protocol " + protocol + " not yet implemented");
}
return bytes;
}
protected byte[] getBytesForPower(boolean on) {
return new byte[] { 0x71, on ? (byte) 0x23 : 0x24 };
}
}

View File

@@ -0,0 +1,206 @@
/**
* Copyright (c) 2010-2020 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.wifiled.internal.handler;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
/**
* The {@link ClassicWiFiLEDDriver} class is responsible for the communication with the WiFi LED controller.
* It's used for sending color or program settings and also extracting the data out of the received telegrams.
*
* @author Osman Basha - Initial contribution
* @author Stefan Endrullis
* @author Ries van Twisk - Prevent flashes during classic driver color + white updates
*/
public class ClassicWiFiLEDDriver extends AbstractWiFiLEDDriver {
private static final int WAIT_UPDATE_LED_FOR_MS = 25;
private final WiFiLEDHandler wifiLedHandler;
private final ExecutorService updateScheduler = Executors.newSingleThreadExecutor();
private Future<Boolean> ledUpdateFuture = CompletableFuture.completedFuture(null);
private LEDStateDTO cachedLedStatus = null;
public ClassicWiFiLEDDriver(WiFiLEDHandler wifiLedHandler, String host, int port, Protocol protocol) {
super(host, port, protocol);
this.wifiLedHandler = wifiLedHandler;
}
@Override
public void shutdown() {
}
@Override
public synchronized LEDStateDTO getLEDStateDTO() throws IOException {
if (ledUpdateFuture.isDone()) {
LEDState s = getLEDState();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new IOException(e);
}
return LEDStateDTO.valueOf(s.state, s.program, s.programSpeed, s.red, s.green, s.blue, s.white, s.white2);
} else {
return cachedLedStatus;
}
}
@Override
public synchronized void setColor(HSBType color) throws IOException {
logger.debug("Setting color to {}", color);
LEDStateDTO ledState = getLEDStateDTO().withColor(color).withoutProgram();
sendLEDData(ledState);
}
@Override
public synchronized void setBrightness(PercentType brightness) throws IOException {
logger.debug("Setting brightness to {}", brightness);
LEDStateDTO ledState = getLEDStateDTO().withBrightness(brightness).withoutProgram();
sendLEDData(ledState);
}
@Override
public synchronized void incBrightness(int step) throws IOException {
logger.debug("Changing brightness by {}", step);
LEDStateDTO ledState = getLEDStateDTO().withIncrementedBrightness(step).withoutProgram();
sendLEDData(ledState);
}
@Override
public synchronized void setWhite(PercentType white) throws IOException {
logger.debug("Setting (warm) white LED to {}", white);
LEDStateDTO ledState = getLEDStateDTO().withWhite(white).withoutProgram();
sendLEDData(ledState);
}
@Override
public synchronized void incWhite(int step) throws IOException {
logger.debug("Changing white by {}", step);
LEDStateDTO ledState = getLEDStateDTO().withIncrementedWhite(step).withoutProgram();
sendLEDData(ledState);
}
@Override
public void setWhite2(PercentType white2) throws IOException {
logger.debug("Setting (warm) white 2 LED to {}", white2);
LEDStateDTO ledState = getLEDStateDTO().withWhite2(white2).withoutProgram();
sendLEDData(ledState);
}
@Override
public void incWhite2(int step) throws IOException {
logger.debug("Changing white by {}", step);
LEDStateDTO ledState = getLEDStateDTO().withIncrementedWhite2(step).withoutProgram();
sendLEDData(ledState);
}
@Override
public void setPower(OnOffType command) throws IOException {
logger.debug("Power {}", command.name());
sendRaw(getBytesForPower(command == OnOffType.ON));
}
@Override
public synchronized void setProgram(StringType program) throws IOException {
logger.debug("Setting program '{}'", program);
LEDStateDTO ledState = getLEDStateDTO().withProgram(program);
sendLEDData(ledState);
}
@Override
public synchronized void setProgramSpeed(PercentType speed) throws IOException {
logger.debug("Setting program speed to {}", speed);
LEDStateDTO ledState = getLEDStateDTO().withProgramSpeed(speed);
if (speed.equals(PercentType.ZERO)) {
ledState = ledState.withoutProgram();
}
sendLEDData(ledState);
}
@Override
public synchronized void incProgramSpeed(int step) throws IOException {
logger.debug("Changing program speed by {}", step);
LEDStateDTO ledState = getLEDStateDTO().withIncrementedProgramSpeed(step);
sendLEDData(ledState);
}
private synchronized void sendLEDData(final LEDStateDTO ledState) {
cachedLedStatus = ledState;
if (!ledUpdateFuture.isDone()) {
ledUpdateFuture.cancel(true);
}
final byte[] bytes;
int program = Integer.valueOf(ledState.getProgram().toString());
if (program == 0x61) {
// "normal" program: set color etc.
byte r = (byte) (ledState.getRGB() >> 16 & 0xFF);
byte g = (byte) (ledState.getRGB() >> 8 & 0xFF);
byte b = (byte) (ledState.getRGB() & 0xFF);
byte w = (byte) (((int) (ledState.getWhite().doubleValue() * 255 / 100)) & 0xFF);
byte w2 = (byte) (((int) (ledState.getWhite2().doubleValue() * 255 / 100)) & 0xFF);
bytes = getBytesForColor(r, g, b, w, w2);
} else {
// program selected
byte p = (byte) (program & 0xFF);
byte s = (byte) (((100 - ledState.getProgramSpeed().intValue()) * 0x1F / 100) & 0xFF);
bytes = new byte[] { 0x61, p, s };
}
ledUpdateFuture = updateScheduler.submit(() -> {
try {
Thread.sleep(WAIT_UPDATE_LED_FOR_MS);
logger.debug("Setting LED State to {}", bytesToHex(bytes));
sendRaw(bytes);
return true;
} catch (IOException e) {
logger.debug("Exception occurred while sending command to LED", e);
wifiLedHandler.reportCommunicationError(e);
} catch (InterruptedException e) {
// Ignore, this is expected
}
return false;
});
}
public static String bytesToHex(byte[] bytes) {
StringBuilder builder = new StringBuilder();
for (byte aByte : bytes) {
builder.append(String.format("%02x ", aByte));
}
String string = builder.toString();
return string.substring(0, string.length() - 1);
}
}

View File

@@ -0,0 +1,338 @@
/**
* Copyright (c) 2010-2020 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.wifiled.internal.handler;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link FadingWiFiLEDDriver} class is responsible for the communication with the WiFi LED controller.
* It utilizes color fading when changing colors or turning the light on of off.
*
* @author Stefan Endrullis - Initial contribution
* @author Ries van Twisk
*/
public class FadingWiFiLEDDriver extends AbstractWiFiLEDDriver {
public static final int DEFAULT_FADE_DURATION_IN_MS = 1000;
public static final int DEFAULT_FADE_STEPS = 100;
public static final int KEEP_COMMUNICATION_OPEN_FOR_MS = 1000;
private static final InternalLedState BLACK_STATE = new InternalLedState();
private boolean power = false;
private InternalLedState currentState = new InternalLedState(); // Use to not update the controller with the same
// value
private InternalLedState currentFaderState = new InternalLedState();
private InternalLedState targetState = new InternalLedState();
private LEDStateDTO dtoState = LEDStateDTO.valueOf(0, 0, 0, 0, 0, 0, 0, 0);
private final ScheduledExecutorService waiterExecutor = Executors.newSingleThreadScheduledExecutor();
private final ScheduledExecutorService faderExecutor = Executors.newSingleThreadScheduledExecutor();
private LEDFaderRunner ledfaderThread = null;
private final Semaphore ledUpdateSyncSemaphore = new Semaphore(1, false);
private final int fadeDurationInMs;
private final int totalFadingSteps;
public FadingWiFiLEDDriver(String host, int port, AbstractWiFiLEDDriver.Protocol protocol, int fadeDurationInMs,
int totalFadingSteps) {
super(host, port, protocol);
this.fadeDurationInMs = fadeDurationInMs < 10 ? 10 : fadeDurationInMs;
this.totalFadingSteps = totalFadingSteps < 1 ? 1 : totalFadingSteps;
}
@Override
public void init() throws IOException {
try {
LEDState s = getLEDState();
dtoState = LEDStateDTO.valueOf(s.state, s.program, s.programSpeed, s.red, s.green, s.blue, s.white,
s.white2);
power = (s.state & 0x01) != 0;
currentState = InternalLedState.fromRGBW(s.red, s.green, s.blue, s.white, s.white2);
currentFaderState = currentState;
} catch (IOException e) {
logger.warn("IOException", e);
}
}
@Override
public void shutdown() {
waiterExecutor.shutdown();
faderExecutor.shutdown();
try {
if (!waiterExecutor.awaitTermination((fadeDurationInMs / totalFadingSteps) * 2, TimeUnit.MILLISECONDS)) {
waiterExecutor.shutdownNow();
}
if (!faderExecutor.awaitTermination((fadeDurationInMs / totalFadingSteps) * 2, TimeUnit.MILLISECONDS)) {
faderExecutor.shutdownNow();
}
} catch (InterruptedException e) {
// Ignored
}
}
@Override
public void setColor(HSBType color) throws IOException {
dtoState = dtoState.withColor(color);
changeState(targetState.withColor(color));
}
@Override
public void setBrightness(PercentType brightness) throws IOException {
dtoState = dtoState.withBrightness(brightness);
changeState(targetState.withBrightness(brightness.doubleValue() / 100));
}
@Override
public void incBrightness(int step) throws IOException {
dtoState = dtoState.withIncrementedBrightness(step);
changeState(targetState.withBrightness(targetState.getBrightness() + ((double) step / 100)));
}
@Override
public void decBrightness(int step) throws IOException {
dtoState = dtoState.withIncrementedBrightness(-step);
changeState(targetState.withBrightness(targetState.getBrightness() - ((double) step / 100)));
}
@Override
public void setWhite(PercentType white) throws IOException {
dtoState = dtoState.withWhite(white);
changeState(targetState.withWhite(white.doubleValue() / 100));
}
@Override
public void incWhite(int step) throws IOException {
dtoState = dtoState.withIncrementedWhite(step);
changeState(targetState.withWhite(targetState.getWhite() + ((double) step / 100)));
}
@Override
public void setWhite2(PercentType white2) throws IOException {
dtoState = dtoState.withWhite2(white2);
changeState(targetState.withWhite2(white2.doubleValue() / 100));
}
@Override
public void incWhite2(int step) throws IOException {
dtoState = dtoState.withIncrementedWhite2(step);
changeState(targetState.withWhite2(targetState.getWhite2() + ((double) step / 100)));
}
@Override
public void setProgram(StringType program) throws IOException {
}
@Override
public void setProgramSpeed(PercentType speed) throws IOException {
}
@Override
public void incProgramSpeed(int step) throws IOException {
}
@Override
public void setPower(OnOffType command) throws IOException {
dtoState = dtoState.withPower(command);
power = command == OnOffType.ON;
fadeToState(power ? targetState : BLACK_STATE);
}
@Override
public LEDStateDTO getLEDStateDTO() throws IOException {
return dtoState;
}
private void changeState(final InternalLedState newState) throws IOException {
targetState = newState;
if (power) {
fadeToState(targetState);
}
}
/**
* Runnable that takes care of fading of the LED's
*/
static final class LEDFaderRunner implements Runnable {
private final Logger logger = LoggerFactory.getLogger(LEDFaderRunner.class);
private String host;
private int port;
private InternalLedState fromState;
private InternalLedState toState;
private final int totalFadingSteps;
private final long keepCommOpenForMS;
private final Function<DataOutputStream, Boolean> powerOnFunc;
private final BiFunction<DataOutputStream, InternalLedState, Boolean> ledSender;
private long lastCommunicationTime = 0;
private int currentFadingStep = 1;
private final Lock lock = new ReentrantLock();
private InternalLedState currentFadeState;
private Socket socket;
private DataOutputStream outputStream;
public LEDFaderRunner(String host, int port, InternalLedState fromState, InternalLedState toState,
int totalFadingSteps, int keepCommOpenForMS, Function<DataOutputStream, Boolean> powerOnFunc,
BiFunction<DataOutputStream, InternalLedState, Boolean> ledSender) {
this.host = host;
this.port = port;
this.fromState = fromState;
this.toState = toState;
this.totalFadingSteps = totalFadingSteps;
this.keepCommOpenForMS = keepCommOpenForMS;
this.powerOnFunc = powerOnFunc;
this.ledSender = ledSender;
}
/**
* Call before starting a thre`ad, it will initialise a socket and power on the LEDs
*
* @throws IOException
*/
public void init() throws IOException {
socket = new Socket(host, port);
socket.setSoTimeout(DEFAULT_SOCKET_TIMEOUT);
outputStream = new DataOutputStream(socket.getOutputStream());
logger.debug("Connected to '{}'", socket);
powerOnFunc.apply(outputStream);
currentFadeState = fromState;
}
public void setToState(InternalLedState toState) {
lock.lock();
this.fromState = currentFadeState;
this.toState = toState;
this.currentFadingStep = 1;
lock.unlock();
}
@Override
public void run() {
lock.lock();
if (currentFadingStep <= totalFadingSteps) {
currentFadeState = fromState.fade(toState, (double) currentFadingStep / totalFadingSteps);
lock.unlock();
logger.debug("currentFadeState: {}", currentFadeState);
if (!ledSender.apply(outputStream, currentFadeState)) {
logger.warn("Failed sending at step {}", currentFadingStep);
throw new IllegalStateException("Failed sending at step " + currentFadingStep);
}
lastCommunicationTime = System.currentTimeMillis();
} else {
lock.unlock();
if (lastCommunicationTime < (System.currentTimeMillis() - keepCommOpenForMS)) {
throw new IllegalStateException("Reached end step");
}
}
currentFadingStep++;
}
public void shutdown() {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
// Ignored
}
}
}
}
private synchronized void fadeToState(final InternalLedState newTargetState) throws IOException {
if (ledUpdateSyncSemaphore.tryAcquire(1)) {
// Create and Execute a new LED Fader
ledfaderThread = new LEDFaderRunner(host, port, currentFaderState, newTargetState, totalFadingSteps,
KEEP_COMMUNICATION_OPEN_FOR_MS, (outputStream) -> {
try {
sendRaw(getBytesForPower(true), outputStream);
return true;
} catch (IOException e) {
logger.warn("IOException", e);
return false;
}
}, (outputStream, fs) -> {
try {
sendLEDData(fs, outputStream);
currentFaderState = fs;
logger.trace("Current: {} {} {} {}", fs.getR(), fs.getG(), fs.getB(), fs.getWhite());
return true;
} catch (IOException e) {
logger.warn("IOException", e);
return false;
}
});
ledfaderThread.init();
final int period = fadeDurationInMs / totalFadingSteps;
final Future<?> future = faderExecutor.scheduleAtFixedRate(ledfaderThread, 0, period < 1 ? 1 : period,
TimeUnit.MILLISECONDS);
// Wait untill LED Thread has finished, when so shutdown fader
waiterExecutor.schedule(() -> {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
// Ignored
} catch (Exception e) {
logger.warn("Exception", e);
}
ledfaderThread.shutdown();
ledfaderThread = null;
ledUpdateSyncSemaphore.release(1);
}, 0, TimeUnit.MILLISECONDS);
} else {
ledfaderThread.setToState(newTargetState);
}
}
private void sendLEDData(InternalLedState ledState, DataOutputStream out) throws IOException {
logger.debug("Setting LED State to {}", ledState);
if (!ledState.equals(currentState)) {
// "normal" program: set color etc.
byte r = (byte) (ledState.getR() & 0xFF);
byte g = (byte) (ledState.getG() & 0xFF);
byte b = (byte) (ledState.getB() & 0xFF);
byte w = (byte) (ledState.getW() & 0xFF);
byte w2 = (byte) (ledState.getW2() & 0xFF);
logger.debug("RGBW: {}, {}, {}, {}, {}", r, g, b, w, w2);
byte[] bytes = getBytesForColor(r, g, b, w, w2);
sendRaw(bytes, out);
}
currentState = ledState;
}
}

View File

@@ -0,0 +1,216 @@
/**
* Copyright (c) 2010-2020 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.wifiled.internal.handler;
import static java.lang.Math.max;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.PercentType;
/**
* Internal LED state.
*
* @author Stefan Endrullis - Initial contribution
*/
public class InternalLedState {
/** Values for the colors red, green, blue from 0 to 1. */
double r, g, b;
/** White values from 0 to 1. */
double w, w2;
public InternalLedState() {
this(0, 0, 0, 0, 0);
}
public InternalLedState(double r, double g, double b, double w, double w2) {
this.r = r;
this.g = g;
this.b = b;
this.w = w;
this.w2 = w2;
}
public static InternalLedState fromRGBW(int r, int g, int b, int w, int w2) {
return new InternalLedState(conv(r), conv(g), conv(b), conv(w), conv(w2));
}
public InternalLedState withColor(HSBType color) {
//@formatter:off
return new InternalLedState(
color.getRed().doubleValue() / 100,
color.getGreen().doubleValue() / 100,
color.getBlue().doubleValue() / 100,
w,
w2
);
//@formatter:on
}
public InternalLedState withBrightness(double brightness) {
return new InternalLedState(r * brightness, g * brightness, b * brightness, w, w2);
}
public InternalLedState withWhite(double w) {
return new InternalLedState(r, g, b, w, w2);
}
public InternalLedState withWhite2(double w2) {
return new InternalLedState(r, g, b, w, w2);
}
public PercentType toHSBType() {
return HSBType.fromRGB(conv(r), conv(g), conv(b));
}
/**
* Fades from this color to the that color according to the given progress value from 0 (this color)
* to 1 (that color).
*
* @param that that color
* @param progress value between 0 (this color) and 1 (that color)
* @return faded color
*/
public InternalLedState fade(InternalLedState that, double progress) {
double invProgress = 1 - progress;
//@formatter:off
return new InternalLedState(
this.r * invProgress + that.r * progress,
this.g * invProgress + that.g * progress,
this.b * invProgress + that.b * progress,
this.w * invProgress + that.w * progress,
this.w2 * invProgress + that.w2 * progress
);
//@formatter:on
}
/**
* Returns the brightness or the RGB color.
*
* @return value between 0 and 1
*/
public double getBrightness() {
return max(r, max(g, b));
}
/**
* Returns the white value.
*
* @return value between 0 and 1
*/
public double getWhite() {
return w;
}
/**
* Returns the white2 value.
*
* @return value between 0 and 1
*/
public double getWhite2() {
return w2;
}
private static double conv(int v) {
return (double) v / 255;
}
private static int conv(double v) {
return (int) (v * 255 + 0.5);
}
/**
* Returns red value.
*
* @return value between 0 and 255
*/
public int getR() {
return conv(r);
}
/**
* Returns green value.
*
* @return value between 0 and 255
*/
public int getG() {
return conv(g);
}
/**
* Returns blue value.
*
* @return value between 0 and 255
*/
public int getB() {
return conv(b);
}
/**
* Returns white value.
*
* @return value between 0 and 255
*/
public int getW() {
return conv(w);
}
/**
* Returns white2 value.
*
* @return value between 0 and 255
*/
public int getW2() {
return conv(w2);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
InternalLedState that = (InternalLedState) o;
//@formatter:off
return Double.compare(that.r, r) == 0
&& Double.compare(that.g, g) == 0
&& Double.compare(that.b, b) == 0
&& Double.compare(that.w, w) == 0;
//@formatter:on
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(r);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(g);
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(b);
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(w);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public String toString() {
return "InternalLedState{" + "r=" + r + ", g=" + g + ", b=" + b + ", w=" + w + '}';
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2020 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.wifiled.internal.handler;
/**
* @author Stefan Endrullis - Initial contribution
*/
public class LEDState {
public final int state, program, programSpeed;
public final int red, green, blue, white, white2;
public LEDState(int state, int program, int programSpeed, int red, int green, int blue, int white, int white2) {
this.state = state;
this.program = program;
this.programSpeed = programSpeed;
this.red = red;
this.green = green;
this.blue = blue;
this.white = white;
this.white2 = white2;
}
}

View File

@@ -0,0 +1,183 @@
/**
* Copyright (c) 2010-2020 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.wifiled.internal.handler;
import static java.lang.Math.*;
import java.awt.Color;
import java.math.BigDecimal;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
/**
* The {@link LEDStateDTO} class holds the data and the settings for a LED device (i.e. the selected colors, the running
* program, etc.).
*
* @author Osman Basha - Initial contribution
* @author Stefan Endrullis - Initial contribution
* @author Ries van Twisk - Prevent flashes during classic driver color + white updates
*/
public class LEDStateDTO {
private HSBType hsbType;
private OnOffType power;
private PercentType white;
private PercentType white2;
private StringType program;
private PercentType programSpeed;
public LEDStateDTO(OnOffType power, DecimalType hue, PercentType saturation, PercentType brightness,
PercentType white, PercentType white2, StringType program, PercentType programSpeed) {
this.hsbType = new HSBType(hue, saturation, brightness);
this.power = power;
this.white = white;
this.white2 = white2;
this.program = program;
this.programSpeed = programSpeed;
}
public LEDStateDTO(OnOffType power, HSBType hsb, PercentType white, PercentType white2, StringType program,
PercentType programSpeed) {
this.hsbType = hsb;
this.power = power;
this.white = white;
this.white2 = white2;
this.program = program;
this.programSpeed = programSpeed;
}
public PercentType getWhite() {
return white;
}
public PercentType getWhite2() {
return white2;
}
public StringType getProgram() {
return program;
}
public PercentType getProgramSpeed() {
return programSpeed;
}
@Override
public String toString() {
return power + "," + hsbType.getHue() + "," + hsbType.getSaturation() + "," + hsbType.getBrightness() + ","
+ getWhite() + "," + getWhite2() + " [" + getProgram() + "," + getProgramSpeed() + "]";
}
public static LEDStateDTO valueOf(int state, int program, int programSpeed, int red, int green, int blue, int white,
int white2) {
OnOffType power = (state & 0x01) != 0 ? OnOffType.ON : OnOffType.OFF;
float[] hsv = new float[3];
Color.RGBtoHSB(red, green, blue, hsv);
DecimalType h = new DecimalType(new BigDecimal(hsv[0]).multiply(new BigDecimal(360.0)));
PercentType s = new PercentType(new BigDecimal(hsv[1]).multiply(new BigDecimal(100.0)));
PercentType b = new PercentType(new BigDecimal(hsv[2]).multiply(new BigDecimal(100.0)));
HSBType hsbType = new HSBType(h, s, b);
PercentType w = new PercentType(new BigDecimal(white).divide(new BigDecimal(255.0), 3, BigDecimal.ROUND_HALF_UP)
.multiply(new BigDecimal(100.0)));
PercentType w2 = new PercentType(new BigDecimal(white2)
.divide(new BigDecimal(255.0), 3, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100.0)));
// Range: 0x00 .. 0x1F. Speed is inversed
BigDecimal ps = new BigDecimal(programSpeed).divide(new BigDecimal(0x1f), 2, BigDecimal.ROUND_HALF_UP)
.multiply(new BigDecimal(100.0));
PercentType e = new PercentType(new BigDecimal(100.0).subtract(ps));
StringType p = new StringType(Integer.toString(program));
return new LEDStateDTO(power, hsbType, w, w2, p, e);
}
public LEDStateDTO withColor(HSBType color) {
return new LEDStateDTO(power, color, this.getWhite(), this.getWhite2(), this.getProgram(),
this.getProgramSpeed());
}
public LEDStateDTO withBrightness(PercentType brightness) {
return new LEDStateDTO(power, hsbType.getHue(), hsbType.getSaturation(), brightness, this.getWhite(),
this.getWhite2(), this.getProgram(), this.getProgramSpeed());
}
public LEDStateDTO withIncrementedBrightness(int step) {
int brightness = hsbType.getBrightness().intValue();
brightness = max(min(brightness + step, 0), 100);
return withBrightness(new PercentType(brightness));
}
public LEDStateDTO withWhite(PercentType white) {
return new LEDStateDTO(power, hsbType, white, this.getWhite2(), this.getProgram(), this.getProgramSpeed());
}
public LEDStateDTO withIncrementedWhite(int step) {
int white = this.getWhite().intValue();
white = max(min(white + step, 0), 100);
return withWhite(new PercentType(white));
}
public LEDStateDTO withWhite2(PercentType white2) {
return new LEDStateDTO(power, hsbType, this.getWhite(), white2, this.getProgram(), this.getProgramSpeed());
}
public LEDStateDTO withIncrementedWhite2(int step) {
int white = this.getWhite().intValue();
white = max(min(white + step, 0), 100);
return withWhite(new PercentType(white));
}
public LEDStateDTO withProgram(StringType program) {
return new LEDStateDTO(power, hsbType, this.getWhite(), this.getWhite2(), program, this.getProgramSpeed());
}
public LEDStateDTO withoutProgram() {
return withProgram(new StringType(String.valueOf(0x61)));
}
public LEDStateDTO withProgramSpeed(PercentType programSpeed) {
return new LEDStateDTO(power, hsbType, this.getWhite(), this.getWhite2(), this.getProgram(), programSpeed);
}
public LEDStateDTO withIncrementedProgramSpeed(int step) {
int programSpeed = this.getProgramSpeed().intValue();
programSpeed = max(min(programSpeed + step, 0), 100);
return withProgramSpeed(new PercentType(programSpeed));
}
public LEDStateDTO withPower(OnOffType power) {
return new LEDStateDTO(power, hsbType, this.getWhite(), this.getWhite2(), this.getProgram(), getProgramSpeed());
}
public int getRGB() {
return hsbType.getRGB();
}
public HSBType getHSB() {
return hsbType;
}
public OnOffType getPower() {
return power;
}
}

View File

@@ -0,0 +1,234 @@
/**
* Copyright (c) 2010-2020 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.wifiled.internal.handler;
import java.io.IOException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.wifiled.internal.WiFiLEDBindingConstants;
import org.openhab.binding.wifiled.internal.configuration.WiFiLEDConfig;
import org.openhab.binding.wifiled.internal.handler.AbstractWiFiLEDDriver.Driver;
import org.openhab.binding.wifiled.internal.handler.AbstractWiFiLEDDriver.Protocol;
import org.openhab.core.library.types.*;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link WiFiLEDHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Osman Basha - Initial contribution
* @author Ries van Twisk
*/
public class WiFiLEDHandler extends BaseThingHandler {
private static final int INC_DEC_STEP = 10;
private Logger logger = LoggerFactory.getLogger(WiFiLEDHandler.class);
private AbstractWiFiLEDDriver driver;
private ScheduledFuture<?> pollingJob;
public WiFiLEDHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
logger.debug("Initializing WiFiLED handler '{}'", getThing().getUID());
WiFiLEDConfig config = getConfigAs(WiFiLEDConfig.class);
int port = (config.getPort() == null) ? AbstractWiFiLEDDriver.DEFAULT_PORT : config.getPort();
Protocol protocol = config.getProtocol() == null ? Protocol.LD382A : Protocol.valueOf(config.getProtocol());
Driver driverName = config.getDriver() == null ? Driver.CLASSIC : Driver.valueOf(config.getDriver());
switch (driverName) {
case CLASSIC:
driver = new ClassicWiFiLEDDriver(this, config.getIp(), port, protocol);
break;
case FADING:
int fadeDurationInMs = config.getFadeDurationInMs() == null
? FadingWiFiLEDDriver.DEFAULT_FADE_DURATION_IN_MS
: config.getFadeDurationInMs();
int fadeSteps = config.getFadeSteps() == null ? FadingWiFiLEDDriver.DEFAULT_FADE_STEPS
: config.getFadeSteps();
driver = new FadingWiFiLEDDriver(config.getIp(), port, protocol, fadeDurationInMs, fadeSteps);
break;
}
try {
driver.init();
logger.debug("Found a WiFi LED device '{}'", getThing().getUID());
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, e.getMessage());
return;
}
updateStatus(ThingStatus.ONLINE);
int pollingPeriod = (config.getPollingPeriod() == null) ? 30 : config.getPollingPeriod();
if (pollingPeriod > 0) {
pollingJob = scheduler.scheduleWithFixedDelay(() -> update(), 0, pollingPeriod, TimeUnit.SECONDS);
logger.debug("Polling job scheduled to run every {} sec. for '{}'", pollingPeriod, getThing().getUID());
}
}
@Override
public void dispose() {
logger.debug("Disposing WiFiLED handler '{}'", getThing().getUID());
if (pollingJob != null) {
pollingJob.cancel(true);
pollingJob = null;
}
driver.shutdown();
driver = null;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("Handle command '{}' for {}", command, channelUID);
try {
if (command == RefreshType.REFRESH) {
update();
} else if (channelUID.getId().equals(WiFiLEDBindingConstants.CHANNEL_POWER)) {
handleColorCommand(command);
} else if (channelUID.getId().equals(WiFiLEDBindingConstants.CHANNEL_COLOR)) {
handleColorCommand(command);
} else if (channelUID.getId().equals(WiFiLEDBindingConstants.CHANNEL_WHITE)) {
handleWhiteCommand(command);
} else if (channelUID.getId().equals(WiFiLEDBindingConstants.CHANNEL_WHITE2)) {
handleWhite2Command(command);
} else if (channelUID.getId().equals(WiFiLEDBindingConstants.CHANNEL_PROGRAM)
&& (command instanceof StringType)) {
driver.setProgram((StringType) command);
} else if (channelUID.getId().equals(WiFiLEDBindingConstants.CHANNEL_PROGRAM_SPEED)) {
handleProgramSpeedCommand(command);
}
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
}
}
private void handleColorCommand(Command command) throws IOException {
if (command instanceof HSBType) {
driver.setColor((HSBType) command);
} else if (command instanceof PercentType) {
driver.setBrightness((PercentType) command);
} else if (command instanceof OnOffType) {
driver.setPower((OnOffType) command);
} else if (command instanceof IncreaseDecreaseType) {
IncreaseDecreaseType increaseDecreaseType = (IncreaseDecreaseType) command;
if (increaseDecreaseType.equals(IncreaseDecreaseType.INCREASE)) {
driver.incBrightness(INC_DEC_STEP);
} else {
driver.decBrightness(INC_DEC_STEP);
}
}
}
private void handleWhiteCommand(Command command) throws IOException {
if (command instanceof PercentType) {
driver.setWhite((PercentType) command);
} else if (command instanceof OnOffType) {
OnOffType onOffCommand = (OnOffType) command;
if (onOffCommand.equals(OnOffType.ON)) {
driver.setWhite(PercentType.HUNDRED);
} else {
driver.setWhite(PercentType.ZERO);
}
} else if (command instanceof IncreaseDecreaseType) {
IncreaseDecreaseType increaseDecreaseType = (IncreaseDecreaseType) command;
if (increaseDecreaseType.equals(IncreaseDecreaseType.INCREASE)) {
driver.incWhite(INC_DEC_STEP);
} else {
driver.decWhite(INC_DEC_STEP);
}
}
}
private void handleWhite2Command(Command command) throws IOException {
if (command instanceof PercentType) {
driver.setWhite2((PercentType) command);
} else if (command instanceof OnOffType) {
OnOffType onOffCommand = (OnOffType) command;
if (onOffCommand.equals(OnOffType.ON)) {
driver.setWhite2(PercentType.HUNDRED);
} else {
driver.setWhite2(PercentType.ZERO);
}
} else if (command instanceof IncreaseDecreaseType) {
IncreaseDecreaseType increaseDecreaseType = (IncreaseDecreaseType) command;
if (increaseDecreaseType.equals(IncreaseDecreaseType.INCREASE)) {
driver.incWhite2(INC_DEC_STEP);
} else {
driver.decWhite2(INC_DEC_STEP);
}
}
}
private void handleProgramSpeedCommand(Command command) throws IOException {
if (command instanceof PercentType) {
driver.setProgramSpeed((PercentType) command);
} else if (command instanceof OnOffType) {
OnOffType onOffCommand = (OnOffType) command;
if (onOffCommand.equals(OnOffType.ON)) {
driver.setProgramSpeed(PercentType.HUNDRED);
} else {
driver.setProgramSpeed(PercentType.ZERO);
}
} else if (command instanceof IncreaseDecreaseType) {
IncreaseDecreaseType increaseDecreaseType = (IncreaseDecreaseType) command;
if (increaseDecreaseType.equals(IncreaseDecreaseType.INCREASE)) {
driver.incProgramSpeed(INC_DEC_STEP);
} else {
driver.decProgramSpeed(INC_DEC_STEP);
}
}
}
private synchronized void update() {
logger.debug("Updating WiFiLED data '{}'", getThing().getUID());
try {
LEDStateDTO ledState = driver.getLEDStateDTO();
HSBType color = ledState.getHSB();
updateState(WiFiLEDBindingConstants.CHANNEL_POWER, ledState.getPower());
updateState(WiFiLEDBindingConstants.CHANNEL_COLOR, color);
updateState(WiFiLEDBindingConstants.CHANNEL_WHITE, ledState.getWhite());
updateState(WiFiLEDBindingConstants.CHANNEL_WHITE2, ledState.getWhite2());
updateState(WiFiLEDBindingConstants.CHANNEL_PROGRAM, ledState.getProgram());
updateState(WiFiLEDBindingConstants.CHANNEL_PROGRAM_SPEED, ledState.getProgramSpeed());
if (getThing().getStatus().equals(ThingStatus.OFFLINE)) {
updateStatus(ThingStatus.ONLINE);
}
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
}
}
public void reportCommunicationError(IOException e) {
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="wifiled" 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>WiFi LED Binding</name>
<description>Binding for WiFi LED devices. These are known as Magic Home RGBW LED, UFO LED, LED NET controller etc.</description>
<author>Osman Basha, Stefan Endrullis,Ries van Twisk</author>
</binding:binding>

View File

@@ -0,0 +1,27 @@
# binding
binding.wifiled.name = WiFi LED Binding
binding.wifiled.description = Binding für WiFi LED Geräte. Diese werden u.a. als Magic Home RGBW LED, UFO LED, LED NET controller etc.vertrieben.
# thing types
thing-type.wifiled.wifiled.label = WiFi LED
thing-type.wifiled.wifiled.description = WiFi LED Gerät
thing-type.config.wifiled.wifiled.ip.label = IP
thing-type.config.wifiled.wifiled.ip.description = IP-Adresse oder Host-Name des Gerätes
thing-type.config.wifiled.wifiled.port.label = Port
thing-type.config.wifiled.wifiled.port.description = Genutzte Portnummer des Gerätes
thing-type.config.wifiled.wifiled.pollingPeriod.label = Abfrageintervall
thing-type.config.wifiled.wifiled.pollingPeriod.description = Daten-Abfrageintervall in Sek.
thing-type.config.wifiled.wifiled.protocol.label = Protokoll
thing-type.config.wifiled.wifiled.protocol.description = Das zur Kommunikation mit dem Gerät genutzte Protokoll
thing-type.config.wifiled.wifiled.driver.label = Treiber
thing-type.config.wifiled.wifiled.driver.description = Treiber zur Ansteuerung des LED-Controllers
thing-type.config.wifiled.wifiled.fadeDurationInMs.description = Dauer der Überblendung
thing-type.config.wifiled.wifiled.fadeSteps.description = Anzahl von Schritten für Überblendung
# channel types
channel-type.wifiled.power.label = Eingeschaltet
channel-type.wifiled.colorType.label = Farbe
channel-type.wifiled.white.label = Weiß
channel-type.wifiled.white2.label = Weiß 2
channel-type.wifiled.program.label = Programm
channel-type.wifiled.programSpeed.label = Programm-Geschwindigkeit

View File

@@ -0,0 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="wifiled"
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="wifiled">
<label>WiFi LED</label>
<description>WiFi LED Device</description>
<channels>
<channel id="power" typeId="power"/>
<channel id="color" typeId="color"/>
<channel id="white" typeId="white"/>
<channel id="white2" typeId="white2"/>
<channel id="program" typeId="program"/>
<channel id="programSpeed" typeId="programSpeed"/>
</channels>
<config-description>
<parameter name="ip" type="text" required="true">
<label>IP</label>
<description>IP address or host name of the WIFI LED Controller</description>
</parameter>
<parameter name="port" type="integer" required="false" min="1024" max="49151">
<label>Port</label>
<description>Used Port of the device</description>
<default>5577</default>
<advanced>true</advanced>
</parameter>
<parameter name="pollingPeriod" type="integer" required="false">
<label>Polling Period</label>
<description>Polling period for refreshing the data in s</description>
<default>30</default>
<advanced>true</advanced>
</parameter>
<parameter name="protocol" type="text" required="false">
<label>Device Protocol</label>
<description>The protocol used for communication with the device</description>
<default>LD382A</default>
<options>
<option value="LD382A">LD382A</option>
<option value="LD382">LD382</option>
<option value="LD686">LD686</option>
</options>
<advanced>true</advanced>
</parameter>
<parameter name="driver" type="text" required="false">
<label>Device Driver</label>
<description>The driver used to control the device</description>
<default>CLASSIC</default>
<options>
<option value="CLASSIC">CLASSIC</option>
<option value="FADING">FADING</option>
</options>
<advanced>true</advanced>
</parameter>
<parameter name="fadeDurationInMs" type="integer" required="false" min="0" max="10000">
<label>Fading Duration</label>
<description>The duration for the color fading in milliseconds</description>
<default>1000</default>
<advanced>true</advanced>
</parameter>
<parameter name="fadeSteps" type="integer" required="false" min="1" max="256">
<label>Fading Steps</label>
<description>The number of steps used to fade over to the new color</description>
<default>100</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<channel-type id="power">
<item-type>Switch</item-type>
<label>Power</label>
<description>Power state</description>
</channel-type>
<channel-type id="color">
<item-type>Color</item-type>
<label>Color</label>
<category>ColorLight</category>
</channel-type>
<channel-type id="white">
<item-type>Dimmer</item-type>
<label>White</label>
</channel-type>
<channel-type id="white2">
<item-type>Dimmer</item-type>
<label>White 2</label>
</channel-type>
<channel-type id="program" advanced="true">
<item-type>String</item-type>
<label>Program</label>
<state readOnly="false">
<options>
<option value="97">NONE</option>
<option value="37">Seven Colors Cross Fade</option>
<option value="38">Red Gradual Change</option>
<option value="39">Green Gradual Change</option>
<option value="40">Blue Gradual Change</option>
<option value="41">Yellow Gradual Change</option>
<option value="42">Cyan Gradual Change</option>
<option value="43">Purple Gradual Change</option>
<option value="44">White Gradual Change</option>
<option value="45">Red,Green Cross Fade</option>
<option value="46">Red, Blue Cross Fade</option>
<option value="47">Green, Blue Cross Fade</option>
<option value="48">Seven Colors Strobe Flash</option>
<option value="49">Red Strobe Flash</option>
<option value="50">Green Strobe Flash</option>
<option value="51">Blue Strobe Flash</option>
<option value="52">Yellow Strobe Flash</option>
<option value="53">Cyan Strobe Flash</option>
<option value="54">Purple Strobe Flash</option>
<option value="55">White Strobe Flash</option>
<option value="56">Seven Colors Jumping Change</option>
</options>
</state>
</channel-type>
<channel-type id="programSpeed" advanced="true">
<item-type>Dimmer</item-type>
<label>Program Speed</label>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2020 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.wifiled.discovery;
/**
* Test app for discovering devices.
*
* @author Stefan Endrullis &lt;stefan@endrullis.de&gt;
*/
public class WiFiLEDDiscoveryServiceTestApp {
public static void main(String[] args) {
WiFiLEDDiscoveryService discoveryService = new WiFiLEDDiscoveryService();
discoveryService.startScan();
}
}

View File

@@ -0,0 +1,127 @@
/**
* Copyright (c) 2010-2020 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.wifiled.handler;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import java.io.IOException;
/**
* Test app for the fading driver.
*
* @author Stefan Endrullis
*/
public class WiFiLEDHandlerTestApp {
private static AbstractWiFiLEDDriver driver;
public static void main(String[] args) throws IOException, InterruptedException {
String ip = "192.168.178.91";
Integer port = AbstractWiFiLEDDriver.DEFAULT_PORT;
AbstractWiFiLEDDriver.Protocol protocol = AbstractWiFiLEDDriver.Protocol.LD686;
boolean fadingDriver = false;
System.out.println("start");
driver = fadingDriver ?
new FadingWiFiLEDDriver(ip, port, protocol, 0, 1) :
new ClassicWiFiLEDDriver(this, ip, port, protocol);
System.out.println("driver created");
driver.init();
System.out.println("driver initialized");
testStateChanges();
//testFrequentStateChanges();
System.exit(0);
}
private static void testStateChanges() throws IOException, InterruptedException {
driver.setPower(OnOffType.OFF);
System.out.println("off");
Thread.sleep(500);
driver.setPower(OnOffType.ON);
driver.setWhite(PercentType.HUNDRED);
assertState("ON,0,0,0,100");
Thread.sleep(4000);
assertState("ON,0,0,0,100");
driver.setColor(HSBType.BLUE);
assertState("ON,240,100,100,100");
Thread.sleep(4000);
assertState("ON,240,100,100,100");
driver.setWhite(PercentType.ZERO);
assertState("ON,240,100,100,0");
Thread.sleep(4000);
assertState("ON,240,100,100,0");
driver.setColor(HSBType.GREEN);
driver.setWhite(PercentType.ZERO);
System.out.println("g: " + driver.getLEDStateDTO());
Thread.sleep(4000);
System.out.println("g: " + driver.getLEDStateDTO());
driver.setColor(HSBType.RED);
driver.setWhite(PercentType.ZERO);
System.out.println("r: " + driver.getLEDStateDTO());
Thread.sleep(4000);
System.out.println("r: " + driver.getLEDStateDTO());
driver.setColor(HSBType.fromRGB(255, 32, 0));
driver.setWhite(new PercentType(14));
System.out.println("c: " + driver.getLEDStateDTO());
Thread.sleep(4000);
System.out.println("c: " + driver.getLEDStateDTO());
driver.setPower(OnOffType.OFF);
System.out.println("o: " + driver.getLEDStateDTO());
Thread.sleep(4000);
System.out.println("o: " + driver.getLEDStateDTO());
}
private static void testFrequentStateChanges() throws IOException, InterruptedException {
driver.setPower(OnOffType.ON);
driver.setWhite(PercentType.ZERO);
for (int i = 0; i < 100; i++) {
driver.setColor(HSBType.BLUE);
Thread.sleep(100);
driver.setColor(HSBType.RED);
Thread.sleep(100);
}
}
private static void assertState(String state) throws IOException {
if (!driver.getLEDStateDTO().toString().equals(state + " [0,100]")) {
//throw new RuntimeException("Expected: " + state + " [0,100]; actually: " + driver.getLEDStateDTO().toString());
}
}
}

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2020 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.wifiled.internal.handler;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
/**
* Test for LEDStateDTO
*
* @author Ries van Twisk - Prevent flashes during classic driver color + white updates
*/
public class LEDStateDTOTest {
@Test
public void RGBTest() {
for (int r = 0; r < 256 - 3; r = r + 3) {
for (int g = 0; g < 256 - 5; g = g + 5) {
for (int b = 0; b < 256 - 7; b = b + 7) {
LEDStateDTO ledState = LEDStateDTO.valueOf(0, 0, 0, r, g, b, 0, 0);
assertThat(ledState.getRGB() & 0xffffff, is(r << 16 | g << 8 | b));
}
}
}
}
@Test
public void RGBTest1() {
LEDStateDTO ledState = LEDStateDTO.valueOf(0, 0, 0, 0xff, 0xff, 0xff, 0, 0);
assertThat(ledState.getRGB() & 0xffffff, is(0xffffff));
}
@Test
public void programSpeedtest() {
for (int ps = 0; ps < 0x20; ps++) {
LEDStateDTO ledState = LEDStateDTO.valueOf(0, 0, ps, 0, 0, 0, 0, 0);
assertThat(ledState.getProgramSpeed().intValue(), is((int) Math.round(100.0 - (100.0 / 0x1f * ps))));
}
}
@Test
public void whiteTest() {
for (int wt = 0; wt < 256; wt++) {
LEDStateDTO ledState = LEDStateDTO.valueOf(0, 0, 0, 0, 0, 0, wt, wt);
assertThat((int) Math.round(ledState.getWhite().doubleValue() * 2.55), is(wt));
assertThat(255 - (int) Math.round(ledState.getWhite2().doubleValue() * 2.55), is(255 - wt));
}
}
@Test
public void programTest() {
for (int p = 0; p < 256; p++) {
LEDStateDTO ledState = LEDStateDTO.valueOf(0, p, 0, 0, 0, 0, 0, 0);
assertThat(ledState.getProgram().toString(), is(String.valueOf(p)));
}
}
}