[wled] Abstract json api for better segment support (#11509)
* Change to json for states Signed-off-by: Matthew Skinner <matt@pcmus.com> * Add 3rd colours. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Segments now mostly work Signed-off-by: Matthew Skinner <matt@pcmus.com> * changes to json api fully made Signed-off-by: Matthew Skinner <matt@pcmus.com> * Mirror and Reverse channels added. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Remove old channels when needed. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Simplify return Signed-off-by: Matthew Skinner <matt@pcmus.com> * Add support for named presets Signed-off-by: Matthew Skinner <matt@pcmus.com> * Dont add empty preset 0 to list Signed-off-by: Matthew Skinner <matt@pcmus.com> * Add preset saving with custom names Signed-off-by: Matthew Skinner <matt@pcmus.com> * Tidy up Signed-off-by: Matthew Skinner <matt@pcmus.com> * Rename function for clarity Signed-off-by: Matthew Skinner <matt@pcmus.com> * Add more channels Signed-off-by: Matthew Skinner <matt@pcmus.com> * Clean up Signed-off-by: Matthew Skinner <matt@pcmus.com> * Fix bugs and update readme for new channels Signed-off-by: Matthew Skinner <matt@pcmus.com>
This commit is contained in:
@@ -47,15 +47,30 @@ public class WLedActions implements ThingActions {
|
||||
@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) {
|
||||
WLedHandler localHandler = handler;
|
||||
if (presetNumber > 0 && localHandler != null) {
|
||||
localHandler.savePreset(presetNumber);
|
||||
}
|
||||
savePreset(presetNumber, "");
|
||||
}
|
||||
|
||||
public static void savePreset(@Nullable ThingActions actions, int presetNumber) {
|
||||
if (actions instanceof WLedActions) {
|
||||
((WLedActions) actions).savePreset(presetNumber);
|
||||
((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 (localHandler != null) {
|
||||
localHandler.savePreset(presetNumber, presetName);
|
||||
}
|
||||
}
|
||||
|
||||
public static void savePreset(@Nullable ThingActions actions, int presetNumber, String presetName) {
|
||||
if (actions instanceof WLedActions) {
|
||||
((WLedActions) actions).savePreset(presetNumber, presetName);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Instance is not a WLED class.");
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -106,7 +106,7 @@ public class WLedDiscoveryService implements MDNSDiscoveryParticipant {
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,41 +15,36 @@ package org.openhab.binding.wled.internal;
|
||||
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 @@ import org.slf4j.LoggerFactory;
|
||||
@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 <cl>
|
||||
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, "<cl>");
|
||||
updateState(CHANNEL_PRIMARY_COLOR, primaryColor);
|
||||
secondaryColor = parseToHSBType(message, "<cs>");
|
||||
updateState(CHANNEL_SECONDARY_COLOR, secondaryColor);
|
||||
try {
|
||||
primaryWhite = new BigDecimal(WLedHelper.getValue(message, "<wv>", "<"));
|
||||
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, "<ws>", "<"));
|
||||
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<StateOption> fxOptions = new ArrayList<>();
|
||||
List<StateOption> 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("<ac>0</ac>")) {
|
||||
updateState(CHANNEL_MASTER_CONTROLS, OnOffType.OFF);
|
||||
} else {
|
||||
masterBrightness255 = new BigDecimal(WLedHelper.getValue(message, "<ac>", "<"));
|
||||
updateState(CHANNEL_MASTER_CONTROLS,
|
||||
new PercentType(masterBrightness255.divide(BIG_DECIMAL_2_55, RoundingMode.HALF_UP)));
|
||||
}
|
||||
if (message.contains("<ix>0</ix>")) {
|
||||
updateState(CHANNEL_INTENSITY, OnOffType.OFF);
|
||||
} else {
|
||||
BigDecimal bigTemp = new BigDecimal(WLedHelper.getValue(message, "<ix>", "<")).divide(BIG_DECIMAL_2_55,
|
||||
RoundingMode.HALF_UP);
|
||||
updateState(CHANNEL_INTENSITY, new PercentType(bigTemp));
|
||||
}
|
||||
if (message.contains("<cy>1</cy>")) {
|
||||
updateState(CHANNEL_PRESET_CYCLE, OnOffType.ON);
|
||||
} else {
|
||||
updateState(CHANNEL_PRESET_CYCLE, OnOffType.OFF);
|
||||
}
|
||||
if (message.contains("<nl>1</nl>")) {
|
||||
updateState(CHANNEL_SLEEP, OnOffType.ON);
|
||||
} else {
|
||||
updateState(CHANNEL_SLEEP, OnOffType.OFF);
|
||||
}
|
||||
if (message.contains("<ns>1</ns>")) {
|
||||
updateState(CHANNEL_SYNC_SEND, OnOffType.ON);
|
||||
} else {
|
||||
updateState(CHANNEL_SYNC_SEND, OnOffType.OFF);
|
||||
}
|
||||
if (message.contains("<nr>1</nr>")) {
|
||||
updateState(CHANNEL_SYNC_RECEIVE, OnOffType.ON);
|
||||
} else {
|
||||
updateState(CHANNEL_SYNC_RECEIVE, OnOffType.OFF);
|
||||
}
|
||||
if (message.contains("<fx>")) {
|
||||
updateState(CHANNEL_FX, new StringType(WLedHelper.getValue(message, "<fx>", "<")));
|
||||
}
|
||||
if (message.contains("<sx>")) {
|
||||
BigDecimal bigTemp = new BigDecimal(WLedHelper.getValue(message, "<sx>", "<")).divide(BIG_DECIMAL_2_55,
|
||||
RoundingMode.HALF_UP);
|
||||
updateState(CHANNEL_SPEED, new PercentType(bigTemp));
|
||||
}
|
||||
if (message.contains("<fp>")) {
|
||||
updateState(CHANNEL_PALETTES, new StringType(WLedHelper.getValue(message, "<fp>", "<")));
|
||||
}
|
||||
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");
|
||||
}
|
||||
} 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
|
||||
} else {
|
||||
sendGetRequest("/win&TT=1000&A=255");
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
if (masterBrightness255.intValue() > 15) {
|
||||
sendGetRequest("/win&TT=1000&A=~-15");
|
||||
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) {
|
||||
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=0");
|
||||
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;
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
} else if (command instanceof HSBType) {
|
||||
if (PercentType.ZERO.equals(((HSBType) command).getBrightness())) {
|
||||
sendGetRequest("/win&TT=500&T=0");
|
||||
}
|
||||
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);
|
||||
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 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_PRESET_CYCLE: // ch removed in firmware 0.13.0 and newer
|
||||
if (command instanceof OnOffType) {
|
||||
localApi.setPresetCycle(OnOffType.ON.equals(command));
|
||||
}
|
||||
}
|
||||
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:
|
||||
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) {
|
||||
}
|
||||
sendGetRequest("/win&PS=" + presetIndex);
|
||||
}
|
||||
|
||||
private void pollLED() {
|
||||
sendGetRequest("/win");
|
||||
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::pollLED, 1, config.pollTime, TimeUnit.SECONDS);
|
||||
pollingFuture = scheduler.scheduleWithFixedDelay(this::pollState, 0, config.pollTime, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -450,6 +303,7 @@ public class WLedHandler extends BaseThingHandler {
|
||||
Future<?> future = pollingFuture;
|
||||
if (future != null) {
|
||||
future.cancel(true);
|
||||
pollingFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,7 @@ import static org.openhab.binding.wled.internal.WLedBindingConstants.SUPPORTED_T
|
||||
|
||||
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 @@ import org.osgi.service.component.annotations.Reference;
|
||||
@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;
|
||||
}
|
||||
|
||||
@@ -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 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WLedHelper {
|
||||
public static HSBType parseToHSBType(String message) {
|
||||
// example message rgb in array brackets [255.0, 255.0, 255.0]
|
||||
List<String> 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<String> 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 @@ public class WLedHelper {
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A List that holds the values from a heading/element that re-occurs in a message multiple times.
|
||||
*
|
||||
*/
|
||||
static List<String> listOfResults(String message, String element, String end) {
|
||||
List<String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> effects = new ArrayList<>();
|
||||
public List<String> 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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<StateOption> presetsOptions = new ArrayList<>();
|
||||
List<StateOption> playlistsOptions = new ArrayList<>();
|
||||
JsonObject obj = gson.fromJson(sendGetRequest("/presets.json"), JsonObject.class);
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
Set<Entry<String, JsonElement>> set = obj.entrySet();
|
||||
int counter = 0;
|
||||
for (Entry<String, JsonElement> 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}");
|
||||
}
|
||||
}
|
||||
@@ -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<Channel> 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)));
|
||||
}
|
||||
}
|
||||
@@ -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<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
|
||||
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<StateOption> 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<StateOption> 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 + "}]}");
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,15 @@
|
||||
<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="presets" typeId="presets"/>
|
||||
<channel id="playlists" typeId="playlists"/>
|
||||
<channel id="presetDuration" typeId="presetDuration"/>
|
||||
<channel id="transformTime" typeId="transformTime"/>
|
||||
<channel id="presetCycle" typeId="presetCycle"/>
|
||||
@@ -22,6 +26,11 @@
|
||||
<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="syncSend" typeId="syncSend"/>
|
||||
<channel id="syncReceive" typeId="syncReceive"/>
|
||||
@@ -36,11 +45,11 @@
|
||||
<description>Time in seconds of how often to fetch the state of the LEDs.</description>
|
||||
<default>10</default>
|
||||
</parameter>
|
||||
<parameter name="segmentIndex" type="integer" required="true" min="-1">
|
||||
<parameter name="segmentIndex" type="integer" required="true" min="0">
|
||||
<label>Segment Index</label>
|
||||
<description>Leave this as -1 if you are not using segments, otherwise set this to the segment index number that you
|
||||
<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>-1</default>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
<parameter name="saturationThreshold" type="integer" required="true" min="0" max="99">
|
||||
<label>Saturation Threshold</label>
|
||||
@@ -62,6 +71,13 @@
|
||||
</tags>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="segmentBrightness" advanced="true">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Segment Brightness</label>
|
||||
<description>Changes the brightness of the whole segment</description>
|
||||
<category>DimmableLight</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="primaryColor" advanced="true">
|
||||
<item-type>Color</item-type>
|
||||
<label>Primary Color</label>
|
||||
@@ -90,6 +106,20 @@
|
||||
<category>DimmableLight</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="tertiaryColor" advanced="true">
|
||||
<item-type>Color</item-type>
|
||||
<label>Tertiary Color</label>
|
||||
<description>Allows you to change the third color used in FX</description>
|
||||
<category>ColorLight</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="tertiaryWhite" advanced="true">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Tertiary White</label>
|
||||
<description>Changes the brightness of the third white LED</description>
|
||||
<category>DimmableLight</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="palettes">
|
||||
<item-type>String</item-type>
|
||||
<label>Palettes</label>
|
||||
@@ -128,6 +158,12 @@
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="playlists">
|
||||
<item-type>String</item-type>
|
||||
<label>Playlists</label>
|
||||
<description>The currently playing play list</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="presetDuration" advanced="true">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Preset Duration</label>
|
||||
@@ -136,12 +172,55 @@
|
||||
<state min="0.1" max="65" step="0.1" pattern="%.1f %unit%" readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="grouping" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Grouping</label>
|
||||
<description>How many consecutive LEDs of the same segment will be grouped to the same color</description>
|
||||
<state min="1" max="255" step="1" pattern="%.0f" readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="spacing" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Spacing</label>
|
||||
<description>How many LEDs are turned off and skipped between each group</description>
|
||||
<state min="0" max="255" step="1" pattern="%.0f" readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="liveOverride" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Live Override</label>
|
||||
<description>Live data override. 0 is off, 1 is override until live data ends, 2 is override until ESP reboot</description>
|
||||
<state min="0" max="2" step="1" pattern="%.0f" readOnly="false">
|
||||
<options>
|
||||
<option value="0">Off</option>
|
||||
<option value="1">Override Live</option>
|
||||
<option value="2">Until Reboot</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="transformTime" advanced="true">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Transform Time</label>
|
||||
<description>Time it takes to change/fade from one look to the next.</description>
|
||||
<category>Time</category>
|
||||
<state min="0" max="65" step="0.1" pattern="%.1f %unit%" readOnly="false"/>
|
||||
<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"/>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="speed" advanced="true">
|
||||
@@ -156,6 +235,18 @@
|
||||
<description>Change the intensity of the FX</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="mirror" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Mirror Effect</label>
|
||||
<description>Mirror the effect for this segment</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="reverse" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Reverse Direction</label>
|
||||
<description>Reverse the direction of the current segment</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="sleep" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Sleep Timer</label>
|
||||
@@ -163,7 +254,7 @@
|
||||
<category>Time</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="presetCycle">
|
||||
<channel-type id="presetCycle" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Preset Cycle</label>
|
||||
<description>Cycle through the saved presets</description>
|
||||
|
||||
Reference in New Issue
Block a user