diff --git a/bundles/org.openhab.binding.wled/README.md b/bundles/org.openhab.binding.wled/README.md index 0f8f159f7bb2..46e5451e98c3 100644 --- a/bundles/org.openhab.binding.wled/README.md +++ b/bundles/org.openhab.binding.wled/README.md @@ -24,39 +24,47 @@ For additional segments, you can add them manually and set the `segmentIndex` co |-|-|-|-| | `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 -1 if you do not know what a segment is. | Y | -1 | +| `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 | Channel | Type | Description | |-|-|-| -| `masterControls` | Color | Gives you control over the WLED like it is any normal light. Tag this control for Alexa or Google/Nest to change the lights instantly to any colour, brightness or on/off state that you ask for regardless of what mode the light is in. | +| `masterControls` | Color | Gives you control over the WLED segment like it is any normal light. Tag this control for Alexa or Google/Nest to change the lights instantly to any colour, brightness or on/off state that you ask for regardless of what mode the light is in. | +| `segmentBrightness` | Dimmer | Allows you to Dim and turn the entire segment ON and OFF. | | `primaryColor` | Color | The primary colour used in FX. | -| `primaryWhite` | Dimmer | The amount of white light used in the primary colour if you have RGBW LEDs. | +| `primaryWhite` | Dimmer | The amount of white light used in the primary colour. Only available if you have RGBW LEDs. | | `secondaryColor` | Color | The secondary colour used in FX. | -| `secondaryWhite` | Dimmer | The amount of white light used in the secondary colour if you have RGBW LEDs. | +| `secondaryWhite` | Dimmer | The amount of white light used in the second colour. Only available if you have RGBW LEDs. | +| `tertiaryColor` | Color | The third colour used in FX. | +| `tertiaryWhite` | Dimmer | The amount of white light used in the third colour. Only available if you have RGBW LEDs. | | `palettes` | String | A list of colour palettes you can select from that are used in the FX. | | `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. | -| `presetCycle` | Switch | Turns ON/OFF the automatic changing from one preset to the next. | -| `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. | +| `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. | ## Rule Actions -This binding has a rule Action `savePreset(int presetNumber)` which can save the current state of the WLED string into a preset slot that you can specify. -Currently 1 to 16 are valid preset slots. +This binding has two rule Actions `savePreset(int presetNumber)` and `savePreset(int presetNumber, String presetName)` which can save the current state of the WLED string into a preset slot that you can specify. In Xtend rules, you can use the Actions like this. ``` -getActions("wled", "wled:wled:XmasTree").savePreset(5) +getActions("wled", "wled:wled:XmasTree").savePreset(5,"Flashy Preset") ``` ## Sitemap Example @@ -68,7 +76,7 @@ If you use the ADMIN>MODEL>`Create equipment from thing` feature you can use the ``` Text label="XmasLights" icon="rgb"{ Switch item=XmasTree_MasterControls - Slider item=XmasTree_MasterControls + Slider item=XmasTree_SegmentBrightness Colorpicker item=XmasTree_MasterControls Switch item=XmasTree_SleepTimer Colorpicker item=XmasTree_PrimaryColor @@ -77,9 +85,7 @@ If you use the ADMIN>MODEL>`Create equipment from thing` feature you can use the Selection item=XmasTree_Palettes Selection item=XmasTree_Presets Default item=XmasTree_FXSpeed - Default item=XmasTree_FXIntensity - Default item=XmasTree_PresetCycle - Selection item=XmasTree_PresetDuration mappings=[2 ='2 seconds', 10='10 seconds', 30='30 seconds', 60='60 seconds'] + Default item=XmasTree_FXIntensity Selection item=XmasTree_TransformTime mappings=[0='0 seconds', 2 ='2 seconds', 10='10 seconds', 30='30 seconds', 60='60 seconds'] } diff --git a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedActions.java b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedActions.java index f1e302ca1e57..8c2c53712d1f 100644 --- a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedActions.java +++ b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedActions.java @@ -47,15 +47,30 @@ public void setThingHandler(@Nullable ThingHandler handler) { @RuleAction(label = "save state to preset", description = "Save a WLED state to a preset slot") public void savePreset( @ActionInput(name = "presetNumber", label = "Preset Slot", description = "Number for the preset slot you wish to use") int presetNumber) { + savePreset(presetNumber, ""); + } + + public static void savePreset(@Nullable ThingActions actions, int presetNumber) { + if (actions instanceof WLedActions) { + ((WLedActions) actions).savePreset(presetNumber, ""); + } else { + throw new IllegalArgumentException("Instance is not a WLED class."); + } + } + + @RuleAction(label = "save state to preset", description = "Save a WLED state to a preset slot") + 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; - if (presetNumber > 0 && localHandler != null) { - localHandler.savePreset(presetNumber); + if (localHandler != null) { + localHandler.savePreset(presetNumber, presetName); } } - public static void savePreset(@Nullable ThingActions actions, int presetNumber) { + public static void savePreset(@Nullable ThingActions actions, int presetNumber, String presetName) { if (actions instanceof WLedActions) { - ((WLedActions) actions).savePreset(presetNumber); + ((WLedActions) actions).savePreset(presetNumber, presetName); } else { throw new IllegalArgumentException("Instance is not a WLED class."); } diff --git a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedBindingConstants.java b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedBindingConstants.java index 72106cea5616..62883bcb5098 100644 --- a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedBindingConstants.java +++ b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedBindingConstants.java @@ -43,18 +43,27 @@ public class WLedBindingConstants { // Channels public static final String CHANNEL_MASTER_CONTROLS = "masterControls"; + public static final String CHANNEL_SEGMENT_BRIGHTNESS = "segmentBrightness"; public static final String CHANNEL_PRIMARY_COLOR = "primaryColor"; public static final String CHANNEL_SECONDARY_COLOR = "secondaryColor"; + public static final String CHANNEL_THIRD_COLOR = "tertiaryColor"; public static final String CHANNEL_PRIMARY_WHITE = "primaryWhite"; public static final String CHANNEL_SECONDARY_WHITE = "secondaryWhite"; + public static final String CHANNEL_THIRD_WHITE = "tertiaryWhite"; public static final String CHANNEL_PALETTES = "palettes"; public static final String CHANNEL_PRESETS = "presets"; + public static final String CHANNEL_PLAYLISTS = "playlists"; public static final String CHANNEL_PRESET_DURATION = "presetDuration"; public static final String CHANNEL_TRANS_TIME = "transformTime"; public static final String CHANNEL_PRESET_CYCLE = "presetCycle"; public static final String CHANNEL_FX = "fx"; public static final String CHANNEL_SPEED = "speed"; public static final String CHANNEL_INTENSITY = "intensity"; + public static final String CHANNEL_MIRROR = "mirror"; + public static final String CHANNEL_REVERSE = "reverse"; + public static final String CHANNEL_GROUPING = "grouping"; + 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_SYNC_SEND = "syncSend"; public static final String CHANNEL_SYNC_RECEIVE = "syncReceive"; diff --git a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedDiscoveryService.java b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedDiscoveryService.java index f9a3185eb20b..34114b1f32e3 100644 --- a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedDiscoveryService.java +++ b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedDiscoveryService.java @@ -106,7 +106,7 @@ private String sendGetRequest(String address, String url) { 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, -1).withLabel(label).withProperties(properties) + .withProperty(CONFIG_SEGMENT_INDEX, 0).withLabel(label).withProperties(properties) .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build(); } 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 index d61d890f540f..7088441bcbc1 100644 --- 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 @@ -15,41 +15,36 @@ import static org.openhab.binding.wled.internal.WLedBindingConstants.*; import java.math.BigDecimal; -import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.List; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpMethod; +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.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.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.openhab.core.types.StateOption; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,386 +58,244 @@ @NonNullByDefault public class WLedHandler extends BaseThingHandler { private final Logger logger = LoggerFactory.getLogger(getClass()); - private final HttpClient httpClient; - private final WledDynamicStateDescriptionProvider stateDescriptionProvider; + public final WledDynamicStateDescriptionProvider stateDescriptionProvider; + private WledApiFactory apiFactory; + private @Nullable WledApi api; private @Nullable ScheduledFuture pollingFuture = null; - private BigDecimal hue65535 = BigDecimal.ZERO; - private BigDecimal saturation255 = BigDecimal.ZERO; private BigDecimal masterBrightness255 = BigDecimal.ZERO; + public boolean hasWhite = false; private HSBType primaryColor = new HSBType(); - private BigDecimal primaryWhite = BigDecimal.ZERO; private HSBType secondaryColor = new HSBType(); - private BigDecimal secondaryWhite = BigDecimal.ZERO; - private boolean hasWhite = false; - private WLedConfiguration config = new WLedConfiguration(); + private HSBType thirdColor = new HSBType(); + public WLedConfiguration config = new WLedConfiguration(); - public WLedHandler(Thing thing, HttpClient httpClient, + public WLedHandler(Thing thing, WledApiFactory apiFactory, WledDynamicStateDescriptionProvider stateDescriptionProvider) { super(thing); - this.httpClient = httpClient; + this.apiFactory = apiFactory; this.stateDescriptionProvider = stateDescriptionProvider; } - private void sendGetRequest(String url) { - Request request; - if (url.contains("json") || config.segmentIndex == -1) { - request = httpClient.newRequest(config.address + url); - } else { - request = httpClient.newRequest(config.address + url + "&SM=" + config.segmentIndex); - } - request.timeout(3, TimeUnit.SECONDS); - request.method(HttpMethod.GET); - request.header(HttpHeader.ACCEPT_ENCODING, "gzip"); - logger.trace("Sending WLED GET:{}", url); - String errorReason = ""; - try { - ContentResponse contentResponse = request.send(); - if (contentResponse.getStatus() == 200) { - processState(contentResponse.getContentAsString()); - return; - } else { - errorReason = String.format("WLED request failed with %d: %s", contentResponse.getStatus(), - contentResponse.getReason()); - } - } catch (TimeoutException e) { - errorReason = "TimeoutException: WLED was not reachable on your network"; - } catch (ExecutionException e) { - errorReason = String.format("ExecutionException: %s", e.getMessage()); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - errorReason = String.format("InterruptedException: %s", e.getMessage()); - } - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorReason); - } - - private HSBType parseToHSBType(String message, String element) { - int startIndex = message.indexOf(element); - if (startIndex == -1) { - return new HSBType(); - } - int endIndex = message.indexOf("<", startIndex + element.length()); - int r = 0, g = 0, b = 0; - try { - r = Integer.parseInt(message.substring(startIndex + element.length(), endIndex)); - // look for second element - startIndex = message.indexOf(element, endIndex); - if (startIndex == -1) { - return new HSBType(); - } - endIndex = message.indexOf("<", startIndex + element.length()); - g = Integer.parseInt(message.substring(startIndex + element.length(), endIndex)); - // look for third element called - startIndex = message.indexOf(element, endIndex); - if (startIndex == -1) { - return new HSBType(); - } - endIndex = message.indexOf("<", startIndex + element.length()); - b = Integer.parseInt(message.substring(startIndex + element.length(), endIndex)); - } catch (NumberFormatException e) { - logger.warn("NumberFormatException when parsing the WLED color fields:{}", e.getMessage()); - } - return HSBType.fromRGB(r, g, b); - } - - private void parseColours(String message) { - primaryColor = parseToHSBType(message, ""); - updateState(CHANNEL_PRIMARY_COLOR, primaryColor); - secondaryColor = parseToHSBType(message, ""); - updateState(CHANNEL_SECONDARY_COLOR, secondaryColor); - try { - primaryWhite = new BigDecimal(WLedHelper.getValue(message, "", "<")); - if (primaryWhite.intValue() > -1) { - hasWhite = true; - updateState(CHANNEL_PRIMARY_WHITE, - new PercentType(primaryWhite.divide(BIG_DECIMAL_2_55, RoundingMode.HALF_UP))); - secondaryWhite = new BigDecimal(WLedHelper.getValue(message, "", "<")); - updateState(CHANNEL_SECONDARY_WHITE, - new PercentType(secondaryWhite.divide(BIG_DECIMAL_2_55, RoundingMode.HALF_UP))); - } - } catch (IllegalArgumentException e) { - logger.warn("IllegalArgumentException when parsing the WLED colour and white fields:{}", e.getMessage()); - } - } - - /** - * - * This function should prevent the need to keep updating the binding as more FX and Palettes are added to the - * firmware. - */ - private void scrapeChannelOptions(String message) { - List fxOptions = new ArrayList<>(); - List palleteOptions = new ArrayList<>(); - int counter = 0; - for (String value : WLedHelper.getValue(message, "\"effects\":[", "]").replace("\"", "").split(",")) { - fxOptions.add(new StateOption(Integer.toString(counter++), value)); - } - stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_FX), fxOptions); - counter = 0; - for (String value : (WLedHelper.getValue(message, "\"palettes\":[", "]").replace("\"", "")).split(",")) { - palleteOptions.add(new StateOption(Integer.toString(counter++), value)); - } - stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_PALETTES), palleteOptions); - } - - private void processState(String message) { - logger.trace("WLED states are:{}", message); - if (thing.getStatus() != ThingStatus.ONLINE) { - updateStatus(ThingStatus.ONLINE); - sendGetRequest("/json"); // fetch FX and Pallete names - } - if (message.contains("\"effects\":[")) {// JSON API reply - scrapeChannelOptions(message); - return; - } - if (message.contains("0")) { - updateState(CHANNEL_MASTER_CONTROLS, OnOffType.OFF); - } else { - masterBrightness255 = new BigDecimal(WLedHelper.getValue(message, "", "<")); - updateState(CHANNEL_MASTER_CONTROLS, - new PercentType(masterBrightness255.divide(BIG_DECIMAL_2_55, RoundingMode.HALF_UP))); - } - if (message.contains("0")) { - updateState(CHANNEL_INTENSITY, OnOffType.OFF); - } else { - BigDecimal bigTemp = new BigDecimal(WLedHelper.getValue(message, "", "<")).divide(BIG_DECIMAL_2_55, - RoundingMode.HALF_UP); - updateState(CHANNEL_INTENSITY, new PercentType(bigTemp)); - } - if (message.contains("1")) { - updateState(CHANNEL_PRESET_CYCLE, OnOffType.ON); - } else { - updateState(CHANNEL_PRESET_CYCLE, OnOffType.OFF); - } - if (message.contains("1")) { - updateState(CHANNEL_SLEEP, OnOffType.ON); - } else { - updateState(CHANNEL_SLEEP, OnOffType.OFF); - } - if (message.contains("1")) { - updateState(CHANNEL_SYNC_SEND, OnOffType.ON); - } else { - updateState(CHANNEL_SYNC_SEND, OnOffType.OFF); - } - if (message.contains("1")) { - updateState(CHANNEL_SYNC_RECEIVE, OnOffType.ON); - } else { - updateState(CHANNEL_SYNC_RECEIVE, OnOffType.OFF); - } - if (message.contains("")) { - updateState(CHANNEL_FX, new StringType(WLedHelper.getValue(message, "", "<"))); - } - if (message.contains("")) { - BigDecimal bigTemp = new BigDecimal(WLedHelper.getValue(message, "", "<")).divide(BIG_DECIMAL_2_55, - RoundingMode.HALF_UP); - updateState(CHANNEL_SPEED, new PercentType(bigTemp)); - } - if (message.contains("")) { - updateState(CHANNEL_PALETTES, new StringType(WLedHelper.getValue(message, "", "<"))); - } - parseColours(message); - } - - private void sendWhite() { - if (hasWhite) { - sendGetRequest("/win&TT=1000&FX=0&CY=0&CL=hFF000000" + "&A=" + masterBrightness255); - } else { - sendGetRequest("/win&TT=1000&FX=0&CY=0&CL=hFFFFFF" + "&A=" + masterBrightness255); - } - } - - /** - * - * @param hsb - * @return WLED needs the letter h followed by 2 digit HEX code for RRGGBB - */ - private String createColorHex(HSBType hsb) { - return String.format("h%06X", hsb.getRGB() & 0x00FFFFFF); - } - @Override public void handleCommand(ChannelUID channelUID, Command command) { + WledApi localApi = api; + if (localApi == null) { + return; + } BigDecimal bigTemp; - PercentType localPercentType; if (command instanceof RefreshType) { - switch (channelUID.getId()) { - case CHANNEL_MASTER_CONTROLS: - sendGetRequest("/win"); - } return;// no need to check for refresh below } logger.debug("command {} sent to {}", command, channelUID.getId()); - switch (channelUID.getId()) { - case CHANNEL_SYNC_SEND: - if (OnOffType.OFF.equals(command)) { - sendGetRequest("/win&NS=0"); - } else { - sendGetRequest("/win&NS=1"); - } - break; - case CHANNEL_SYNC_RECEIVE: - if (OnOffType.OFF.equals(command)) { - sendGetRequest("/win&NR=0"); - } else { - sendGetRequest("/win&NR=1"); - } - break; - case CHANNEL_PRIMARY_WHITE: - if (command instanceof PercentType) { - sendGetRequest("/win&W=" + ((PercentType) command).toBigDecimal().multiply(BIG_DECIMAL_2_55)); - } - break; - case CHANNEL_SECONDARY_WHITE: - if (command instanceof PercentType) { - sendGetRequest("/win&W2=" + ((PercentType) command).toBigDecimal().multiply(BIG_DECIMAL_2_55)); - } - break; - case CHANNEL_MASTER_CONTROLS: - if (command instanceof OnOffType) { - if (OnOffType.OFF.equals(command)) { - sendGetRequest("/win&TT=250&T=0"); - } else { - sendGetRequest("/win&TT=1000&T=1"); + 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)); } - } else if (command instanceof IncreaseDecreaseType) { - if (IncreaseDecreaseType.INCREASE.equals(command)) { - if (masterBrightness255.intValue() < 240) { - sendGetRequest("/win&TT=1000&A=~15"); // 255 divided by 15 = 17 different levels + break; + case CHANNEL_MASTER_CONTROLS: + if (command instanceof OnOffType) { + 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 { - sendGetRequest("/win&TT=1000&A=255"); + 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; } - } else { - if (masterBrightness255.intValue() > 15) { - sendGetRequest("/win&TT=1000&A=~-15"); + 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 { - sendGetRequest("/win&TT=1000&A=0"); + localApi.setMasterHSB((HSBType) command, config.segmentIndex); } + } else if (command instanceof PercentType) { + localApi.setMasterBrightness((PercentType) command, config.segmentIndex); } - } else if (command instanceof HSBType) { - if (PercentType.ZERO.equals(((HSBType) command).getBrightness())) { - sendGetRequest("/win&TT=500&T=0"); + 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)); } - primaryColor = (HSBType) command; - hue65535 = primaryColor.getHue().toBigDecimal().multiply(BIG_DECIMAL_182_04); - saturation255 = primaryColor.getSaturation().toBigDecimal().multiply(BIG_DECIMAL_2_55); - masterBrightness255 = primaryColor.getBrightness().toBigDecimal().multiply(BIG_DECIMAL_2_55); - if (primaryColor.getSaturation().intValue() < config.saturationThreshold) { - sendWhite(); - } else if (primaryColor.getSaturation().intValue() == 32 && primaryColor.getHue().intValue() == 36 - && hasWhite) { - // Google sends this when it wants white - sendWhite(); - } else { - if (config.segmentIndex == -1) { - sendGetRequest("/win&TT=1000&FX=0&CY=0&HU=" + hue65535 + "&SA=" + saturation255 + "&A=" - + masterBrightness255); - } else { - sendGetRequest("/win&TT=1000&FX=0&CY=0&CL=" + createColorHex(primaryColor) + "&A=" - + masterBrightness255); + 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()); } } - } else if (command instanceof PercentType) { - masterBrightness255 = ((PercentType) command).toBigDecimal().multiply(BIG_DECIMAL_2_55); - sendGetRequest("/win&TT=1000&A=" + masterBrightness255); - } - return; - case CHANNEL_PRIMARY_COLOR: - if (command instanceof HSBType) { - primaryColor = (HSBType) command; - sendGetRequest("/win&CL=" + createColorHex(primaryColor)); - } else if (command instanceof PercentType) { - primaryColor = new HSBType(primaryColor.getHue(), primaryColor.getSaturation(), - ((PercentType) command)); - sendGetRequest("/win&CL=" + createColorHex(primaryColor)); - } - return; - case CHANNEL_SECONDARY_COLOR: - if (command instanceof HSBType) { - secondaryColor = (HSBType) command; - sendGetRequest("/win&C2=" + createColorHex(secondaryColor)); - } else if (command instanceof PercentType) { - secondaryColor = new HSBType(secondaryColor.getHue(), secondaryColor.getSaturation(), - ((PercentType) command)); - sendGetRequest("/win&C2=" + createColorHex(secondaryColor)); - } - return; - case CHANNEL_PALETTES: - sendGetRequest("/win&FP=" + command); - break; - case CHANNEL_FX: - sendGetRequest("/win&FX=" + command); - break; - case CHANNEL_SPEED: - localPercentType = ((State) command).as(PercentType.class); - if (localPercentType != null) { - bigTemp = localPercentType.toBigDecimal().multiply(BIG_DECIMAL_2_55); - sendGetRequest("/win&SX=" + bigTemp); - } - break; - case CHANNEL_INTENSITY: - localPercentType = ((State) command).as(PercentType.class); - if (localPercentType != null) { - bigTemp = localPercentType.toBigDecimal().multiply(BIG_DECIMAL_2_55); - sendGetRequest("/win&IX=" + bigTemp); - } - break; - case CHANNEL_SLEEP: - if (OnOffType.ON.equals(command)) { - sendGetRequest("/win&ND"); - } else { - sendGetRequest("/win&NL=0"); - } - break; - case CHANNEL_PRESETS: - sendGetRequest("/win&PL=" + command); - break; - case CHANNEL_PRESET_DURATION: - if (command instanceof QuantityType) { - QuantityType seconds = ((QuantityType) command).toUnit(Units.SECOND); - if (seconds != null) { - bigTemp = new BigDecimal(seconds.intValue()).multiply(new BigDecimal(1000)); - 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_TRANS_TIME: - if (command instanceof QuantityType) { - QuantityType seconds = ((QuantityType) command).toUnit(Units.SECOND); - if (seconds != null) { - bigTemp = new BigDecimal(seconds.intValue()).multiply(new BigDecimal(1000)); - sendGetRequest("/win&TT=" + 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; - case CHANNEL_PRESET_CYCLE: - if (OnOffType.ON.equals(command)) { - sendGetRequest("/win&CY=1"); - } else { - sendGetRequest("/win&CY=0"); - } - break; + break; + } + } catch (ApiException e) { + logger.debug("Exception occured:{}", e.getMessage()); } } - public void savePreset(int presetIndex) { - if (presetIndex > 16) { - logger.warn("Presets above 16 do not exist, and the action sent {}", presetIndex); - return; + public void savePreset(int position, String presetName) { + try { + if (api != null) { + api.savePreset(position, presetName); + } + } catch (ApiException e) { + } + } + + public void removeChannels(ArrayList removeChannels) { + if (!removeChannels.isEmpty()) { + ThingBuilder thingBuilder = editThing(); + thingBuilder.withoutChannels(removeChannels); + updateThing(thingBuilder.build()); } - sendGetRequest("/win&PS=" + presetIndex); } - private void pollLED() { - sendGetRequest("/win"); + 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::pollLED, 1, config.pollTime, TimeUnit.SECONDS); + pollingFuture = scheduler.scheduleWithFixedDelay(this::pollState, 0, config.pollTime, TimeUnit.SECONDS); } @Override @@ -450,6 +303,7 @@ public void dispose() { Future future = pollingFuture; if (future != null) { future.cancel(true); + pollingFuture = null; } } diff --git a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedHandlerFactory.java b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedHandlerFactory.java index 52d60c68e677..9c41546fd01f 100644 --- a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedHandlerFactory.java +++ b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedHandlerFactory.java @@ -16,8 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; -import org.openhab.core.io.net.http.HttpClientFactory; +import org.openhab.binding.wled.internal.api.WledApiFactory; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.binding.BaseThingHandlerFactory; @@ -36,29 +35,26 @@ @NonNullByDefault @Component(configurationPid = "binding.wled", service = ThingHandlerFactory.class) public class WLedHandlerFactory extends BaseThingHandlerFactory { - private final HttpClient httpClient; private final WledDynamicStateDescriptionProvider stateDescriptionProvider; + private final WledApiFactory apiFactory; @Activate - public WLedHandlerFactory(@Reference HttpClientFactory httpClientFactory, + public WLedHandlerFactory(@Reference WledApiFactory apiFactory, final @Reference WledDynamicStateDescriptionProvider stateDescriptionProvider) { - this.httpClient = httpClientFactory.getCommonHttpClient(); + this.apiFactory = apiFactory; this.stateDescriptionProvider = stateDescriptionProvider; } @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { - if (SUPPORTED_THING_TYPES.contains(thingTypeUID)) { - return true; - } - return false; + return SUPPORTED_THING_TYPES.contains(thingTypeUID); } @Override protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (SUPPORTED_THING_TYPES.contains(thingTypeUID)) { - return new WLedHandler(thing, httpClient, stateDescriptionProvider); + return new WLedHandler(thing, apiFactory, stateDescriptionProvider); } return null; } diff --git a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedHelper.java b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedHelper.java index a50a08e32fac..cf36cb6a757e 100644 --- a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedHelper.java +++ b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WLedHelper.java @@ -12,10 +12,13 @@ */ package org.openhab.binding.wled.internal; -import java.util.LinkedList; +import java.math.BigDecimal; +import java.util.Arrays; import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.library.types.HSBType; +import org.openhab.core.library.types.PercentType; /** * The {@link WLedHelper} Provides helper classes that are used from multiple classes in the binding. @@ -24,6 +27,28 @@ */ @NonNullByDefault public class WLedHelper { + public static HSBType parseToHSBType(String message) { + // example message rgb in array brackets [255.0, 255.0, 255.0] + List colors = Arrays.asList(message.replaceAll("\\[|\\]", "").split("\\s*,\\s*")); + try { + int r = new BigDecimal(colors.get(0)).intValue(); + int g = new BigDecimal(colors.get(1)).intValue(); + int b = new BigDecimal(colors.get(2)).intValue(); + return HSBType.fromRGB(r, g, b); + } catch (NumberFormatException e) { + return new HSBType(); + } + } + + public static PercentType parseWhitePercent(String message) { + // example message rgb in array brackets [255.0, 255.0, 255.0, 255.0] + List colors = Arrays.asList(message.replaceAll("\\[|\\]", "").split("\\s*,\\s*")); + try { + return new PercentType(new BigDecimal(colors.get(2))); + } catch (IllegalArgumentException e) { + return new PercentType(); + } + } /** * @return A string that starts after finding the element and terminates when it finds the first occurrence of the @@ -40,26 +65,4 @@ static String getValue(String message, String element, String end) { } return ""; } - - /** - * @return A List that holds the values from a heading/element that re-occurs in a message multiple times. - * - */ - static List listOfResults(String message, String element, String end) { - List results = new LinkedList<>(); - String temp = ""; - for (int startLookingFromIndex = 0; startLookingFromIndex != -1;) { - startLookingFromIndex = message.indexOf(element, startLookingFromIndex); - if (startLookingFromIndex >= 0) { - temp = getValue(message.substring(startLookingFromIndex), element, end); - if (!temp.isEmpty()) { - results.add(temp); - } else { - return results;// end string must not exist so stop looking. - } - startLookingFromIndex += temp.length(); - } - } - return results; - } } diff --git a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WledState.java b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WledState.java new file mode 100644 index 000000000000..dfec00167857 --- /dev/null +++ b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/WledState.java @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.wled.internal; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.Gson; + +/** + * The {@link WledState} class holds the state and replies for a WLED device. + * + * @author Matthew Skinner - Initial contribution + */ +@NonNullByDefault +public class WledState { + protected final Gson gson = new Gson(); + public JsonResponse jsonResponse = new JsonResponse(); + public StateResponse stateResponse = new StateResponse(); + public InfoResponse infoResponse = new InfoResponse(); + public LedInfo ledInfo = new LedInfo(); + public NightLightState nightLightState = new NightLightState(); + public UdpnState udpnState = new UdpnState(); + public SegmentState segmentState = new SegmentState(); + public PresetState[] presetState = new PresetState[1]; + + public class JsonResponse { + public List effects = new ArrayList<>(); + public List palettes = new ArrayList<>(); + } + + public void unpackJsonObjects() { + @Nullable + NightLightState localNightLightState = gson.fromJson(stateResponse.nl.toString(), NightLightState.class); + if (localNightLightState != null) { + nightLightState = localNightLightState; + } + + @Nullable + UdpnState localUdpnState = gson.fromJson(stateResponse.udpn.toString(), UdpnState.class); + if (localUdpnState != null) { + udpnState = localUdpnState; + } + } + + public class StateResponse { + public boolean on = true; + public Object nl = "{}"; + public Object udpn = "{}"; + public SegmentState[] seg = new SegmentState[1]; + public int bri = 0; + public int transition = 7; + public int ps = -1; + public int pss = 0; + public int pl = -1; + public int lor = 0; + } + + public class UdpnState { + public boolean send = false; + public boolean recv = false; + } + + public class SegmentState { + public int id = 0; + public int start = 0; + public int stop = 0; + public int len = 0; + public int grp = 0; + public int spc = 0; + public boolean on = true; + public int bri = 0; + public Object[] col = new Object[1]; + public int fx = 0; + public int sx = 0; + public int ix = 0; + public int pal = 0; + public boolean sel = true; + public boolean rev = false; + public boolean mi = false; + } + + public class NightLightState { + public boolean on = true; + public int dur = 0; + public int mode = 0; + public int tbri = 0; + public int rem = 0; + } + + public class InfoResponse { + public String ver = "00000"; + public String mac = ""; + public Object leds = "{}"; + } + + public class LedInfo { + public boolean rgbw = false; + } + + public class PresetState { + public String n = "";// Name of preset + public int bri = 0;// brightness in 255, 0 means it is a playlist as bri was not defined + } +} diff --git a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/ApiException.java b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/ApiException.java new file mode 100644 index 000000000000..602afb8596d8 --- /dev/null +++ b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/ApiException.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.wled.internal.api; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ApiException} will be thrown whenever the wled API can not successfully communicate with the device. + * + * @author Matthew Skinner - Initial contribution + */ + +@NonNullByDefault +public class ApiException extends Exception { + /** + * Serial ID of this error class. + */ + private static final long serialVersionUID = 1238256795216449L; + + /** + * Basic constructor allowing the storing of a single message. + * + * @param message Descriptive message about the error. + */ + public ApiException(String message) { + super(message); + } + + public ApiException(String message, Throwable e) { + super(message, e); + } +} diff --git a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApi.java b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApi.java new file mode 100644 index 000000000000..658b6f8a74ae --- /dev/null +++ b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApi.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.wled.internal.api; + +import java.math.BigDecimal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.library.types.HSBType; +import org.openhab.core.library.types.PercentType; + +/** + * The {@link WledApi} is the JSON API methods that can be extended for different firmware versions. + * + * @author Matthew Skinner - Initial contribution + */ +@NonNullByDefault +public interface WledApi { + public abstract void update() throws ApiException; + + public abstract void initialize() throws ApiException; + + public abstract int getFirmwareVersion() throws ApiException; + + public abstract String sendGetRequest(String string) throws ApiException; + + /** + * Turns on/off ALL segments + */ + public abstract void setGlobalOn(boolean bool) throws ApiException; + + /** + * Turns on/off just THIS segment + */ + public abstract void setMasterOn(boolean bool, int segmentIndex) throws ApiException; + + /** + * Sets the brightness of ALL segments + */ + public abstract void setGlobalBrightness(PercentType percent) throws ApiException; + + /** + * Sets the brightness of just THIS segment + */ + public abstract void setMasterBrightness(PercentType percent, int segmentIndex) throws ApiException; + + /** + * Stops any running FX and instantly changes the segment to the desired colour + */ + public abstract void setMasterHSB(HSBType hsbType, int segmentIndex) throws ApiException; + + public abstract void setEffect(String string, int segmentIndex) throws ApiException; + + public abstract void setPreset(String string) throws ApiException; + + public abstract void setPalette(String string, int segmentIndex) throws ApiException; + + public abstract void setFxIntencity(PercentType percentType, int segmentIndex) throws ApiException; + + public abstract void setFxSpeed(PercentType percentType, int segmentIndex) throws ApiException; + + public abstract void setSleep(boolean bool) throws ApiException; + + public abstract void setUdpSend(boolean bool) throws ApiException; + + public abstract void setUdpRecieve(boolean bool) throws ApiException; + + public abstract void setTransitionTime(BigDecimal time) throws ApiException; + + public abstract void setPresetCycle(boolean bool) throws ApiException; + + public abstract void setPrimaryColor(HSBType hsbType, int segmentIndex) throws ApiException; + + public abstract void setSecondaryColor(HSBType hsbType, int segmentIndex) throws ApiException; + + public abstract void setTertiaryColor(HSBType hsbType, int segmentIndex) throws ApiException; + + public abstract void setWhiteOnly(PercentType percentType, int segmentIndex) throws ApiException; + + public abstract void setMirror(boolean bool, int segmentIndex) throws ApiException; + + public abstract void setReverse(boolean bool, int segmentIndex) throws ApiException; + + public abstract void setLiveOverride(String value) throws ApiException; + + public abstract void setGrouping(int value, int segmentIndex) throws ApiException; + + public abstract void setSpacing(int value, int segmentIndex) throws ApiException; + + /** + * Saves a preset to the position number with the supplied name. If the supplied name is an empty String then the + * name 'Preset x' will be used by default using the position number given. + * + */ + public abstract void savePreset(int position, String presetName) throws ApiException; +} diff --git a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApiFactory.java b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApiFactory.java new file mode 100644 index 000000000000..f71cac034443 --- /dev/null +++ b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApiFactory.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.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.core.io.net.http.HttpClientFactory; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link WledApiFactory} is responsible for creating an instance of the API that is optimized for different + * firmware versions. + * + * @author Matthew Skinner - Initial contribution + */ +@Component(service = WledApiFactory.class) +@NonNullByDefault +public class WledApiFactory { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final HttpClient httpClient; + + @Activate + public WledApiFactory(@Reference HttpClientFactory httpClientFactory) { + this.httpClient = httpClientFactory.getCommonHttpClient(); + } + + public WledApi getApi(WLedHandler handler) throws ApiException { + WledApi lowestSupportedApi = new WledApiV084(handler, httpClient); + int version = lowestSupportedApi.getFirmwareVersion(); + logger.debug("Treating firmware as int:{}", version); + if (version >= 130) { + return new WledApiV0130(handler, httpClient); + } else if (version >= 110) { + return new WledApiV0110(handler, httpClient); + } else if (version >= 100) { + return new WledApiV084(handler, httpClient); + } + logger.warn("Your WLED firmware is very old, upgrade to at least 0.10.0"); + return lowestSupportedApi; + } +} diff --git a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApiV0110.java b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApiV0110.java new file mode 100644 index 000000000000..8c6444305c57 --- /dev/null +++ b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApiV0110.java @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.wled.internal.api; + +import static org.openhab.binding.wled.internal.WLedBindingConstants.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; +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.core.thing.ChannelUID; +import org.openhab.core.types.StateOption; + +import com.google.gson.JsonElement; +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 + * as newer firmwares come out with breaking changes, extend this class into a newer firmware version class. + * + * @author Matthew Skinner - Initial contribution + */ +@NonNullByDefault +public class WledApiV0110 extends WledApiV084 { + + public WledApiV0110(WLedHandler handler, HttpClient httpClient) { + super(handler, httpClient); + } + + @Override + public void initialize() throws ApiException { + super.initialize(); + getPresets(); + } + + protected void getPresets() throws JsonSyntaxException, ApiException { + List presetsOptions = new ArrayList<>(); + List playlistsOptions = new ArrayList<>(); + JsonObject obj = gson.fromJson(sendGetRequest("/presets.json"), JsonObject.class); + if (obj == null) { + return; + } + Set> set = obj.entrySet(); + int counter = 0; + for (Entry presetEntry : set) { + logger.trace("Preset:{} json:{}", presetEntry.getKey(), presetEntry.getValue()); + PresetState preset = gson.fromJson(presetEntry.getValue(), PresetState.class); + if (preset != null && counter > 0) { + if (preset.bri == 0) { + playlistsOptions.add(new StateOption(Integer.toString(counter), preset.n)); + } else { + presetsOptions.add(new StateOption(Integer.toString(counter), preset.n)); + } + } + counter++; + } + handler.stateDescriptionProvider.setStateOptions(new ChannelUID(handler.getThing().getUID(), CHANNEL_PRESETS), + presetsOptions); + handler.stateDescriptionProvider.setStateOptions(new ChannelUID(handler.getThing().getUID(), CHANNEL_PLAYLISTS), + playlistsOptions); + } + + @Override + public void savePreset(int position, String presetName) throws ApiException { + if (position < 1) { + logger.warn("Preset position {} is not supported in this firmware version", position); + return; + } + + String name = presetName; + if (name.isEmpty()) { + name = "Preset " + position; + } + postState("{\"psave\":" + position + ",\"n\":\"" + name + "\",\"ib\":true,\"sb\":true}"); + } +} diff --git a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApiV0130.java b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApiV0130.java new file mode 100644 index 000000000000..e69bbc823365 --- /dev/null +++ b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApiV0130.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.wled.internal.api; + +import static org.openhab.binding.wled.internal.WLedBindingConstants.*; + +import java.util.ArrayList; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.wled.internal.WLedHandler; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.Channel; + +/** + * The {@link WledApiV0130} is the json Api methods for firmware version 0.13.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 + */ +@NonNullByDefault +public class WledApiV0130 extends WledApiV0110 { + + public WledApiV0130(WLedHandler handler, HttpClient httpClient) { + super(handler, httpClient); + } + + @Override + public void initialize() throws ApiException { + super.initialize(); + ArrayList removeChannels = new ArrayList<>(); + // This version of firmware removed these channels + Channel channel = handler.getThing().getChannel(CHANNEL_PRESET_DURATION); + if (channel != null) { + removeChannels.add(channel); + } + channel = handler.getThing().getChannel(CHANNEL_PRESET_CYCLE); + if (channel != null) { + removeChannels.add(channel); + } + handler.removeChannels(removeChannels); + } + + @Override + protected void processState() throws ApiException { + super.processState(); + handler.update(CHANNEL_PLAYLISTS, new StringType(Integer.toString(state.stateResponse.pl))); + } +} diff --git a/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApiV084.java b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApiV084.java new file mode 100644 index 000000000000..bd6d00ddef79 --- /dev/null +++ b/bundles/org.openhab.binding.wled/src/main/java/org/openhab/binding/wled/internal/api/WledApiV084.java @@ -0,0 +1,508 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.wled.internal.api; + +import static org.openhab.binding.wled.internal.WLedBindingConstants.*; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +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.core.library.types.DecimalType; +import org.openhab.core.library.types.HSBType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.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; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; + +/** + * The {@link WledApiV084} is the json Api methods for firmware version 0.8.4 and newer + * as newer firmwares come out with breaking changes, extend this class into a newer firmware version class. + * + * @author Matthew Skinner - Initial contribution + */ +@NonNullByDefault +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 String address; + protected WledState state = new WledState(); + private int version = 0; + + public WledApiV084(WLedHandler handler, HttpClient httpClient) { + this.handler = handler; + this.address = handler.config.address; + this.httpClient = httpClient; + } + + @Override + public void initialize() throws ApiException { + state.jsonResponse = getJson(); + getUpdatedFxList(); + getUpdatedPaletteList(); + + @Nullable + LedInfo localLedInfo = gson.fromJson(state.infoResponse.leds.toString(), LedInfo.class); + if (localLedInfo != null) { + state.ledInfo = localLedInfo; + } + + handler.hasWhite = state.ledInfo.rgbw; + ArrayList 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 + public String sendGetRequest(String url) throws ApiException { + Request request = httpClient.newRequest(address + url); + request.timeout(3, TimeUnit.SECONDS); + request.method(HttpMethod.GET); + request.header(HttpHeader.ACCEPT_ENCODING, "gzip"); + logger.trace("Sending WLED GET:{}", url); + String errorReason = ""; + try { + ContentResponse contentResponse = request.send(); + if (contentResponse.getStatus() == 200) { + return contentResponse.getContentAsString(); + } else { + errorReason = String.format("WLED request failed with %d: %s", contentResponse.getStatus(), + contentResponse.getReason()); + } + } catch (TimeoutException e) { + errorReason = "TimeoutException: WLED was not reachable on your network"; + } catch (ExecutionException e) { + errorReason = String.format("ExecutionException: %s", e.getMessage()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + errorReason = String.format("InterruptedException: %s", e.getMessage()); + } + throw new ApiException(errorReason); + } + + protected String postState(String json) throws ApiException { + return sendPostRequest("/json/state", json); + } + + protected String sendPostRequest(String url, String json) throws ApiException { + logger.debug("Sending WLED POST:{} Message:{}", url, json); + Request request = httpClient.POST(address + url); + request.timeout(3, TimeUnit.SECONDS); + request.header(HttpHeader.CONTENT_TYPE, "application/json"); + request.content(new StringContentProvider(json), "application/json"); + String errorReason = ""; + try { + ContentResponse contentResponse = request.send(); + if (contentResponse.getStatus() == 200) { + return contentResponse.getContentAsString(); + } else { + errorReason = String.format("WLED request failed with %d: %s", contentResponse.getStatus(), + contentResponse.getReason()); + } + } catch (InterruptedException e) { + errorReason = String.format("InterruptedException: %s", e.getMessage()); + } catch (TimeoutException e) { + errorReason = "TimeoutException: WLED was not reachable on your network"; + } catch (ExecutionException e) { + errorReason = String.format("ExecutionException: %s", e.getMessage()); + } + throw new ApiException(errorReason); + } + + protected void updateStateFromReply(String jsonState) { + try { + StateResponse response = gson.fromJson(jsonState, StateResponse.class); + if (response == null) { + throw new ApiException("Reply back from WLED when command was made is not valid JSON"); + } + state.stateResponse = response; + state.unpackJsonObjects(); + processState(); + } catch (JsonSyntaxException | ApiException e) { + logger.debug("Reply back when a command was sent triggered an exception:{}", jsonState); + } + } + + protected StateResponse getState() throws ApiException { + try { + String returnContent = sendGetRequest("/json/state"); + StateResponse response = gson.fromJson(returnContent, StateResponse.class); + if (response == null) { + throw new ApiException("Could not GET:/json/state"); + } + logger.trace("json/state:{}", returnContent); + return response; + } catch (JsonSyntaxException e) { + throw new ApiException("JsonSyntaxException:{}", e); + } + } + + protected InfoResponse getInfo() throws ApiException { + try { + String returnContent = sendGetRequest("/json/info"); + InfoResponse response = gson.fromJson(returnContent, InfoResponse.class); + if (response == null) { + throw new ApiException("Could not GET:/json/info"); + } + return response; + } catch (JsonSyntaxException e) { + throw new ApiException("JsonSyntaxException:{}", e); + } + } + + protected JsonResponse getJson() throws ApiException { + try { + String returnContent = sendGetRequest("/json"); + JsonResponse response = gson.fromJson(returnContent, JsonResponse.class); + if (response == null) { + throw new ApiException("Could not GET:/json"); + } + return response; + } catch (JsonSyntaxException e) { + throw new ApiException("JsonSyntaxException:{}", e); + } + } + + @Override + public void update() throws ApiException { + state.stateResponse = getState(); + state.unpackJsonObjects(); + processState(); + } + + protected void getUpdatedFxList() { + List fxOptions = new ArrayList<>(); + int counter = 0; + for (String value : state.jsonResponse.effects) { + fxOptions.add(new StateOption(Integer.toString(counter++), value)); + } + handler.stateDescriptionProvider.setStateOptions(new ChannelUID(handler.getThing().getUID(), CHANNEL_FX), + fxOptions); + } + + protected void getUpdatedPaletteList() { + List palleteOptions = new ArrayList<>(); + int counter = 0; + for (String value : state.jsonResponse.palettes) { + palleteOptions.add(new StateOption(Integer.toString(counter++), value)); + } + handler.stateDescriptionProvider.setStateOptions(new ChannelUID(handler.getThing().getUID(), CHANNEL_PALETTES), + palleteOptions); + } + + @Override + public int getFirmwareVersion() throws ApiException { + state.infoResponse = getInfo(); + String temp = state.infoResponse.ver; + logger.debug("Firmware for WLED is ver:{}", temp); + temp = temp.replaceAll("\\.", ""); + if (temp.length() > 4) { + temp = temp.substring(0, 4); + } + version = Integer.parseInt(temp); + 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_MASTER_CONTROLS, tempHSB); + 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())); + } + + if (!state.stateResponse.seg[handler.config.segmentIndex].on) { + handler.update(CHANNEL_MASTER_CONTROLS, OnOffType.OFF); + handler.update(CHANNEL_SEGMENT_BRIGHTNESS, OnOffType.OFF); + } else { + handler.update(CHANNEL_SEGMENT_BRIGHTNESS, + new PercentType(new BigDecimal(state.stateResponse.seg[handler.config.segmentIndex].bri) + .divide(BIG_DECIMAL_2_55, RoundingMode.HALF_UP))); + } + 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 { + handler.update(CHANNEL_SYNC_RECEIVE, OnOffType.OFF); + } + if (state.udpnState.send) { + handler.update(CHANNEL_SYNC_SEND, OnOffType.ON); + } 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); + } else { + handler.update(CHANNEL_REVERSE, 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)); + } + + @Override + public void setGlobalOn(boolean bool) throws ApiException { + updateStateFromReply(postState("{\"on\":" + bool + ",\"v\":true,\"tt\":2}")); + } + + @Override + public void setMasterOn(boolean bool, int segmentIndex) throws ApiException { + updateStateFromReply( + postState("{\"v\":true,\"tt\":2,\"seg\":[{\"id\":" + segmentIndex + ",\"on\":" + bool + "}]}")); + } + + @Override + public void setGlobalBrightness(PercentType percent) throws ApiException { + if (percent.equals(PercentType.ZERO)) { + updateStateFromReply(postState("{\"on\":false,\"v\":true}")); + return; + } + updateStateFromReply(postState("{\"on\":true,\"v\":true,\"tt\":2,\"bri\":" + + percent.toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "}")); + } + + @Override + public void setMasterBrightness(PercentType percent, int segmentIndex) throws ApiException { + if (percent.equals(PercentType.ZERO)) { + updateStateFromReply(postState("{\"v\":true,\"seg\":[{\"id\":" + segmentIndex + ",\"on\":false}]}")); + return; + } + updateStateFromReply(postState("{\"tt\":2,\"v\":true,\"seg\":[{\"id\":" + segmentIndex + ",\"on\":true,\"bri\":" + + percent.toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "}]}")); + } + + @Override + public void setMasterHSB(HSBType hsbType, int segmentIndex) throws ApiException { + if (hsbType.getBrightness().toBigDecimal().equals(BigDecimal.ZERO)) { + updateStateFromReply(postState("{\"tt\":2,\"v\":true,\"seg\":[{\"on\":false,\"id\":" + segmentIndex + + ",\"fx\":0,\"col\":[[" + hsbType.getRed().toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + + "," + hsbType.getGreen().toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "," + + hsbType.getBlue().toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "]]}]}")); + return; + } + updateStateFromReply(postState("{\"tt\":2,\"v\":true,\"seg\":[{\"on\":true,\"id\":" + segmentIndex + + ",\"fx\":0,\"col\":[[" + hsbType.getRed().toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "," + + hsbType.getGreen().toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "," + + hsbType.getBlue().toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "]]}]}")); + } + + @Override + public void setEffect(String string, int segmentIndex) throws ApiException { + postState("{\"seg\":[{\"id\":" + segmentIndex + ",\"fx\":" + string + "}]}"); + } + + @Override + public void setPreset(String string) throws ApiException { + updateStateFromReply(postState("{\"ps\":" + string + ",\"v\":true}")); + } + + @Override + public void setPalette(String string, int segmentIndex) throws ApiException { + postState("{\"seg\":[{\"id\":" + segmentIndex + ",\"pal\":" + string + "}]}"); + } + + @Override + public void setFxIntencity(PercentType percentType, int segmentIndex) throws ApiException { + postState("{\"seg\":[{\"id\":" + segmentIndex + ",\"ix\":" + + percentType.toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "}]}"); + } + + @Override + public void setFxSpeed(PercentType percentType, int segmentIndex) throws ApiException { + postState("{\"seg\":[{\"id\":" + segmentIndex + ",\"sx\":" + + percentType.toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "}]}"); + } + + @Override + public void setSleep(boolean bool) throws ApiException { + postState("{\"nl\":{\"on\":" + bool + "}}"); + } + + @Override + public void setUdpSend(boolean bool) throws ApiException { + postState("{\"udpn\":{\"send\":" + bool + "}}"); + } + + @Override + public void setUdpRecieve(boolean bool) throws ApiException { + postState("{\"udpn\":{\"recv\":" + bool + "}}"); + } + + @Override + public void setTransitionTime(BigDecimal time) throws ApiException { + postState("{\"transition\":" + time + "}"); + } + + @Override + public void setPresetCycle(boolean bool) throws ApiException { + if (bool) { + postState("{\"pl\":0}"); + } else { + postState("{\"pl\":-1}"); + } + } + + @Override + public void setPrimaryColor(HSBType hsbType, int segmentIndex) throws ApiException { + postState("{\"on\":true,\"seg\":[{\"id\":" + segmentIndex + ",\"col\":[[" + + hsbType.getRed().toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "," + + hsbType.getGreen().toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "," + + hsbType.getBlue().toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "],[],[]]}]}"); + } + + @Override + public void setSecondaryColor(HSBType hsbType, int segmentIndex) throws ApiException { + postState("{\"on\":true,\"seg\":[{\"id\":" + segmentIndex + ",\"col\":[[],[" + + hsbType.getRed().toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "," + + hsbType.getGreen().toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "," + + hsbType.getBlue().toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "],[]]}]}"); + } + + @Override + public void setTertiaryColor(HSBType hsbType, int segmentIndex) throws ApiException { + postState("{\"on\":true,\"seg\":[{\"id\":" + segmentIndex + ",\"col\":[[],[],[" + + hsbType.getRed().toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "," + + hsbType.getGreen().toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "," + + hsbType.getBlue().toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "]]}]}"); + } + + @Override + public void setWhiteOnly(PercentType percentType, int segmentIndex) throws ApiException { + postState("{\"seg\":[{\"on\":true,\"id\":" + segmentIndex + ",\"fx\":0,\"col\":[[0,0,0," + + percentType.toBigDecimal().multiply(BIG_DECIMAL_2_55).intValue() + "]]}]}"); + } + + @Override + public void setMirror(boolean bool, int segmentIndex) throws ApiException { + postState("{\"seg\":[{\"id\":" + segmentIndex + ",\"mi\":" + bool + "}]}"); + } + + @Override + public void setReverse(boolean bool, int segmentIndex) throws ApiException { + postState("{\"seg\":[{\"id\":" + segmentIndex + ",\"rev\":" + bool + "}]}"); + } + + @Override + public void savePreset(int position, String presetName) throws ApiException { + // named presets not supported in older firmwares, and max of 16. + if (position > 16 || position < 1) { + logger.warn("Preset position {} is not supported in this firmware version", position); + return; + } + try { + sendGetRequest("/win&PS=" + position); + } catch (ApiException e) { + logger.warn("Preset failed to save:{}", e.getMessage()); + } + } + + @Override + public void setLiveOverride(String value) throws ApiException { + postState("{\"lor\":" + value + "}"); + } + + @Override + public void setGrouping(int value, int segmentIndex) throws ApiException { + postState("{\"seg\":[{\"id\":" + segmentIndex + ",\"grp\":" + value + "}]}"); + } + + @Override + public void setSpacing(int value, int segmentIndex) throws ApiException { + postState("{\"seg\":[{\"id\":" + segmentIndex + ",\"spc\":" + value + "}]}"); + } +} diff --git a/bundles/org.openhab.binding.wled/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.wled/src/main/resources/OH-INF/thing/thing-types.xml index 4ae88dbcf4f1..1d565066ff4d 100644 --- a/bundles/org.openhab.binding.wled/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.wled/src/main/resources/OH-INF/thing/thing-types.xml @@ -10,11 +10,15 @@ ColorLight + + + + @@ -22,6 +26,11 @@ + + + + + @@ -36,11 +45,11 @@ Time in seconds of how often to fetch the state of the LEDs. 10 - + - Leave this as -1 if you are not using segments, otherwise set this to the segment index number that you + Leave this as 0 if you are not using segments, otherwise set this to the segment index number that you wish to control. - -1 + 0 @@ -62,6 +71,13 @@ + + Dimmer + + Changes the brightness of the whole segment + DimmableLight + + Color @@ -90,6 +106,20 @@ DimmableLight + + Color + + Allows you to change the third color used in FX + ColorLight + + + + Dimmer + + Changes the brightness of the third white LED + DimmableLight + + String @@ -128,6 +158,12 @@ + + String + + The currently playing play list + + Number:Time @@ -136,12 +172,55 @@ + + Number + + How many consecutive LEDs of the same segment will be grouped to the same color + + + + + Number + + How many LEDs are turned off and skipped between each group + + + + + String + + Live data override. 0 is off, 1 is override until live data ends, 2 is override until ESP reboot + + + + + + + + + Number:Time Time it takes to change/fade from one look to the next. Time - + + + + @@ -156,6 +235,18 @@ Change the intensity of the FX + + Switch + + Mirror the effect for this segment + + + + Switch + + Reverse the direction of the current segment + + Switch @@ -163,7 +254,7 @@ Time - + Switch Cycle through the saved presets