[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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 884 additions and 377 deletions

View File

@ -7,27 +7,50 @@ This binding allows you to auto discover and use LED strings based on the WLED p
| Thing Type ID | Description |
|-|-|
| `wled` | Use this for RGB and RGBW strings. |
| `json` | A bridge to a WLED device using the JSON API. Add this thing first. |
| `segment` | A segment is used to turn a LED strip or string, into 1 or more lights. Each segment is like a separate light globe that can have its own color or effect. |
## Discovery
The auto discovery will find your WLED if your network supports mDNS and the UDP port 5353 is not blocked by a fire wall.
Before discovering any WLED devices, you may wish to name them by providing a 'Server description' in the WLED web page, CONFIG>User Interface> setup page.
openHAB will then discover and auto name your WLED to the name provided as the 'Server description'.
If it fails to find your WLED, you can still manually add a `wled` thing by using the UI or textual methods.
For multiple segments, the binding will only auto find the first segment.
For additional segments, you can add them manually and set the `segmentIndex` config to the correct number shown in the WLED control web page.
openHAB will then discover and auto name your WLED bridge thing to the name provided as the 'Server description'.
Segments will be discovered with an Inbox scan after the bridge thing is first showing up as ONLINE.
Any segments that have been given a name in the WLED firmware, will be given the same name when discovery adds them to the Inbox.
## Thing Configuration
## Bridge Thing Configuration
| Parameter | Description | Required | Default |
|-|-|-|-|
| `address`| The full URL to your WLED device. Example is `http://192.168.0.2:80` | Y | |
| `pollTime`| How often in seconds you want the states of the LED fetched in case you make changes with a non openHAB app, web browser, or the light is auto changing FX or presets. | Y | 10 |
| `segmentIndex` | The index number to the LED segment you wish these channels to control. Leave on 0 if you do not know what a segment is. | Y | 0 |
| `saturationThreshold` | Allows you to use a colorpicker control linked to the `masterControls` channel to trigger only using the pure white LEDs instead of creating fake white light from the RGB channels. Try setting the value to 12 or leave this on 0 for RGB strings. | Y | 0 |
## Channels
## Bridge Thing Channels
| Channel | Type | Description |
|-|-|-|
| `globalBrightness` | Dimmer | Changes the brightness of all segments at the same time. |
| `presets` | String | A list of presets that you can select from and will display -1 when no presets are running. |
| `playlists` | String | A list of playlists that you can select from and will display -1 when none are running. |
| `presetCycle` | Switch | Turns ON/OFF the automatic changing from one preset to the next. Only in V0.12.0 and older firmwares. |
| `presetDuration` | Number:Time | How long in seconds it will display a preset for, before it begins to change from one preset to the next with `presetCycle` turned ON. Only in V0.12.0 and older firmwares. |
| `transformTime` | Number:Time | How long in seconds it takes to transform/morph from one look to the next. |
| `sleep` | Switch | Turns on the sleep or 'night light' timer which can be configured to work in many different ways. Refer to WLED documentation for how this can be setup. The default action is the light will fade to OFF over the next 60 minutes. |
| `sleepMode` | String | Timed Light Mode selects how the light will fade or increase when the sleep timer is turned ON. |
| `sleepDuration` | Number:Time | Time it takes to change/fade to the target brightness. |
| `sleepTargetBrightness` | Dimmer | Sets how bright the light will be after the sleep duration time has expired. |
| `syncSend` | Switch | Sends UDP packets that tell other WLED lights to follow this one. |
| `syncReceive` | Switch | Allows UDP packets from other WLED lights to control this one. |
| `liveOverride` | String | A value of "0" turns off, "1" will override live data to display what you want, and "2" overrides until you reboot the ESP device. |
## Thing Configuration
| Parameter | Description | Required | Default |
|-|-|-|-|
| `segmentIndex` | The index number to the LED segment you wish these channels to control. Leave on 0 if you do not know what a segment is. | Y | 0 |
## Thing Channels
| Channel | Type | Description |
|-|-|-|
@ -43,17 +66,8 @@ For additional segments, you can add them manually and set the `segmentIndex` co
| `fx` | String | A list of Effects you can select from. |
| `speed` | Dimmer | Changes the speed of the loaded effect. |
| `intensity` | Dimmer | Changes the intensity of the loaded effect. |
| `presets` | String | A list of presets that you can select from and will display -1 when no presets are running. |
| `playlists` | String | A list of playlists that you can select from and will display -1 when none are running. |
| `presetCycle` | Switch | Turns ON/OFF the automatic changing from one preset to the next. Only in V0.12.0 and older firmwares. |
| `presetDuration` | Number:Time | How long in seconds it will display a preset for, before it begins to change from one preset to the next with `presetCycle` turned ON. Only in V0.12.0 and older firmwares. |
| `transformTime` | Number:Time | How long in seconds it takes to transform/morph from one look to the next. |
| `sleep` | Switch | Turns on the sleep or 'night light' timer which can be configured to work in many different ways. Refer to WLED documentation for how this can be setup. The default action is the light will fade to OFF over the next 60 minutes. |
| `syncSend` | Switch | Sends UDP packets that tell other WLED lights to follow this one. |
| `syncReceive` | Switch | Allows UDP packets from other WLED lights to control this one. |
| `mirror` | Switch | Mirror the effect for this segment. |
| `reverse` | Switch | Reverse the effect for this segment. |
| `liveOverride` | String | A value of "0" turns off, "1" will override live data to display what you want, and "2" overrides until you reboot the ESP device. |
| `grouping` | Number | The number of LEDs that are grouped together to display as one pixel in FX. Use metadata to display a list widget slider. |
| `spacing` | Number | The number of LEDs that will not light up in between FX pixels. Use metadata to display a list widget slider. |
@ -64,12 +78,12 @@ This binding has two rule Actions `savePreset(int presetNumber)` and `savePreset
In Xtend rules, you can use the Actions like this.
```
getActions("wled", "wled:wled:XmasTree").savePreset(5,"Flashy Preset")
getActions("wled", "wled:json:XmasTree").savePreset(5,"Flashy Preset")
```
## Sitemap Example
If you use the ADMIN>MODEL>`Create equipment from thing` feature you can use the below and just change the name before the underscore to match what you named the `wled` thing when it was added via the Inbox.
If you use the ADMIN>MODEL>`Create equipment from thing` feature you can use the below and just change the name before the underscore to match what you named the `segment` thing when it was added via the Inbox.
*.sitemap

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>