[wled] Change to bridge/thing structure and add global controls (#12199)

* Update to using bridge/thing
* remove white channels.
* Improve Discovery
* Add more sleep/ timed light / NL features
* Change advanced channels
* fix bug for sleep duration
* Update readme with new channels
* Move channels into separate readme heading.
* fix white jumps to 0 after moving control.

Signed-off-by: Matthew Skinner <matt@pcmus.com>
This commit is contained in:
Matthew Skinner
2022-05-27 21:26:47 +10:00
committed by GitHub
parent 87023da9e3
commit 7f249872bc
19 changed files with 884 additions and 377 deletions

View File

@@ -14,6 +14,7 @@ package org.openhab.binding.wled.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.wled.internal.handlers.WLedBridgeHandler;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.RuleAction;
import org.openhab.core.thing.binding.ThingActions;
@@ -32,11 +33,11 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault
public class WLedActions implements ThingActions {
public final Logger logger = LoggerFactory.getLogger(getClass());
private @Nullable WLedHandler handler;
private @Nullable WLedBridgeHandler handler;
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
this.handler = (WLedHandler) handler;
this.handler = (WLedBridgeHandler) handler;
}
@Override
@@ -62,7 +63,7 @@ public class WLedActions implements ThingActions {
public void savePreset(
@ActionInput(name = "presetNumber", label = "Preset Slot", description = "Number for the preset slot you wish to use") int presetNumber,
@ActionInput(name = "presetName", label = "Preset Name", description = "Name for the preset that you wish to use") String presetName) {
WLedHandler localHandler = handler;
WLedBridgeHandler localHandler = handler;
if (localHandler != null) {
localHandler.savePreset(presetNumber, presetName);
}

View File

@@ -28,12 +28,14 @@ import org.openhab.core.thing.ThingTypeUID;
public class WLedBindingConstants {
public static final String BINDING_ID = "wled";
public static final String BRIDGE_TYPE_ID = "json";
public static final BigDecimal BIG_DECIMAL_2_55 = new BigDecimal(2.55);
public static final BigDecimal BIG_DECIMAL_182_04 = new BigDecimal(182.04);
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_WLED = new ThingTypeUID(BINDING_ID, "wled");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_WLED);
public static final ThingTypeUID THING_TYPE_SEGMENT = new ThingTypeUID(BINDING_ID, "segment");
public static final ThingTypeUID THING_TYPE_JSON = new ThingTypeUID(BINDING_ID, BRIDGE_TYPE_ID);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_SEGMENT, THING_TYPE_JSON);
// Configs
public static final String CONFIG_ADDRESS = "address";
@@ -42,6 +44,7 @@ public class WLedBindingConstants {
public static final String CONFIG_SAT_THRESHOLD = "saturationThreshold";
// Channels
public static final String CHANNEL_GLOBAL_BRIGHTNESS = "globalBrightness";
public static final String CHANNEL_MASTER_CONTROLS = "masterControls";
public static final String CHANNEL_SEGMENT_BRIGHTNESS = "segmentBrightness";
public static final String CHANNEL_PRIMARY_COLOR = "primaryColor";
@@ -65,6 +68,9 @@ public class WLedBindingConstants {
public static final String CHANNEL_SPACING = "spacing";
public static final String CHANNEL_LIVE_OVERRIDE = "liveOverride";
public static final String CHANNEL_SLEEP = "sleep";
public static final String CHANNEL_SLEEP_MODE = "sleepMode";
public static final String CHANNEL_SLEEP_DURATION = "sleepDuration";
public static final String CHANNEL_SLEEP_BRIGHTNESS = "sleepTargetBrightness";
public static final String CHANNEL_SYNC_SEND = "syncSend";
public static final String CHANNEL_SYNC_RECEIVE = "syncReceive";
}

View File

@@ -22,9 +22,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
@NonNullByDefault
public class WLedConfiguration {
public String address = "";
public int pollTime;
public int segmentIndex;
public int pollTime = 5;
public int saturationThreshold;
public boolean sortEffects = false;
public boolean sortPalettes = false;
public boolean sortEffects = true;
public boolean sortPalettes = true;
}

View File

@@ -14,7 +14,6 @@ package org.openhab.binding.wled.internal;
import static org.openhab.binding.wled.internal.WLedBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -92,22 +91,20 @@ public class WLedDiscoveryService implements MDNSDiscoveryParticipant {
return null;
}
String response = sendGetRequest(address[0], "/json");
// LinkedList<String> segmentIndexList = WLedHelper.listOfResults(response, "{\"id\":", ",");
// How to create multiple things from the returned list of segments?
String label = WLedHelper.getValue(response, "\"name\":\"", "\"");
if (label.isEmpty()) {
label = "WLED @ " + address[0];
}
String macAddress = WLedHelper.getValue(response, "\"mac\":\"", "\"");
String firmware = WLedHelper.getValue(response, "\"ver\":\"", "\"");
ThingTypeUID thingtypeuid = new ThingTypeUID("wled", "wled");
ThingUID thingUID = new ThingUID(thingtypeuid, macAddress);
Map<String, Object> properties = new HashMap<>();
properties.put(Thing.PROPERTY_MAC_ADDRESS, macAddress);
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmware);
return DiscoveryResultBuilder.create(thingUID).withProperty(CONFIG_ADDRESS, address[0])
.withProperty(CONFIG_SEGMENT_INDEX, 0).withLabel(label).withProperties(properties)
.withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
if (!macAddress.isBlank()) {
String firmware = WLedHelper.getValue(response, "\"ver\":\"", "\"");
ThingUID thingUID = new ThingUID(THING_TYPE_JSON, macAddress);
Map<String, Object> properties = Map.of(Thing.PROPERTY_MAC_ADDRESS, macAddress,
Thing.PROPERTY_FIRMWARE_VERSION, firmware, CONFIG_ADDRESS, address[0]);
return DiscoveryResultBuilder.create(thingUID).withLabel(label).withProperties(properties)
.withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
}
return null;
}
@Override

View File

@@ -12,11 +12,14 @@
*/
package org.openhab.binding.wled.internal;
import static org.openhab.binding.wled.internal.WLedBindingConstants.SUPPORTED_THING_TYPES;
import static org.openhab.binding.wled.internal.WLedBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.wled.internal.api.WledApiFactory;
import org.openhab.binding.wled.internal.handlers.WLedBridgeHandler;
import org.openhab.binding.wled.internal.handlers.WLedSegmentHandler;
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;
@@ -53,8 +56,10 @@ public class WLedHandlerFactory extends BaseThingHandlerFactory {
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
return new WLedHandler(thing, apiFactory, stateDescriptionProvider);
if (THING_TYPE_SEGMENT.equals(thingTypeUID)) {
return new WLedSegmentHandler(thing);
} else if (THING_TYPE_JSON.equals(thingTypeUID)) {
return new WLedBridgeHandler((Bridge) thing, apiFactory, stateDescriptionProvider);
}
return null;
}

View File

@@ -12,7 +12,10 @@
*/
package org.openhab.binding.wled.internal;
import static org.openhab.binding.wled.internal.WLedBindingConstants.BIG_DECIMAL_2_55;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
@@ -44,7 +47,7 @@ public class WLedHelper {
// example message rgb in array brackets [255.0, 255.0, 255.0, 255.0]
List<String> colors = Arrays.asList(message.replaceAll("\\[|\\]", "").split("\\s*,\\s*"));
try {
return new PercentType(new BigDecimal(colors.get(2)));
return new PercentType(new BigDecimal(colors.get(3)).divide(BIG_DECIMAL_2_55, RoundingMode.HALF_UP));
} catch (IllegalArgumentException e) {
return new PercentType();
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2022 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.wled.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link WLedSegmentConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Matthew Skinner - Initial contribution
*/
@NonNullByDefault
public class WLedSegmentConfiguration {
public int segmentIndex;
}

View File

@@ -0,0 +1,101 @@
/**
* Copyright (c) 2010-2022 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.wled.internal;
import static org.openhab.binding.wled.internal.WLedBindingConstants.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.wled.internal.api.WledApi;
import org.openhab.binding.wled.internal.handlers.WLedBridgeHandler;
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.Thing;
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;
/**
* The {@link WLedSegmentDiscoveryService} Discovers and adds any Wled segments found by the bridge device.
*
* @author Matthew Skinner - Initial contribution
*/
@NonNullByDefault
public class WLedSegmentDiscoveryService extends AbstractDiscoveryService
implements DiscoveryService, ThingHandlerService {
private @Nullable WLedBridgeHandler bridgeHandler;
private @Nullable ThingUID bridgeUID;
private static final int SEARCH_TIME = 10;
public WLedSegmentDiscoveryService() {
super(SUPPORTED_THING_TYPES, SEARCH_TIME);
}
public WLedSegmentDiscoveryService(Set<ThingTypeUID> supportedThingTypes, int timeout)
throws IllegalArgumentException {
super(supportedThingTypes, timeout);
}
private void buildThing(int segmentIndex, String segmentName) {
ThingUID localBridgeUID = bridgeUID;
if (localBridgeUID == null) {
return;
}
String newThingUID = localBridgeUID.getId() + "-" + segmentIndex;
ThingUID thingUID = new ThingUID(THING_TYPE_SEGMENT, localBridgeUID, newThingUID);
Map<String, Object> properties = Map.of(Thing.PROPERTY_SERIAL_NUMBER, newThingUID, CONFIG_SEGMENT_INDEX,
segmentIndex);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withLabel(segmentName)
.withProperties(properties).withBridge(bridgeUID)
.withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).build();
thingDiscovered(discoveryResult);
}
@Override
protected void startScan() {
WLedBridgeHandler localBridgeHandler = bridgeHandler;
if (localBridgeHandler != null) {
WledApi localAPI = localBridgeHandler.api;
if (localAPI != null) {
List<String> names = localAPI.getSegmentNames();
for (int count = 0; count < names.size(); count++) {
buildThing(count, names.get(count));
}
}
}
}
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof WLedBridgeHandler) {
bridgeHandler = (WLedBridgeHandler) handler;
bridgeUID = bridgeHandler.getThing().getUID();
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler;
}
@Override
public void deactivate() {
}
}

View File

@@ -91,6 +91,7 @@ public class WledState {
public boolean sel = true;
public boolean rev = false;
public boolean mi = false;
public String n = "Segment X";
}
public class NightLightState {

View File

@@ -13,10 +13,12 @@
package org.openhab.binding.wled.internal.api;
import java.math.BigDecimal;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.types.StateOption;
/**
* The {@link WledApi} is the JSON API methods that can be extended for different firmware versions.
@@ -70,6 +72,12 @@ public interface WledApi {
public abstract void setSleep(boolean bool) throws ApiException;
public abstract void setSleepMode(String value) throws ApiException;
public abstract void setSleepDuration(BigDecimal time) throws ApiException;
public abstract void setSleepTargetBrightness(PercentType percent) throws ApiException;
public abstract void setUdpSend(boolean bool) throws ApiException;
public abstract void setUdpRecieve(boolean bool) throws ApiException;
@@ -102,4 +110,10 @@ public interface WledApi {
*
*/
public abstract void savePreset(int position, String presetName) throws ApiException;
public abstract List<StateOption> getUpdatedFxList();
public abstract List<StateOption> getUpdatedPaletteList();
public abstract List<String> getSegmentNames();
}

View File

@@ -14,7 +14,7 @@ package org.openhab.binding.wled.internal.api;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.wled.internal.WLedHandler;
import org.openhab.binding.wled.internal.handlers.WLedBridgeHandler;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
@@ -39,7 +39,7 @@ public class WledApiFactory {
this.httpClient = httpClientFactory.getCommonHttpClient();
}
public WledApi getApi(WLedHandler handler) throws ApiException {
public WledApi getApi(WLedBridgeHandler handler) throws ApiException {
WledApi lowestSupportedApi = new WledApiV084(handler, httpClient);
int version = lowestSupportedApi.getFirmwareVersion();
logger.debug("Treating firmware as int:{}", version);

View File

@@ -21,8 +21,8 @@ import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.wled.internal.WLedHandler;
import org.openhab.binding.wled.internal.WledState.PresetState;
import org.openhab.binding.wled.internal.handlers.WLedBridgeHandler;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.StateOption;
@@ -31,7 +31,7 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
/**
* The {@link WledApiV0130} is the json Api methods for firmware version 0.11.0 and newer
* The {@link WledApiV0110} is the json Api methods for firmware version 0.11.0 and newer
* as newer firmwares come out with breaking changes, extend this class into a newer firmware version class.
*
* @author Matthew Skinner - Initial contribution
@@ -39,7 +39,7 @@ import com.google.gson.JsonSyntaxException;
@NonNullByDefault
public class WledApiV0110 extends WledApiV084 {
public WledApiV0110(WLedHandler handler, HttpClient httpClient) {
public WledApiV0110(WLedBridgeHandler handler, HttpClient httpClient) {
super(handler, httpClient);
}
@@ -89,4 +89,9 @@ public class WledApiV0110 extends WledApiV084 {
}
postState("{\"psave\":" + position + ",\"n\":\"" + name + "\",\"ib\":true,\"sb\":true}");
}
@Override
public void setSleepMode(String value) throws ApiException {
postState("{\"nl\":{\"mode\":" + value + "}}");
}
}

View File

@@ -15,10 +15,12 @@ package org.openhab.binding.wled.internal.api;
import static org.openhab.binding.wled.internal.WLedBindingConstants.*;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.wled.internal.WLedHandler;
import org.openhab.binding.wled.internal.WledState.SegmentState;
import org.openhab.binding.wled.internal.handlers.WLedBridgeHandler;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Channel;
@@ -31,7 +33,7 @@ import org.openhab.core.thing.Channel;
@NonNullByDefault
public class WledApiV0130 extends WledApiV0110 {
public WledApiV0130(WLedHandler handler, HttpClient httpClient) {
public WledApiV0130(WLedBridgeHandler handler, HttpClient httpClient) {
super(handler, httpClient);
}
@@ -48,12 +50,24 @@ public class WledApiV0130 extends WledApiV0110 {
if (channel != null) {
removeChannels.add(channel);
}
handler.removeChannels(removeChannels);
if (!removeChannels.isEmpty()) {
handler.removeBridgeChannels(removeChannels);
}
}
@Override
protected void processState() throws ApiException {
super.processState();
protected void processState(int segmentIndex) throws ApiException {
super.processState(segmentIndex);
handler.update(CHANNEL_PLAYLISTS, new StringType(Integer.toString(state.stateResponse.pl)));
}
@Override
public List<String> getSegmentNames() {
// segment names was only first added in 0.13.0 firmware
List<String> segmentNames = new ArrayList<>(state.stateResponse.seg.length);
for (SegmentState state : state.stateResponse.seg) {
segmentNames.add(state.n);
}
return segmentNames;
}
}

View File

@@ -31,13 +31,13 @@ import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.openhab.binding.wled.internal.WLedHandler;
import org.openhab.binding.wled.internal.WLedHelper;
import org.openhab.binding.wled.internal.WledState;
import org.openhab.binding.wled.internal.WledState.InfoResponse;
import org.openhab.binding.wled.internal.WledState.JsonResponse;
import org.openhab.binding.wled.internal.WledState.LedInfo;
import org.openhab.binding.wled.internal.WledState.StateResponse;
import org.openhab.binding.wled.internal.handlers.WLedBridgeHandler;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
@@ -45,8 +45,6 @@ import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.StateOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -65,12 +63,12 @@ public class WledApiV084 implements WledApi {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
protected final Gson gson = new Gson();
protected final HttpClient httpClient;
protected final WLedHandler handler;
protected final WLedBridgeHandler handler;
protected final String address;
protected WledState state = new WledState();
private int version = 0;
public WledApiV084(WLedHandler handler, HttpClient httpClient) {
public WledApiV084(WLedBridgeHandler handler, HttpClient httpClient) {
this.handler = handler;
this.address = handler.config.address;
this.httpClient = httpClient;
@@ -79,33 +77,13 @@ public class WledApiV084 implements WledApi {
@Override
public void initialize() throws ApiException {
state.jsonResponse = getJson();
getUpdatedFxList();
getUpdatedPaletteList();
state.infoResponse = getInfo();
@Nullable
LedInfo localLedInfo = gson.fromJson(state.infoResponse.leds.toString(), LedInfo.class);
if (localLedInfo != null) {
state.ledInfo = localLedInfo;
}
handler.hasWhite = state.ledInfo.rgbw;
ArrayList<Channel> removeChannels = new ArrayList<>();
if (!state.ledInfo.rgbw) {
logger.debug("WLED is not setup to use RGBW, so removing un-needed white channels");
Channel channel = handler.getThing().getChannel(CHANNEL_PRIMARY_WHITE);
if (channel != null) {
removeChannels.add(channel);
}
channel = handler.getThing().getChannel(CHANNEL_SECONDARY_WHITE);
if (channel != null) {
removeChannels.add(channel);
}
channel = handler.getThing().getChannel(CHANNEL_THIRD_WHITE);
if (channel != null) {
removeChannels.add(channel);
}
}
handler.removeChannels(removeChannels);
}
@Override
@@ -172,7 +150,10 @@ public class WledApiV084 implements WledApi {
}
state.stateResponse = response;
state.unpackJsonObjects();
processState();
processBridgeStates();
for (int count = 0; count < state.stateResponse.seg.length; count++) {
processState(count);
}
} catch (JsonSyntaxException | ApiException e) {
logger.debug("Reply back when a command was sent triggered an exception:{}", jsonState);
}
@@ -199,6 +180,7 @@ public class WledApiV084 implements WledApi {
if (response == null) {
throw new ApiException("Could not GET:/json/info");
}
logger.trace("/json/info:{}", returnContent);
return response;
} catch (JsonSyntaxException e) {
throw new ApiException("JsonSyntaxException:{}", e);
@@ -222,10 +204,14 @@ public class WledApiV084 implements WledApi {
public void update() throws ApiException {
state.stateResponse = getState();
state.unpackJsonObjects();
processState();
processBridgeStates();
for (int count = 0; count < state.stateResponse.seg.length; count++) {
processState(count);
}
}
protected void getUpdatedFxList() {
@Override
public List<StateOption> getUpdatedFxList() {
List<StateOption> fxOptions = new ArrayList<>();
int counter = 0;
for (String value : state.jsonResponse.effects) {
@@ -234,11 +220,11 @@ public class WledApiV084 implements WledApi {
if (handler.config.sortEffects) {
fxOptions.sort(Comparator.comparing(o -> o.getValue().equals("0") ? "" : o.getLabel()));
}
handler.stateDescriptionProvider.setStateOptions(new ChannelUID(handler.getThing().getUID(), CHANNEL_FX),
fxOptions);
return fxOptions;
}
protected void getUpdatedPaletteList() {
@Override
public List<StateOption> getUpdatedPaletteList() {
List<StateOption> palleteOptions = new ArrayList<>();
int counter = 0;
for (String value : state.jsonResponse.palettes) {
@@ -247,8 +233,7 @@ public class WledApiV084 implements WledApi {
if (handler.config.sortPalettes) {
palleteOptions.sort(Comparator.comparing(o -> o.getValue().equals("0") ? "" : o.getLabel()));
}
handler.stateDescriptionProvider.setStateOptions(new ChannelUID(handler.getThing().getUID(), CHANNEL_PALETTES),
palleteOptions);
return palleteOptions;
}
@Override
@@ -264,46 +249,20 @@ public class WledApiV084 implements WledApi {
return version;
}
protected void processState() throws ApiException {
if (state.stateResponse.seg.length <= handler.config.segmentIndex) {
throw new ApiException("Segment " + handler.config.segmentIndex
+ " is not currently setup correctly in the WLED firmware");
}
HSBType tempHSB = WLedHelper
.parseToHSBType(state.stateResponse.seg[handler.config.segmentIndex].col[0].toString());
handler.update(CHANNEL_PRIMARY_COLOR, tempHSB);
handler.update(CHANNEL_SECONDARY_COLOR,
WLedHelper.parseToHSBType(state.stateResponse.seg[handler.config.segmentIndex].col[1].toString()));
handler.update(CHANNEL_THIRD_COLOR,
WLedHelper.parseToHSBType(state.stateResponse.seg[handler.config.segmentIndex].col[2].toString()));
if (state.ledInfo.rgbw) {
handler.update(CHANNEL_PRIMARY_WHITE, WLedHelper
.parseWhitePercent(state.stateResponse.seg[handler.config.segmentIndex].col[0].toString()));
handler.update(CHANNEL_SECONDARY_WHITE, WLedHelper
.parseWhitePercent(state.stateResponse.seg[handler.config.segmentIndex].col[1].toString()));
handler.update(CHANNEL_THIRD_WHITE, WLedHelper
.parseWhitePercent(state.stateResponse.seg[handler.config.segmentIndex].col[2].toString()));
}
// Global OFF or Segment OFF needs to be treated as OFF
if (!state.stateResponse.seg[handler.config.segmentIndex].on || !state.stateResponse.on) {
handler.update(CHANNEL_MASTER_CONTROLS, OnOffType.OFF);
handler.update(CHANNEL_SEGMENT_BRIGHTNESS, OnOffType.OFF);
protected void processBridgeStates() throws ApiException {
if (!state.stateResponse.on) {
handler.update(CHANNEL_GLOBAL_BRIGHTNESS, OnOffType.OFF);
} else {
handler.update(CHANNEL_MASTER_CONTROLS, tempHSB);
handler.update(CHANNEL_SEGMENT_BRIGHTNESS,
new PercentType(new BigDecimal(state.stateResponse.seg[handler.config.segmentIndex].bri)
.divide(BIG_DECIMAL_2_55, RoundingMode.HALF_UP)));
handler.update(CHANNEL_GLOBAL_BRIGHTNESS, new PercentType(
new BigDecimal(state.stateResponse.bri).divide(BIG_DECIMAL_2_55, RoundingMode.HALF_UP)));
}
handler.update(CHANNEL_LIVE_OVERRIDE, new StringType(Integer.toString(state.stateResponse.lor)));
handler.update(CHANNEL_PRESETS, new StringType(Integer.toString(state.stateResponse.ps)));
if (state.nightLightState.on) {
handler.update(CHANNEL_SLEEP, OnOffType.ON);
} else {
handler.update(CHANNEL_SLEEP, OnOffType.OFF);
}
if (state.stateResponse.pl == 0) {
handler.update(CHANNEL_PRESET_CYCLE, OnOffType.ON);
} else {
handler.update(CHANNEL_PRESET_CYCLE, OnOffType.OFF);
}
if (state.udpnState.recv) {
handler.update(CHANNEL_SYNC_RECEIVE, OnOffType.ON);
} else {
@@ -314,32 +273,75 @@ public class WledApiV084 implements WledApi {
} else {
handler.update(CHANNEL_SYNC_SEND, OnOffType.OFF);
}
if (state.stateResponse.seg[handler.config.segmentIndex].mi) {
handler.update(CHANNEL_MIRROR, OnOffType.ON);
if (state.stateResponse.pl == 0) {
handler.update(CHANNEL_PRESET_CYCLE, OnOffType.ON);
} else {
handler.update(CHANNEL_MIRROR, OnOffType.OFF);
}
if (state.stateResponse.seg[handler.config.segmentIndex].rev) {
handler.update(CHANNEL_REVERSE, OnOffType.ON);
} else {
handler.update(CHANNEL_REVERSE, OnOffType.OFF);
handler.update(CHANNEL_PRESET_CYCLE, OnOffType.OFF);
}
handler.update(CHANNEL_TRANS_TIME, new QuantityType<>(
new BigDecimal(state.stateResponse.transition).divide(BigDecimal.TEN), Units.SECOND));
handler.update(CHANNEL_PRESETS, new StringType(Integer.toString(state.stateResponse.ps)));
handler.update(CHANNEL_FX,
new StringType(Integer.toString(state.stateResponse.seg[handler.config.segmentIndex].fx)));
handler.update(CHANNEL_PALETTES,
new StringType(Integer.toString(state.stateResponse.seg[handler.config.segmentIndex].pal)));
handler.update(CHANNEL_SPEED,
new PercentType(new BigDecimal(state.stateResponse.seg[handler.config.segmentIndex].sx)
.divide(BIG_DECIMAL_2_55, RoundingMode.HALF_UP)));
handler.update(CHANNEL_INTENSITY,
new PercentType(new BigDecimal(state.stateResponse.seg[handler.config.segmentIndex].ix)
.divide(BIG_DECIMAL_2_55, RoundingMode.HALF_UP)));
handler.update(CHANNEL_LIVE_OVERRIDE, new StringType(Integer.toString(state.stateResponse.lor)));
handler.update(CHANNEL_GROUPING, new DecimalType(state.stateResponse.seg[handler.config.segmentIndex].grp));
handler.update(CHANNEL_SPACING, new DecimalType(state.stateResponse.seg[handler.config.segmentIndex].spc));
handler.update(CHANNEL_SLEEP_DURATION,
new QuantityType<>(new BigDecimal(state.nightLightState.dur), Units.MINUTE));
handler.update(CHANNEL_SLEEP_BRIGHTNESS, new PercentType(
new BigDecimal(state.nightLightState.tbri).divide(BIG_DECIMAL_2_55, RoundingMode.HALF_UP)));
handler.update(CHANNEL_SLEEP_MODE, new StringType(Integer.toString(state.nightLightState.mode)));
}
protected void processState(int segmentIndex) throws ApiException {
if (state.stateResponse.seg.length <= segmentIndex) {
throw new ApiException(
"Segment " + segmentIndex + " is not currently setup correctly in the WLED firmware");
}
if (handler.handlerMissing(segmentIndex)) {
// There is no thing setup for this segmentIndex.
return;
}
HSBType tempHSB = WLedHelper.parseToHSBType(state.stateResponse.seg[segmentIndex].col[0].toString());
handler.update(segmentIndex, CHANNEL_PRIMARY_COLOR, tempHSB);
handler.update(segmentIndex, CHANNEL_SECONDARY_COLOR,
WLedHelper.parseToHSBType(state.stateResponse.seg[segmentIndex].col[1].toString()));
handler.update(segmentIndex, CHANNEL_THIRD_COLOR,
WLedHelper.parseToHSBType(state.stateResponse.seg[segmentIndex].col[2].toString()));
if (state.ledInfo.rgbw) {
handler.update(segmentIndex, CHANNEL_PRIMARY_WHITE,
WLedHelper.parseWhitePercent(state.stateResponse.seg[segmentIndex].col[0].toString()));
handler.update(segmentIndex, CHANNEL_SECONDARY_WHITE,
WLedHelper.parseWhitePercent(state.stateResponse.seg[segmentIndex].col[1].toString()));
handler.update(segmentIndex, CHANNEL_THIRD_WHITE,
WLedHelper.parseWhitePercent(state.stateResponse.seg[segmentIndex].col[2].toString()));
}
// Global OFF or Segment OFF needs to be treated as OFF
if (!state.stateResponse.seg[segmentIndex].on || !state.stateResponse.on) {
handler.update(segmentIndex, CHANNEL_MASTER_CONTROLS, OnOffType.OFF);
handler.update(segmentIndex, CHANNEL_SEGMENT_BRIGHTNESS, OnOffType.OFF);
} else {
handler.update(segmentIndex, CHANNEL_MASTER_CONTROLS, tempHSB);
handler.update(segmentIndex, CHANNEL_SEGMENT_BRIGHTNESS,
new PercentType(new BigDecimal(state.stateResponse.seg[segmentIndex].bri).divide(BIG_DECIMAL_2_55,
RoundingMode.HALF_UP)));
}
if (state.stateResponse.seg[segmentIndex].mi) {
handler.update(segmentIndex, CHANNEL_MIRROR, OnOffType.ON);
} else {
handler.update(segmentIndex, CHANNEL_MIRROR, OnOffType.OFF);
}
if (state.stateResponse.seg[segmentIndex].rev) {
handler.update(segmentIndex, CHANNEL_REVERSE, OnOffType.ON);
} else {
handler.update(segmentIndex, CHANNEL_REVERSE, OnOffType.OFF);
}
handler.update(segmentIndex, CHANNEL_FX,
new StringType(Integer.toString(state.stateResponse.seg[segmentIndex].fx)));
handler.update(segmentIndex, CHANNEL_PALETTES,
new StringType(Integer.toString(state.stateResponse.seg[segmentIndex].pal)));
handler.update(segmentIndex, CHANNEL_SPEED,
new PercentType(new BigDecimal(state.stateResponse.seg[segmentIndex].sx).divide(BIG_DECIMAL_2_55,
RoundingMode.HALF_UP)));
handler.update(segmentIndex, CHANNEL_INTENSITY,
new PercentType(new BigDecimal(state.stateResponse.seg[segmentIndex].ix).divide(BIG_DECIMAL_2_55,
RoundingMode.HALF_UP)));
handler.update(segmentIndex, CHANNEL_GROUPING, new DecimalType(state.stateResponse.seg[segmentIndex].grp));
handler.update(segmentIndex, CHANNEL_SPACING, new DecimalType(state.stateResponse.seg[segmentIndex].spc));
}
@Override
@@ -512,4 +514,28 @@ public class WledApiV084 implements WledApi {
public void setSpacing(int value, int segmentIndex) throws ApiException {
postState("{\"seg\":[{\"id\":" + segmentIndex + ",\"spc\":" + value + "}]}");
}
@Override
public List<String> getSegmentNames() {
List<String> segmentNames = new ArrayList<>(state.stateResponse.seg.length);
for (int count = 0; count < state.stateResponse.seg.length; count++) {
segmentNames.add("Segment " + count);
}
return segmentNames;
}
@Override
public void setSleepMode(String value) throws ApiException {
// Binding requires firmware 0.11.0 and newer
}
@Override
public void setSleepDuration(BigDecimal time) throws ApiException {
postState("{\"nl\":{\"dur\":" + time + "}}");
}
@Override
public void setSleepTargetBrightness(PercentType percent) throws ApiException {
postState("{\"nl\":{\"tbri\":" + percent.toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "}}");
}
}

View File

@@ -0,0 +1,270 @@
/**
* Copyright (c) 2010-2022 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.wled.internal.handlers;
import static org.openhab.binding.wled.internal.WLedBindingConstants.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
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.wled.internal.WLedActions;
import org.openhab.binding.wled.internal.WLedConfiguration;
import org.openhab.binding.wled.internal.WLedSegmentDiscoveryService;
import org.openhab.binding.wled.internal.WledDynamicStateDescriptionProvider;
import org.openhab.binding.wled.internal.api.ApiException;
import org.openhab.binding.wled.internal.api.WledApi;
import org.openhab.binding.wled.internal.api.WledApiFactory;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
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.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link WLedBridgeHandler} is responsible for talking and parsing data to/from the WLED device.
*
* @author Matthew Skinner - Initial contribution
*/
@NonNullByDefault
public class WLedBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(getClass());
public final WledDynamicStateDescriptionProvider stateDescriptionProvider;
private Map<Integer, WLedSegmentHandler> segmentHandlers = new HashMap<Integer, WLedSegmentHandler>();
private WledApiFactory apiFactory;
public boolean hasWhite = false;
public @Nullable WledApi api;
private @Nullable ScheduledFuture<?> pollingFuture = null;
public WLedConfiguration config = new WLedConfiguration();
public WLedBridgeHandler(Bridge bridge, WledApiFactory apiFactory,
WledDynamicStateDescriptionProvider stateDescriptionProvider) {
super(bridge);
this.apiFactory = apiFactory;
this.stateDescriptionProvider = stateDescriptionProvider;
}
/**
* If no thing is setup for specified segmentIndex this will return FALSE.
*/
public boolean handlerMissing(int segmentIndex) {
return (segmentHandlers.get(segmentIndex) == null);
}
public void savePreset(int position, String presetName) {
WledApi localAPI = api;
try {
if (localAPI != null) {
localAPI.savePreset(position, presetName);
}
} catch (ApiException e) {
logger.debug("Error occured when trying to save a preset:{}", e.getMessage());
}
}
public void removeBridgeChannels(ArrayList<Channel> removeChannels) {
ThingBuilder thingBuilder = editThing();
thingBuilder.withoutChannels(removeChannels);
updateThing(thingBuilder.build());
}
/**
* Updates a channel with a new state for a child of this bridge using the segmentIndex
*
* @param segmentIndex
* @param channelID
* @param state
*/
public void update(int segmentIndex, String channelID, State state) {
WLedSegmentHandler segmentHandler = segmentHandlers.get(segmentIndex);
if (segmentHandler != null) {
segmentHandler.update(channelID, state);
}
}
/**
* Updates the bridges channels with a new state.
*
* @param channelID
* @param state
*/
public void update(String channelID, State state) {
updateState(channelID, state);
}
@Override
public void childHandlerInitialized(final ThingHandler childHandler, final Thing childThing) {
BigDecimal segmentIndex = (BigDecimal) childThing.getConfiguration().get(CONFIG_SEGMENT_INDEX);
segmentHandlers.put(segmentIndex.intValue(), (WLedSegmentHandler) childHandler);
}
@Override
public void childHandlerDisposed(final ThingHandler childHandler, final Thing childThing) {
BigDecimal segmentIndex = (BigDecimal) childThing.getConfiguration().get(CONFIG_SEGMENT_INDEX);
segmentHandlers.remove(segmentIndex.intValue());
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
WledApi localApi = api;
if (localApi == null) {
return;
}
try {
switch (channelUID.getId()) {
case CHANNEL_GLOBAL_BRIGHTNESS:
if (command instanceof OnOffType) {
localApi.setGlobalOn(OnOffType.ON.equals(command));
} else if (command instanceof PercentType) {
if (PercentType.ZERO.equals(command)) {
localApi.setGlobalOn(false);
return;
}
localApi.setGlobalBrightness((PercentType) command);
}
break;
case CHANNEL_SLEEP:
localApi.setSleep(OnOffType.ON.equals(command));
break;
case CHANNEL_SLEEP_MODE:
localApi.setSleepMode(command.toString());
break;
case CHANNEL_SLEEP_BRIGHTNESS:
if (command instanceof PercentType) {
localApi.setSleepTargetBrightness((PercentType) command);
}
break;
case CHANNEL_SLEEP_DURATION:
if (command instanceof QuantityType) {
QuantityType<?> minutes = ((QuantityType<?>) command).toUnit(Units.MINUTE);
if (minutes != null) {
localApi.setSleepDuration(new BigDecimal(minutes.intValue()));
}
} else if (command instanceof DecimalType) {
localApi.setSleepDuration(new BigDecimal(((DecimalType) command).intValue()));
}
break;
case CHANNEL_PLAYLISTS:
localApi.setPreset(command.toString());
break;
case CHANNEL_SYNC_SEND:
localApi.setUdpSend(OnOffType.ON.equals(command));
break;
case CHANNEL_SYNC_RECEIVE:
localApi.setUdpRecieve(OnOffType.ON.equals(command));
break;
case CHANNEL_LIVE_OVERRIDE:
localApi.setLiveOverride(command.toString());
break;
case CHANNEL_PRESETS:
localApi.setPreset(command.toString());
break;
case CHANNEL_TRANS_TIME:
if (command instanceof QuantityType) {
QuantityType<?> seconds = ((QuantityType<?>) command).toUnit(Units.SECOND);
if (seconds != null) {
localApi.setTransitionTime(new BigDecimal(seconds.multiply(BigDecimal.TEN).intValue()));
}
} else if (command instanceof DecimalType) {
localApi.setTransitionTime(
new BigDecimal(((DecimalType) command).intValue()).multiply(BigDecimal.TEN));
}
break;
case CHANNEL_PRESET_DURATION:// ch removed in firmware 0.13.0 and newer
if (command instanceof QuantityType) {
QuantityType<?> seconds = ((QuantityType<?>) command).toUnit(Units.SECOND);
if (seconds != null) {
BigDecimal bigTemp = new BigDecimal(seconds.intValue()).multiply(new BigDecimal(1000));
localApi.sendGetRequest("/win&PT=" + bigTemp.intValue());
}
} else if (command instanceof DecimalType) {
BigDecimal bigTemp = new BigDecimal(((DecimalType) command).intValue())
.multiply(new BigDecimal(1000));
localApi.sendGetRequest("/win&PT=" + bigTemp.intValue());
}
break;
case CHANNEL_PRESET_CYCLE: // ch removed in firmware 0.13.0 and newer
if (command instanceof OnOffType) {
localApi.setPresetCycle(OnOffType.ON.equals(command));
}
break;
}
} catch (ApiException e) {
logger.debug("Exception occured when Channel:{}, Command:{}, Error:{}", channelUID.getId(), command,
e.getMessage());
}
}
private void pollState() {
WledApi localApi = api;
try {
if (localApi == null) {
api = localApi = apiFactory.getApi(this);
localApi.initialize();
}
localApi.update();
updateStatus(ThingStatus.ONLINE);
} catch (ApiException e) {
api = null;// Firmware may be updated so need to check next connect
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
@Override
public void initialize() {
config = getConfigAs(WLedConfiguration.class);
if (!config.address.contains("://")) {
logger.debug("Address was not entered in correct format, it may be the raw IP so adding http:// to start");
config.address = "http://" + config.address;
}
pollingFuture = scheduler.scheduleWithFixedDelay(this::pollState, 0, config.pollTime, TimeUnit.SECONDS);
}
@Override
public void dispose() {
Future<?> future = pollingFuture;
if (future != null) {
future.cancel(true);
pollingFuture = null;
}
api = null; // re-initialize api after configuration change
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Set.of(WLedActions.class, WLedSegmentDiscoveryService.class);
}
}

View File

@@ -10,37 +10,30 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.wled.internal;
package org.openhab.binding.wled.internal.handlers;
import static org.openhab.binding.wled.internal.WLedBindingConstants.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.wled.internal.WLedSegmentConfiguration;
import org.openhab.binding.wled.internal.api.ApiException;
import org.openhab.binding.wled.internal.api.WledApi;
import org.openhab.binding.wled.internal.api.WledApiFactory;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
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.thing.binding.ThingHandlerService;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
@@ -49,40 +42,67 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link WLedHandler} is responsible for handling commands and states, which are
* sent to one of the channels or http replies back.
* The {@link WLedSegmentHandler} is responsible for handling only a single segment from a WLED device.
*
* @author Matthew Skinner - Initial contribution
*/
@NonNullByDefault
public class WLedHandler extends BaseThingHandler {
public class WLedSegmentHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(getClass());
public final WledDynamicStateDescriptionProvider stateDescriptionProvider;
private WledApiFactory apiFactory;
private @Nullable WledApi api;
private @Nullable ScheduledFuture<?> pollingFuture = null;
private WLedSegmentConfiguration config = new WLedSegmentConfiguration();
private BigDecimal masterBrightness255 = BigDecimal.ZERO;
public boolean hasWhite = false;
private HSBType primaryColor = new HSBType();
private HSBType secondaryColor = new HSBType();
private HSBType thirdColor = new HSBType();
public WLedConfiguration config = new WLedConfiguration();
public WLedHandler(Thing thing, WledApiFactory apiFactory,
WledDynamicStateDescriptionProvider stateDescriptionProvider) {
public WLedSegmentHandler(Thing thing) {
super(thing);
this.apiFactory = apiFactory;
this.stateDescriptionProvider = stateDescriptionProvider;
}
public void update(String channelID, State state) {
updateState(channelID, state);
}
private void removeWhiteChannels() {
List<Channel> removeChannels = new ArrayList<>();
Channel channel = getThing().getChannel(CHANNEL_PRIMARY_WHITE);
if (channel != null) {
removeChannels.add(channel);
}
channel = getThing().getChannel(CHANNEL_SECONDARY_WHITE);
if (channel != null) {
removeChannels.add(channel);
}
channel = getThing().getChannel(CHANNEL_THIRD_WHITE);
if (channel != null) {
removeChannels.add(channel);
}
removeChannels(removeChannels);
}
private void removeChannels(List<Channel> removeChannels) {
if (!removeChannels.isEmpty()) {
ThingBuilder thingBuilder = editThing();
thingBuilder.withoutChannels(removeChannels);
updateThing(thingBuilder.build());
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
WledApi localApi = api;
Bridge bridge = getBridge();
if (bridge == null) {
return;
}
WLedBridgeHandler bridgeHandler = (WLedBridgeHandler) bridge.getHandler();
if (bridgeHandler == null) {
return;
}
WledApi localApi = bridgeHandler.api;
if (localApi == null) {
return;
}
BigDecimal bigTemp;
if (command instanceof RefreshType) {
return;// no need to check for refresh below
}
@@ -103,9 +123,6 @@ public class WLedHandler extends BaseThingHandler {
case CHANNEL_MIRROR:
localApi.setMirror(OnOffType.ON.equals(command), config.segmentIndex);
break;
case CHANNEL_LIVE_OVERRIDE:
localApi.setLiveOverride(command.toString());
break;
case CHANNEL_SPACING:
if (command instanceof DecimalType) {
localApi.setSpacing(((DecimalType) command).intValue(), config.segmentIndex);
@@ -119,12 +136,6 @@ public class WLedHandler extends BaseThingHandler {
case CHANNEL_REVERSE:
localApi.setReverse(OnOffType.ON.equals(command), config.segmentIndex);
break;
case CHANNEL_SYNC_SEND:
localApi.setUdpSend(OnOffType.ON.equals(command));
break;
case CHANNEL_SYNC_RECEIVE:
localApi.setUdpRecieve(OnOffType.ON.equals(command));
break;
case CHANNEL_PRIMARY_WHITE:
if (command instanceof PercentType) {
localApi.sendGetRequest(
@@ -165,10 +176,11 @@ public class WLedHandler extends BaseThingHandler {
}
localApi.setGlobalOn(true);
primaryColor = (HSBType) command;
if (primaryColor.getSaturation().intValue() < config.saturationThreshold && hasWhite) {
if (primaryColor.getSaturation().intValue() < bridgeHandler.config.saturationThreshold
&& bridgeHandler.hasWhite) {
localApi.setWhiteOnly((PercentType) command, config.segmentIndex);
} else if (primaryColor.getSaturation().intValue() == 32
&& primaryColor.getHue().intValue() == 36 && hasWhite) {
&& primaryColor.getHue().intValue() == 36 && bridgeHandler.hasWhite) {
localApi.setWhiteOnly((PercentType) command, config.segmentIndex);
} else {
localApi.setMasterHSB((HSBType) command, config.segmentIndex);
@@ -216,105 +228,38 @@ public class WLedHandler extends BaseThingHandler {
case CHANNEL_INTENSITY:
localApi.setFxIntencity((PercentType) command, config.segmentIndex);
break;
case CHANNEL_SLEEP:
localApi.setSleep(OnOffType.ON.equals(command));
break;
case CHANNEL_PLAYLISTS:
case CHANNEL_PRESETS:
localApi.setPreset(command.toString());
break;
case CHANNEL_PRESET_DURATION:// ch removed in firmware 0.13.0 and newer
if (command instanceof QuantityType) {
QuantityType<?> seconds = ((QuantityType<?>) command).toUnit(Units.SECOND);
if (seconds != null) {
bigTemp = new BigDecimal(seconds.intValue()).multiply(new BigDecimal(1000));
localApi.sendGetRequest("/win&PT=" + bigTemp.intValue());
}
}
break;
case CHANNEL_TRANS_TIME:
if (command instanceof QuantityType) {
QuantityType<?> seconds = ((QuantityType<?>) command).toUnit(Units.SECOND);
if (seconds != null) {
localApi.setTransitionTime(new BigDecimal(seconds.multiply(BigDecimal.TEN).intValue()));
}
}
break;
case CHANNEL_PRESET_CYCLE: // ch removed in firmware 0.13.0 and newer
if (command instanceof OnOffType) {
localApi.setPresetCycle(OnOffType.ON.equals(command));
}
break;
}
} catch (ApiException e) {
logger.debug("Exception occured:{}", e.getMessage());
}
}
public void savePreset(int position, String presetName) {
try {
if (api != null) {
api.savePreset(position, presetName);
}
} catch (ApiException e) {
}
}
public void removeChannels(ArrayList<Channel> removeChannels) {
if (!removeChannels.isEmpty()) {
ThingBuilder thingBuilder = editThing();
thingBuilder.withoutChannels(removeChannels);
updateThing(thingBuilder.build());
}
}
public void update(String channelID, State state) {
updateState(channelID, state);
}
private void pollState() {
WledApi localApi = api;
try {
if (localApi == null) {
api = apiFactory.getApi(this);
api.initialize();
}
if (localApi == null) {
return;
}
localApi.update();
updateStatus(ThingStatus.ONLINE);
} catch (ApiException e) {
api = null;// Firmware may be updated so need to check next connect
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
@Override
public void initialize() {
config = getConfigAs(WLedConfiguration.class);
if (config.segmentIndex < 0) {
config.segmentIndex = 0;
config = getConfigAs(WLedSegmentConfiguration.class);
Bridge bridge = getBridge();
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge is selected.");
} else {
WLedBridgeHandler localBridgeHandler = (WLedBridgeHandler) bridge.getHandler();
if (localBridgeHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
return;
}
WledApi localAPI = localBridgeHandler.api;
if (localAPI != null) {
updateStatus(ThingStatus.ONLINE);
localBridgeHandler.stateDescriptionProvider
.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_FX), localAPI.getUpdatedFxList());
localBridgeHandler.stateDescriptionProvider.setStateOptions(
new ChannelUID(getThing().getUID(), CHANNEL_PALETTES), localAPI.getUpdatedPaletteList());
if (!localBridgeHandler.hasWhite) {
logger.debug("WLED is not setup to use RGBW, so removing un-needed white channels");
removeWhiteChannels();
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
}
}
if (!config.address.contains("://")) {
logger.debug("Address was not entered in correct format, it may be the raw IP so adding http:// to start");
config.address = "http://" + config.address;
}
pollingFuture = scheduler.scheduleWithFixedDelay(this::pollState, 0, config.pollTime, TimeUnit.SECONDS);
}
@Override
public void dispose() {
Future<?> future = pollingFuture;
if (future != null) {
future.cancel(true);
pollingFuture = null;
}
api = null; // re-initialize api after configuration change
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(WLedActions.class);
}
}

View File

@@ -5,24 +5,32 @@ binding.wled.description = This is the binding for WLED
# thing types
thing-type.wled.wled.label = WLED String
thing-type.wled.wled.description = A WLED string of LEDs
thing-type.wled.json.label = WLED via JSON API
thing-type.wled.json.description = A connection to a WLED device controlled via the JSON API.
thing-type.wled.segment.label = WLED Segment
thing-type.wled.segment.description = Controls an entire LED strip, or a section of the strip if the LED string is split into multiple segments.
# thing types config
thing-type.config.wled.wled.address.label = Address
thing-type.config.wled.wled.address.description = Use this format http://192.168.1.2:80
thing-type.config.wled.wled.pollTime.label = Poll States
thing-type.config.wled.wled.pollTime.description = Time in seconds of how often to fetch the state of the LEDs.
thing-type.config.wled.wled.saturationThreshold.label = Saturation Threshold
thing-type.config.wled.wled.saturationThreshold.description = This feature allows you to specify a number that if the saturation drops below, will trigger white.
thing-type.config.wled.wled.segmentIndex.label = Segment Index
thing-type.config.wled.wled.segmentIndex.description = Leave this as 0 if you are not using segments, otherwise set this to the segment index number that you wish to control.
thing-type.config.wled.json.address.label = Address
thing-type.config.wled.json.address.description = Use this format http://192.168.1.2:80
thing-type.config.wled.json.pollTime.label = Poll Time
thing-type.config.wled.json.pollTime.description = Time in seconds of how often to fetch the state of the LEDs.
thing-type.config.wled.json.saturationThreshold.label = Saturation Threshold
thing-type.config.wled.json.saturationThreshold.description = This feature allows you to specify a number that if the saturation drops below, will trigger white.
thing-type.config.wled.json.sortEffects.label = Sort Effects
thing-type.config.wled.json.sortEffects.description = If set, will sort the state options of the effects channel alphabetically while keeping the first option (Solid) at the top.
thing-type.config.wled.json.sortPalettes.label = Sort Palettes
thing-type.config.wled.json.sortPalettes.description = If set, will sort the state options of the palettes channel alphabetically while keeping the first option (Default) at the top.
thing-type.config.wled.segment.segmentIndex.label = Segment Index
thing-type.config.wled.segment.segmentIndex.description = Leave this as 0 if you are not using multiple segments, otherwise set this to the segment index number that you wish to control.
# channel types
channel-type.wled.fx.label = Effect
channel-type.wled.fx.description = Use the built in FX
channel-type.wled.globalBrightness.label = Global Brightness
channel-type.wled.globalBrightness.description = Allows you to fade and turn all segments ON and OFF at the same time
channel-type.wled.grouping.label = Grouping
channel-type.wled.grouping.description = How many consecutive LEDs of the same segment will be grouped to the same color
channel-type.wled.intensity.label = FX Intensity
@@ -46,22 +54,6 @@ channel-type.wled.presetDuration.label = Preset Duration
channel-type.wled.presetDuration.description = Time for how long to show each preset for before moving to the next
channel-type.wled.presets.label = Presets
channel-type.wled.presets.description = Auto rotate or change to a saved preset
channel-type.wled.presets.state.option.1 = Preset 1
channel-type.wled.presets.state.option.2 = Preset 2
channel-type.wled.presets.state.option.3 = Preset 3
channel-type.wled.presets.state.option.4 = Preset 4
channel-type.wled.presets.state.option.5 = Preset 5
channel-type.wled.presets.state.option.6 = Preset 6
channel-type.wled.presets.state.option.7 = Preset 7
channel-type.wled.presets.state.option.8 = Preset 8
channel-type.wled.presets.state.option.9 = Preset 9
channel-type.wled.presets.state.option.10 = Preset 10
channel-type.wled.presets.state.option.11 = Preset 11
channel-type.wled.presets.state.option.12 = Preset 12
channel-type.wled.presets.state.option.13 = Preset 13
channel-type.wled.presets.state.option.14 = Preset 14
channel-type.wled.presets.state.option.15 = Preset 15
channel-type.wled.presets.state.option.16 = Preset 16
channel-type.wled.primaryColor.label = Primary Color
channel-type.wled.primaryColor.description = Allows you to change the primary color used in FX
channel-type.wled.primaryWhite.label = Primary White
@@ -76,6 +68,30 @@ channel-type.wled.segmentBrightness.label = Segment Brightness
channel-type.wled.segmentBrightness.description = Changes the brightness of the whole segment
channel-type.wled.sleep.label = Sleep Timer
channel-type.wled.sleep.description = Fade the level of light and turn off after set time
channel-type.wled.sleepDuration.label = Sleep Duration
channel-type.wled.sleepDuration.description = Time it takes to change/fade to the target brightness.
channel-type.wled.sleepDuration.state.option.1min = 1 Minute
channel-type.wled.sleepDuration.state.option.5min = 5 Minutes
channel-type.wled.sleepDuration.state.option.10min = 10 Minutes
channel-type.wled.sleepDuration.state.option.15min = 15 Minutes
channel-type.wled.sleepDuration.state.option.20min = 20 Minutes
channel-type.wled.sleepDuration.state.option.25min = 25 Minutes
channel-type.wled.sleepDuration.state.option.30min = 30 Minutes
channel-type.wled.sleepDuration.state.option.40min = 40 Minutes
channel-type.wled.sleepDuration.state.option.50min = 50 Minutes
channel-type.wled.sleepDuration.state.option.60min = 1 Hour
channel-type.wled.sleepDuration.state.option.90min = 1.5 Hours
channel-type.wled.sleepDuration.state.option.120min = 2 Hours
channel-type.wled.sleepDuration.state.option.150min = 2.5 Hours
channel-type.wled.sleepDuration.state.option.240min = 4 Hours
channel-type.wled.sleepMode.label = Sleep Mode
channel-type.wled.sleepMode.description = Timed Light Mode selects how the light will fade or increase when the sleep timer is turned ON.
channel-type.wled.sleepMode.state.option.0 = Instant
channel-type.wled.sleepMode.state.option.1 = Fade
channel-type.wled.sleepMode.state.option.2 = Color Fade
channel-type.wled.sleepMode.state.option.3 = Sunrise
channel-type.wled.sleepTargetBrightness.label = Sleep Target Brightness
channel-type.wled.sleepTargetBrightness.description = Sets how bright the light will be after the sleep duration time has expired.
channel-type.wled.spacing.label = Spacing
channel-type.wled.spacing.description = How many LEDs are turned off and skipped between each group
channel-type.wled.speed.label = FX Speed
@@ -90,3 +106,16 @@ channel-type.wled.tertiaryWhite.label = Tertiary White
channel-type.wled.tertiaryWhite.description = Changes the brightness of the third white LED
channel-type.wled.transformTime.label = Transform Time
channel-type.wled.transformTime.description = Time it takes to change/fade from one look to the next.
channel-type.wled.transformTime.state.option.0s = 0 Seconds
channel-type.wled.transformTime.state.option.0.3s = 0.3 Seconds
channel-type.wled.transformTime.state.option.0.7s = 0.7 Seconds
channel-type.wled.transformTime.state.option.1s = 1 Second
channel-type.wled.transformTime.state.option.2s = 2 Seconds
channel-type.wled.transformTime.state.option.3s = 3 Seconds
channel-type.wled.transformTime.state.option.4s = 4 Seconds
channel-type.wled.transformTime.state.option.5s = 5 Seconds
channel-type.wled.transformTime.state.option.6s = 6 Seconds
channel-type.wled.transformTime.state.option.7s = 7 Seconds
channel-type.wled.transformTime.state.option.8s = 8 Seconds
channel-type.wled.transformTime.state.option.9s = 9 Seconds
channel-type.wled.transformTime.state.option.10s = 10 Seconds

View File

@@ -4,9 +4,65 @@
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="wled">
<label>WLED String</label>
<description>A WLED string of LEDs</description>
<bridge-type id="json">
<label>WLED via JSON API</label>
<description>A connection to a WLED device controlled via the JSON API.</description>
<channels>
<channel id="globalBrightness" typeId="globalBrightness"/>
<channel id="playlists" typeId="playlists"/>
<channel id="presets" typeId="presets"/>
<channel id="presetDuration" typeId="presetDuration"/>
<channel id="transformTime" typeId="transformTime"/>
<channel id="presetCycle" typeId="presetCycle"/>
<channel id="liveOverride" typeId="liveOverride"/>
<channel id="sleep" typeId="sleep"/>
<channel id="sleepMode" typeId="sleepMode"/>
<channel id="sleepDuration" typeId="sleepDuration"/>
<channel id="sleepTargetBrightness" typeId="sleepTargetBrightness"/>
<channel id="syncSend" typeId="syncSend"/>
<channel id="syncReceive" typeId="syncReceive"/>
</channels>
<config-description>
<parameter name="address" type="text" required="true">
<label>Address</label>
<description>Use this format http://192.168.1.2:80</description>
</parameter>
<parameter name="pollTime" type="integer" min="1" unit="s">
<label>Poll Time</label>
<description>Time in seconds of how often to fetch the state of the LEDs.</description>
<default>5</default>
</parameter>
<parameter name="saturationThreshold" type="integer" min="0" max="99">
<label>Saturation Threshold</label>
<description>This feature allows you to specify a number that if the saturation drops below, will trigger white.
</description>
<advanced>true</advanced>
<default>0</default>
</parameter>
<parameter name="sortEffects" type="boolean">
<label>Sort Effects</label>
<description>If set, will sort the state options of the effects channel alphabetically while keeping the first
option (Solid) at the top.</description>
<advanced>true</advanced>
<default>true</default>
</parameter>
<parameter name="sortPalettes" type="boolean">
<label>Sort Palettes</label>
<description>If set, will sort the state options of the palettes channel alphabetically while keeping the first
option (Default) at the top.</description>
<advanced>true</advanced>
<default>true</default>
</parameter>
</config-description>
</bridge-type>
<thing-type id="segment">
<supported-bridge-type-refs>
<bridge-type-ref id="json"/>
</supported-bridge-type-refs>
<label>WLED Segment</label>
<description>Controls an entire LED strip, or a section of the strip if the LED string is split into multiple
segments.</description>
<category>ColorLight</category>
<channels>
<channel id="masterControls" typeId="masterControls"/>
@@ -17,11 +73,6 @@
<channel id="secondaryWhite" typeId="secondaryWhite"/>
<channel id="tertiaryColor" typeId="tertiaryColor"/>
<channel id="tertiaryWhite" typeId="tertiaryWhite"/>
<channel id="presets" typeId="presets"/>
<channel id="playlists" typeId="playlists"/>
<channel id="presetDuration" typeId="presetDuration"/>
<channel id="transformTime" typeId="transformTime"/>
<channel id="presetCycle" typeId="presetCycle"/>
<channel id="palettes" typeId="palettes"/>
<channel id="fx" typeId="fx"/>
<channel id="speed" typeId="speed"/>
@@ -30,48 +81,24 @@
<channel id="reverse" typeId="reverse"/>
<channel id="grouping" typeId="grouping"/>
<channel id="spacing" typeId="spacing"/>
<channel id="liveOverride" typeId="liveOverride"/>
<channel id="sleep" typeId="sleep"/>
<channel id="syncSend" typeId="syncSend"/>
<channel id="syncReceive" typeId="syncReceive"/>
</channels>
<config-description>
<parameter name="address" type="text" required="true">
<label>Address</label>
<description>Use this format http://192.168.1.2:80</description>
</parameter>
<parameter name="pollTime" type="integer" required="true" min="1" unit="s">
<label>Poll States</label>
<description>Time in seconds of how often to fetch the state of the LEDs.</description>
<default>10</default>
</parameter>
<parameter name="segmentIndex" type="integer" required="true" min="0">
<parameter name="segmentIndex" type="integer" min="0">
<label>Segment Index</label>
<description>Leave this as 0 if you are not using segments, otherwise set this to the segment index number that you
wish to control.</description>
<description>Leave this as 0 if you are not using multiple segments, otherwise set this to the segment index number
that you wish to control.</description>
<default>0</default>
</parameter>
<parameter name="saturationThreshold" type="integer" required="true" min="0" max="99">
<label>Saturation Threshold</label>
<description>This feature allows you to specify a number that if the saturation drops below, will trigger white.
</description>
<default>0</default>
</parameter>
<parameter name="sortEffects" type="boolean">
<label>Sort Effects</label>
<description>If set, will sort the state options of the effects channel alphabetically while keeping the first
option (Solid) at the top.</description>
<default>false</default>
</parameter>
<parameter name="sortPalettes" type="boolean">
<label>Sort Palettes</label>
<description>If set, will sort the state options of the palettes channel alphabetically while keeping the first
option (Default) at the top.</description>
<default>false</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="globalBrightness">
<item-type>Dimmer</item-type>
<label>Global Brightness</label>
<description>Allows you to fade and turn all segments ON and OFF at the same time</description>
<category>ColorLight</category>
</channel-type>
<channel-type id="masterControls">
<item-type>Color</item-type>
<label>Master Controls</label>
@@ -83,11 +110,11 @@
</tags>
</channel-type>
<channel-type id="segmentBrightness" advanced="true">
<channel-type id="segmentBrightness">
<item-type>Dimmer</item-type>
<label>Segment Brightness</label>
<description>Changes the brightness of the whole segment</description>
<category>Light</category>
<category>ColorLight</category>
</channel-type>
<channel-type id="primaryColor" advanced="true">
@@ -101,7 +128,7 @@
<item-type>Dimmer</item-type>
<label>Primary White</label>
<description>Changes the brightness of the primary white LED</description>
<category>Light</category>
<category>ColorLight</category>
</channel-type>
<channel-type id="secondaryColor" advanced="true">
@@ -115,7 +142,7 @@
<item-type>Dimmer</item-type>
<label>Secondary White</label>
<description>Changes the brightness of the secondary white LED</description>
<category>Light</category>
<category>ColorLight</category>
</channel-type>
<channel-type id="tertiaryColor" advanced="true">
@@ -129,7 +156,7 @@
<item-type>Dimmer</item-type>
<label>Tertiary White</label>
<description>Changes the brightness of the third white LED</description>
<category>Light</category>
<category>ColorLight</category>
</channel-type>
<channel-type id="palettes">
@@ -148,26 +175,6 @@
<item-type>String</item-type>
<label>Presets</label>
<description>Auto rotate or change to a saved preset</description>
<state>
<options>
<option value="1">Preset 1</option>
<option value="2">Preset 2</option>
<option value="3">Preset 3</option>
<option value="4">Preset 4</option>
<option value="5">Preset 5</option>
<option value="6">Preset 6</option>
<option value="7">Preset 7</option>
<option value="8">Preset 8</option>
<option value="9">Preset 9</option>
<option value="10">Preset 10</option>
<option value="11">Preset 11</option>
<option value="12">Preset 12</option>
<option value="13">Preset 13</option>
<option value="14">Preset 14</option>
<option value="15">Preset 15</option>
<option value="16">Preset 16</option>
</options>
</state>
</channel-type>
<channel-type id="playlists">
@@ -218,19 +225,19 @@
<category>Time</category>
<state>
<options>
<option value="0 s"/>
<option value="0.3 s"/>
<option value="0.7 s"/>
<option value="1 s"/>
<option value="2 s"/>
<option value="3 s"/>
<option value="4 s"/>
<option value="5 s"/>
<option value="6 s"/>
<option value="7 s"/>
<option value="8 s"/>
<option value="9 s"/>
<option value="10 s"/>
<option value="0s">0 Seconds</option>
<option value="0.3s">0.3 Seconds</option>
<option value="0.7s">0.7 Seconds</option>
<option value="1s">1 Second</option>
<option value="2s">2 Seconds</option>
<option value="3s">3 Seconds</option>
<option value="4s">4 Seconds</option>
<option value="5s">5 Seconds</option>
<option value="6s">6 Seconds</option>
<option value="7s">7 Seconds</option>
<option value="8s">8 Seconds</option>
<option value="9s">9 Seconds</option>
<option value="10s">10 Seconds</option>
</options>
</state>
</channel-type>
@@ -266,6 +273,51 @@
<category>Time</category>
</channel-type>
<channel-type id="sleepMode" advanced="true">
<item-type>String</item-type>
<label>Sleep Mode</label>
<description>Timed Light Mode selects how the light will fade or increase when the sleep timer is turned ON.</description>
<state readOnly="false">
<options>
<option value="0">Instant</option>
<option value="1">Fade</option>
<option value="2">Color Fade</option>
<option value="3">Sunrise</option>
</options>
</state>
</channel-type>
<channel-type id="sleepDuration" advanced="true">
<item-type>Number:Time</item-type>
<label>Sleep Duration</label>
<description>Time it takes to change/fade to the target brightness.</description>
<category>Time</category>
<state min="1" max="255" step="1" readOnly="false">
<options>
<option value="1min">1 Minute</option>
<option value="5min">5 Minutes</option>
<option value="10min">10 Minutes</option>
<option value="15min">15 Minutes</option>
<option value="20min">20 Minutes</option>
<option value="25min">25 Minutes</option>
<option value="30min">30 Minutes</option>
<option value="40min">40 Minutes</option>
<option value="50min">50 Minutes</option>
<option value="60min">1 Hour</option>
<option value="90min">1.5 Hours</option>
<option value="120min">2 Hours</option>
<option value="150min">2.5 Hours</option>
<option value="240min">4 Hours</option>
</options>
</state>
</channel-type>
<channel-type id="sleepTargetBrightness" advanced="true">
<item-type>Dimmer</item-type>
<label>Sleep Target Brightness</label>
<description>Sets how bright the light will be after the sleep duration time has expired.</description>
</channel-type>
<channel-type id="presetCycle" advanced="true">
<item-type>Switch</item-type>
<label>Preset Cycle</label>