]> git.basschouten.com Git - openhab-addons.git/commitdiff
[wled] Change to bridge/thing structure and add global controls (#12199)
authorMatthew Skinner <matt@pcmus.com>
Fri, 27 May 2022 11:26:47 +0000 (21:26 +1000)
committerGitHub <noreply@github.com>
Fri, 27 May 2022 11:26:47 +0000 (13:26 +0200)
* 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>
20 files changed:
bundles/org.openhab.binding.wled/README.md
bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedActions.java
bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedBindingConstants.java
bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedConfiguration.java
bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedDiscoveryService.java
bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedHandler.java [deleted file]
bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedHandlerFactory.java
bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedHelper.java
bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedSegmentConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedSegmentDiscoveryService.java [new file with mode: 0644]
bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WledState.java
bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApi.java
bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApiFactory.java
bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApiV0110.java
bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApiV0130.java
bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApiV084.java
bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/handlers/WLedBridgeHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/handlers/WLedSegmentHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.wled/src/main/resources/OH-INF/i18n/wled.properties
bundles/org.openhab.binding.wled/src/main/resources/OH-INF/thing/thing-types.xml

index 46e5451e98c349144daba87f8f7dc2cdb0b93faf..0c805bbd44379cff4b2de1586d6bf872d5c772d8 100644 (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
 
index 72cb29d85ec259325eb33e8915a7d4cf44d28a8d..a79329ec2863503b28b137d6fc98f84672b353a8 100644 (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);
         }
index b5812124a0ed8d4bbe4d78b36fcc13625489d21f..9aa17a5ba24df68881bdf06a2928d513d661b20a 100644 (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";
 }
index 4fdd66d3307e5bd6cf2c73e51d90d8ffc6dfc898..8fb23ea1edfc57b4940f914d1b6e6034e983ea7d 100644 (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;
 }
index 042ca1bd558bdf34dc24693837d5411875f0a7e1..b8e77a692c8bcc71bd0ad1519bae02bd59b2c3e1 100644 (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
diff --git a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedHandler.java b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedHandler.java
deleted file mode 100644 (file)
index f51ab29..0000000
+++ /dev/null
@@ -1,320 +0,0 @@
-/**
- * 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.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 org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-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.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;
-import org.openhab.core.types.State;
-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.
- *
- * @author Matthew Skinner - Initial contribution
- */
-
-@NonNullByDefault
-public class WLedHandler 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 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) {
-        super(thing);
-        this.apiFactory = apiFactory;
-        this.stateDescriptionProvider = stateDescriptionProvider;
-    }
-
-    @Override
-    public void handleCommand(ChannelUID channelUID, Command command) {
-        WledApi localApi = api;
-        if (localApi == null) {
-            return;
-        }
-        BigDecimal bigTemp;
-        if (command instanceof RefreshType) {
-            return;// no need to check for refresh below
-        }
-        logger.debug("command {} sent to {}", command, channelUID.getId());
-        try {
-            switch (channelUID.getId()) {
-                case CHANNEL_SEGMENT_BRIGHTNESS:
-                    if (command instanceof OnOffType) {
-                        localApi.setMasterOn(OnOffType.ON.equals(command), config.segmentIndex);
-                    } else if (command instanceof PercentType) {
-                        if (PercentType.ZERO.equals(command)) {
-                            localApi.setMasterOn(false, config.segmentIndex);
-                            return;
-                        }
-                        localApi.setMasterBrightness((PercentType) command, config.segmentIndex);
-                    }
-                    break;
-                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);
-                    }
-                    break;
-                case CHANNEL_GROUPING:
-                    if (command instanceof DecimalType) {
-                        localApi.setGrouping(((DecimalType) command).intValue(), config.segmentIndex);
-                    }
-                    break;
-                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(
-                                "/win&W=" + ((PercentType) command).toBigDecimal().multiply(BIG_DECIMAL_2_55));
-                    }
-                    break;
-                case CHANNEL_SECONDARY_WHITE:
-                    if (command instanceof PercentType) {
-                        localApi.sendGetRequest(
-                                "/win&W2=" + ((PercentType) command).toBigDecimal().multiply(BIG_DECIMAL_2_55));
-                    }
-                    break;
-                case CHANNEL_MASTER_CONTROLS:
-                    if (command instanceof OnOffType) {
-                        if (OnOffType.ON.equals(command)) {
-                            // global may be off, but we don't want to switch global off and affect other segments
-                            localApi.setGlobalOn(true);
-                        }
-                        localApi.setMasterOn(OnOffType.ON.equals(command), config.segmentIndex);
-                    } else if (command instanceof IncreaseDecreaseType) {
-                        if (IncreaseDecreaseType.INCREASE.equals(command)) {
-                            if (masterBrightness255.intValue() < 240) {
-                                localApi.sendGetRequest("/win&TT=1000&A=~15"); // 255 divided by 15 = 17 levels
-                            } else {
-                                localApi.sendGetRequest("/win&TT=1000&A=255");
-                            }
-                        } else {
-                            if (masterBrightness255.intValue() > 15) {
-                                localApi.sendGetRequest("/win&TT=1000&A=~-15");
-                            } else {
-                                localApi.sendGetRequest("/win&TT=1000&A=0");
-                            }
-                        }
-                    } else if (command instanceof HSBType) {
-                        if ((((HSBType) command).getBrightness()).equals(PercentType.ZERO)) {
-                            localApi.setMasterOn(false, config.segmentIndex);
-                            return;
-                        }
-                        localApi.setGlobalOn(true);
-                        primaryColor = (HSBType) command;
-                        if (primaryColor.getSaturation().intValue() < config.saturationThreshold && hasWhite) {
-                            localApi.setWhiteOnly((PercentType) command, config.segmentIndex);
-                        } else if (primaryColor.getSaturation().intValue() == 32
-                                && primaryColor.getHue().intValue() == 36 && hasWhite) {
-                            localApi.setWhiteOnly((PercentType) command, config.segmentIndex);
-                        } else {
-                            localApi.setMasterHSB((HSBType) command, config.segmentIndex);
-                        }
-                    } else if (command instanceof PercentType) {
-                        localApi.setMasterBrightness((PercentType) command, config.segmentIndex);
-                    }
-                    return;
-                case CHANNEL_PRIMARY_COLOR:
-                    if (command instanceof HSBType) {
-                        primaryColor = (HSBType) command;
-                    } else if (command instanceof PercentType) {
-                        primaryColor = new HSBType(primaryColor.getHue(), primaryColor.getSaturation(),
-                                ((PercentType) command));
-                    }
-                    localApi.setPrimaryColor(primaryColor, config.segmentIndex);
-                    return;
-                case CHANNEL_SECONDARY_COLOR:
-                    if (command instanceof HSBType) {
-                        secondaryColor = (HSBType) command;
-                    } else if (command instanceof PercentType) {
-                        secondaryColor = new HSBType(secondaryColor.getHue(), secondaryColor.getSaturation(),
-                                ((PercentType) command));
-                    }
-                    localApi.setSecondaryColor(secondaryColor, config.segmentIndex);
-                    return;
-                case CHANNEL_THIRD_COLOR:
-                    if (command instanceof HSBType) {
-                        thirdColor = (HSBType) command;
-                    } else if (command instanceof PercentType) {
-                        thirdColor = new HSBType(thirdColor.getHue(), thirdColor.getSaturation(),
-                                ((PercentType) command));
-                    }
-                    localApi.setTertiaryColor(thirdColor, config.segmentIndex);
-                    return;
-                case CHANNEL_PALETTES:
-                    localApi.setPalette(command.toString(), config.segmentIndex);
-                    break;
-                case CHANNEL_FX:
-                    localApi.setEffect(command.toString(), config.segmentIndex);
-                    break;
-                case CHANNEL_SPEED:
-                    localApi.setFxSpeed((PercentType) command, config.segmentIndex);
-                    break;
-                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;
-        }
-        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);
-    }
-}
index 5ca32313a240bb5ffc0507ee4320910560c42abd..0cec099c4332d2d8e57d1dccf1a07dc9b599a1ef 100644 (file)
  */
 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;
     }
index d09b722be23ef9892464fda478dfed197fa11c92..00750bf191f26b4aeae5737f19a25a246392639c 100644 (file)
  */
 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();
         }
diff --git a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedSegmentConfiguration.java b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedSegmentConfiguration.java
new file mode 100644 (file)
index 0000000..2c8851d
--- /dev/null
@@ -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;
+}
diff --git a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedSegmentDiscoveryService.java b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedSegmentDiscoveryService.java
new file mode 100644 (file)
index 0000000..a78847a
--- /dev/null
@@ -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() {
+    }
+}
index ad7ce14c4fb762d502c0477799f9947afe38f8dd..56f2b5e98c925b21241a9eb834a7ad9a04962003 100644 (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 {
index 886bf47d9cc0909cdc301ad7e44b07fbec8cd68c..06f6692645ae873137d120c59b364950f504f4ce 100644 (file)
 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();
 }
index 38ca3963f2bdfb2a37df9a821f18e3a2e50a7623..c0273a652dcf43708c71f65d17bea0965401daab 100644 (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);
index b86c873cc2ca9245696dc3a3d4d4c6036d64e99e..bfa43cb080c8a40a7efc8afb386a86ebc228ca99 100644 (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 + "}}");
+    }
 }
index 5d0a1378453008ea9a403e85c5601bedaf80ba18..74daca7cb064719329bef223f108c768f82ea8a6 100644 (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;
+    }
 }
index 816e2ba05e40c789d1f97d641a6cd156cae24738..7a157d67258eb463ba74bd005e04e50a1e64b540 100644 (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);
-        } else {
-            handler.update(CHANNEL_MIRROR, OnOffType.OFF);
-        }
-        if (state.stateResponse.seg[handler.config.segmentIndex].rev) {
-            handler.update(CHANNEL_REVERSE, OnOffType.ON);
+        if (state.stateResponse.pl == 0) {
+            handler.update(CHANNEL_PRESET_CYCLE, 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() + "}}");
+    }
 }
diff --git a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/handlers/WLedBridgeHandler.java b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/handlers/WLedBridgeHandler.java
new file mode 100644 (file)
index 0000000..aaf94b8
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/handlers/WLedSegmentHandler.java b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/handlers/WLedSegmentHandler.java
new file mode 100644 (file)
index 0000000..d9d8963
--- /dev/null
@@ -0,0 +1,265 @@
+/**
+ * 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.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+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.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.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.builder.ThingBuilder;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.State;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link WLedSegmentHandler} is responsible for handling only a single segment from a WLED device.
+ *
+ * @author Matthew Skinner - Initial contribution
+ */
+
+@NonNullByDefault
+public class WLedSegmentHandler extends BaseThingHandler {
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+    private WLedSegmentConfiguration config = new WLedSegmentConfiguration();
+    private BigDecimal masterBrightness255 = BigDecimal.ZERO;
+    private HSBType primaryColor = new HSBType();
+    private HSBType secondaryColor = new HSBType();
+    private HSBType thirdColor = new HSBType();
+
+    public WLedSegmentHandler(Thing thing) {
+        super(thing);
+    }
+
+    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) {
+        Bridge bridge = getBridge();
+        if (bridge == null) {
+            return;
+        }
+        WLedBridgeHandler bridgeHandler = (WLedBridgeHandler) bridge.getHandler();
+        if (bridgeHandler == null) {
+            return;
+        }
+        WledApi localApi = bridgeHandler.api;
+        if (localApi == null) {
+            return;
+        }
+        if (command instanceof RefreshType) {
+            return;// no need to check for refresh below
+        }
+        logger.debug("command {} sent to {}", command, channelUID.getId());
+        try {
+            switch (channelUID.getId()) {
+                case CHANNEL_SEGMENT_BRIGHTNESS:
+                    if (command instanceof OnOffType) {
+                        localApi.setMasterOn(OnOffType.ON.equals(command), config.segmentIndex);
+                    } else if (command instanceof PercentType) {
+                        if (PercentType.ZERO.equals(command)) {
+                            localApi.setMasterOn(false, config.segmentIndex);
+                            return;
+                        }
+                        localApi.setMasterBrightness((PercentType) command, config.segmentIndex);
+                    }
+                    break;
+                case CHANNEL_MIRROR:
+                    localApi.setMirror(OnOffType.ON.equals(command), config.segmentIndex);
+                    break;
+                case CHANNEL_SPACING:
+                    if (command instanceof DecimalType) {
+                        localApi.setSpacing(((DecimalType) command).intValue(), config.segmentIndex);
+                    }
+                    break;
+                case CHANNEL_GROUPING:
+                    if (command instanceof DecimalType) {
+                        localApi.setGrouping(((DecimalType) command).intValue(), config.segmentIndex);
+                    }
+                    break;
+                case CHANNEL_REVERSE:
+                    localApi.setReverse(OnOffType.ON.equals(command), config.segmentIndex);
+                    break;
+                case CHANNEL_PRIMARY_WHITE:
+                    if (command instanceof PercentType) {
+                        localApi.sendGetRequest(
+                                "/win&W=" + ((PercentType) command).toBigDecimal().multiply(BIG_DECIMAL_2_55));
+                    }
+                    break;
+                case CHANNEL_SECONDARY_WHITE:
+                    if (command instanceof PercentType) {
+                        localApi.sendGetRequest(
+                                "/win&W2=" + ((PercentType) command).toBigDecimal().multiply(BIG_DECIMAL_2_55));
+                    }
+                    break;
+                case CHANNEL_MASTER_CONTROLS:
+                    if (command instanceof OnOffType) {
+                        if (OnOffType.ON.equals(command)) {
+                            // global may be off, but we don't want to switch global off and affect other segments
+                            localApi.setGlobalOn(true);
+                        }
+                        localApi.setMasterOn(OnOffType.ON.equals(command), config.segmentIndex);
+                    } else if (command instanceof IncreaseDecreaseType) {
+                        if (IncreaseDecreaseType.INCREASE.equals(command)) {
+                            if (masterBrightness255.intValue() < 240) {
+                                localApi.sendGetRequest("/win&TT=1000&A=~15"); // 255 divided by 15 = 17 levels
+                            } else {
+                                localApi.sendGetRequest("/win&TT=1000&A=255");
+                            }
+                        } else {
+                            if (masterBrightness255.intValue() > 15) {
+                                localApi.sendGetRequest("/win&TT=1000&A=~-15");
+                            } else {
+                                localApi.sendGetRequest("/win&TT=1000&A=0");
+                            }
+                        }
+                    } else if (command instanceof HSBType) {
+                        if ((((HSBType) command).getBrightness()).equals(PercentType.ZERO)) {
+                            localApi.setMasterOn(false, config.segmentIndex);
+                            return;
+                        }
+                        localApi.setGlobalOn(true);
+                        primaryColor = (HSBType) command;
+                        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 && bridgeHandler.hasWhite) {
+                            localApi.setWhiteOnly((PercentType) command, config.segmentIndex);
+                        } else {
+                            localApi.setMasterHSB((HSBType) command, config.segmentIndex);
+                        }
+                    } else if (command instanceof PercentType) {
+                        localApi.setMasterBrightness((PercentType) command, config.segmentIndex);
+                    }
+                    return;
+                case CHANNEL_PRIMARY_COLOR:
+                    if (command instanceof HSBType) {
+                        primaryColor = (HSBType) command;
+                    } else if (command instanceof PercentType) {
+                        primaryColor = new HSBType(primaryColor.getHue(), primaryColor.getSaturation(),
+                                ((PercentType) command));
+                    }
+                    localApi.setPrimaryColor(primaryColor, config.segmentIndex);
+                    return;
+                case CHANNEL_SECONDARY_COLOR:
+                    if (command instanceof HSBType) {
+                        secondaryColor = (HSBType) command;
+                    } else if (command instanceof PercentType) {
+                        secondaryColor = new HSBType(secondaryColor.getHue(), secondaryColor.getSaturation(),
+                                ((PercentType) command));
+                    }
+                    localApi.setSecondaryColor(secondaryColor, config.segmentIndex);
+                    return;
+                case CHANNEL_THIRD_COLOR:
+                    if (command instanceof HSBType) {
+                        thirdColor = (HSBType) command;
+                    } else if (command instanceof PercentType) {
+                        thirdColor = new HSBType(thirdColor.getHue(), thirdColor.getSaturation(),
+                                ((PercentType) command));
+                    }
+                    localApi.setTertiaryColor(thirdColor, config.segmentIndex);
+                    return;
+                case CHANNEL_PALETTES:
+                    localApi.setPalette(command.toString(), config.segmentIndex);
+                    break;
+                case CHANNEL_FX:
+                    localApi.setEffect(command.toString(), config.segmentIndex);
+                    break;
+                case CHANNEL_SPEED:
+                    localApi.setFxSpeed((PercentType) command, config.segmentIndex);
+                    break;
+                case CHANNEL_INTENSITY:
+                    localApi.setFxIntencity((PercentType) command, config.segmentIndex);
+                    break;
+            }
+        } catch (ApiException e) {
+            logger.debug("Exception occured:{}", e.getMessage());
+        }
+    }
+
+    @Override
+    public void initialize() {
+        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);
+            }
+        }
+    }
+}
index 2ad26997e0472f677dec556c8b65b53b31b4cf86..7ee2ae86cc85461c9e732d7acee9d65e45271303 100644 (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
index 80ac393748c66788d2707659419b407b92d76e80..8e34f14670ed9ea35f1c13ccee586211f2511acc 100644 (file)
@@ -4,34 +4,21 @@
        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>
-               <category>ColorLight</category>
+       <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="masterControls" typeId="masterControls"/>
-                       <channel id="segmentBrightness" typeId="segmentBrightness"/>
-                       <channel id="primaryColor" typeId="primaryColor"/>
-                       <channel id="primaryWhite" typeId="primaryWhite"/>
-                       <channel id="secondaryColor" typeId="secondaryColor"/>
-                       <channel id="secondaryWhite" typeId="secondaryWhite"/>
-                       <channel id="tertiaryColor" typeId="tertiaryColor"/>
-                       <channel id="tertiaryWhite" typeId="tertiaryWhite"/>
-                       <channel id="presets" typeId="presets"/>
+                       <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="palettes" typeId="palettes"/>
-                       <channel id="fx" typeId="fx"/>
-                       <channel id="speed" typeId="speed"/>
-                       <channel id="intensity" typeId="intensity"/>
-                       <channel id="mirror" typeId="mirror"/>
-                       <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="sleepMode" typeId="sleepMode"/>
+                       <channel id="sleepDuration" typeId="sleepDuration"/>
+                       <channel id="sleepTargetBrightness" typeId="sleepTargetBrightness"/>
                        <channel id="syncSend" typeId="syncSend"/>
                        <channel id="syncReceive" typeId="syncReceive"/>
                </channels>
                                <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>
+                       <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>10</default>
+                               <default>5</default>
                        </parameter>
-                       <parameter name="segmentIndex" type="integer" required="true" 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>
-                               <default>0</default>
-                       </parameter>
-                       <parameter name="saturationThreshold" type="integer" required="true" min="0" max="99">
+                       <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>
-                               <default>false</default>
+                               <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>
-                               <default>false</default>
+                               <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"/>
+                       <channel id="segmentBrightness" typeId="segmentBrightness"/>
+                       <channel id="primaryColor" typeId="primaryColor"/>
+                       <channel id="primaryWhite" typeId="primaryWhite"/>
+                       <channel id="secondaryColor" typeId="secondaryColor"/>
+                       <channel id="secondaryWhite" typeId="secondaryWhite"/>
+                       <channel id="tertiaryColor" typeId="tertiaryColor"/>
+                       <channel id="tertiaryWhite" typeId="tertiaryWhite"/>
+                       <channel id="palettes" typeId="palettes"/>
+                       <channel id="fx" typeId="fx"/>
+                       <channel id="speed" typeId="speed"/>
+                       <channel id="intensity" typeId="intensity"/>
+                       <channel id="mirror" typeId="mirror"/>
+                       <channel id="reverse" typeId="reverse"/>
+                       <channel id="grouping" typeId="grouping"/>
+                       <channel id="spacing" typeId="spacing"/>
+               </channels>
+               <config-description>
+                       <parameter name="segmentIndex" type="integer" min="0">
+                               <label>Segment Index</label>
+                               <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>
                </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>
                </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">
                <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">
                <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">
                <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">
                <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">
                <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>
                <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>