2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.wled.internal.handlers;
15 import static org.openhab.binding.wled.internal.WLedBindingConstants.*;
17 import java.math.BigDecimal;
18 import java.util.ArrayList;
19 import java.util.List;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.openhab.binding.wled.internal.WLedSegmentConfiguration;
23 import org.openhab.binding.wled.internal.api.ApiException;
24 import org.openhab.binding.wled.internal.api.WledApi;
25 import org.openhab.core.library.types.DecimalType;
26 import org.openhab.core.library.types.HSBType;
27 import org.openhab.core.library.types.IncreaseDecreaseType;
28 import org.openhab.core.library.types.OnOffType;
29 import org.openhab.core.library.types.PercentType;
30 import org.openhab.core.thing.Bridge;
31 import org.openhab.core.thing.Channel;
32 import org.openhab.core.thing.ChannelUID;
33 import org.openhab.core.thing.Thing;
34 import org.openhab.core.thing.ThingStatus;
35 import org.openhab.core.thing.ThingStatusDetail;
36 import org.openhab.core.thing.binding.BaseThingHandler;
37 import org.openhab.core.thing.binding.builder.ThingBuilder;
38 import org.openhab.core.types.Command;
39 import org.openhab.core.types.RefreshType;
40 import org.openhab.core.types.State;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * The {@link WLedSegmentHandler} is responsible for handling only a single segment from a WLED device.
47 * @author Matthew Skinner - Initial contribution
51 public class WLedSegmentHandler extends BaseThingHandler {
52 private final Logger logger = LoggerFactory.getLogger(getClass());
53 private WLedSegmentConfiguration config = new WLedSegmentConfiguration();
54 private BigDecimal masterBrightness255 = BigDecimal.ZERO;
55 private HSBType primaryColor = new HSBType();
56 private HSBType secondaryColor = new HSBType();
57 private HSBType thirdColor = new HSBType();
59 public WLedSegmentHandler(Thing thing) {
63 public void update(String channelID, State state) {
64 updateState(channelID, state);
67 private void removeWhiteChannels() {
68 List<Channel> removeChannels = new ArrayList<>();
69 Channel channel = getThing().getChannel(CHANNEL_PRIMARY_WHITE);
70 if (channel != null) {
71 removeChannels.add(channel);
73 channel = getThing().getChannel(CHANNEL_SECONDARY_WHITE);
74 if (channel != null) {
75 removeChannels.add(channel);
77 channel = getThing().getChannel(CHANNEL_THIRD_WHITE);
78 if (channel != null) {
79 removeChannels.add(channel);
81 removeChannels(removeChannels);
84 private void removeChannels(List<Channel> removeChannels) {
85 if (!removeChannels.isEmpty()) {
86 ThingBuilder thingBuilder = editThing();
87 thingBuilder.withoutChannels(removeChannels);
88 updateThing(thingBuilder.build());
93 public void handleCommand(ChannelUID channelUID, Command command) {
94 Bridge bridge = getBridge();
98 WLedBridgeHandler bridgeHandler = (WLedBridgeHandler) bridge.getHandler();
99 if (bridgeHandler == null) {
102 WledApi localApi = bridgeHandler.api;
103 if (localApi == null) {
106 if (command instanceof RefreshType) {
107 return;// no need to check for refresh below
109 logger.debug("command {} sent to {}", command, channelUID.getId());
111 switch (channelUID.getId()) {
112 case CHANNEL_SEGMENT_BRIGHTNESS:
113 if (command instanceof OnOffType) {
114 localApi.setMasterOn(OnOffType.ON.equals(command), config.segmentIndex);
115 } else if (command instanceof PercentType percentCommand) {
116 if (PercentType.ZERO.equals(command)) {
117 localApi.setMasterOn(false, config.segmentIndex);
120 // do not turn the globalOn in order to allow for configuring this segment for effects that
122 localApi.setMasterBrightness(percentCommand, config.segmentIndex);
126 localApi.setMirror(OnOffType.ON.equals(command), config.segmentIndex);
128 case CHANNEL_SPACING:
129 if (command instanceof DecimalType decimalCommand) {
130 localApi.setSpacing(decimalCommand.intValue(), config.segmentIndex);
133 case CHANNEL_GROUPING:
134 if (command instanceof DecimalType decimalCommand) {
135 localApi.setGrouping(decimalCommand.intValue(), config.segmentIndex);
138 case CHANNEL_REVERSE:
139 localApi.setReverse(OnOffType.ON.equals(command), config.segmentIndex);
141 case CHANNEL_PRIMARY_WHITE:
142 if (command instanceof PercentType percentCommand) {
143 localApi.sendGetRequest("/win&W=" + percentCommand.toBigDecimal().multiply(BIG_DECIMAL_2_55));
146 case CHANNEL_SECONDARY_WHITE:
147 if (command instanceof PercentType percentCommand) {
148 localApi.sendGetRequest("/win&W2=" + percentCommand.toBigDecimal().multiply(BIG_DECIMAL_2_55));
151 case CHANNEL_MASTER_CONTROLS:
152 if (command instanceof OnOffType) {
153 if (OnOffType.ON.equals(command)) {
154 // global may be off, but we don't want to switch global off and affect other segments
155 localApi.setGlobalOn(true);
157 localApi.setMasterOn(OnOffType.ON.equals(command), config.segmentIndex);
158 } else if (command instanceof IncreaseDecreaseType) {
159 if (IncreaseDecreaseType.INCREASE.equals(command)) {
160 localApi.setGlobalOn(true);
161 if (masterBrightness255.intValue() < 240) {
162 localApi.sendGetRequest("/win&TT=1000&A=~15"); // 255 divided by 15 = 17 levels
164 localApi.sendGetRequest("/win&TT=1000&A=255");
167 if (masterBrightness255.intValue() > 15) {
168 localApi.sendGetRequest("/win&TT=1000&A=~-15");
170 localApi.sendGetRequest("/win&TT=1000&A=0");
173 } else if (command instanceof HSBType hsbCommand) {
174 if ((hsbCommand.getBrightness()).equals(PercentType.ZERO)) {
175 localApi.setMasterOn(false, config.segmentIndex);
178 localApi.setGlobalOn(true);
179 primaryColor = hsbCommand;
180 if (primaryColor.getSaturation().intValue() < bridgeHandler.config.saturationThreshold
181 && bridgeHandler.hasWhite) {
182 localApi.setWhiteOnly(hsbCommand, config.segmentIndex);
183 } else if (primaryColor.getSaturation().intValue() == 32
184 && primaryColor.getHue().intValue() == 36 && bridgeHandler.hasWhite) {
185 localApi.setWhiteOnly(hsbCommand, config.segmentIndex);
187 localApi.setMasterHSB(hsbCommand, config.segmentIndex);
189 } else if (command instanceof PercentType percentCommand) {
190 if (PercentType.ZERO.equals(percentCommand)) {
191 localApi.setMasterOn(false, config.segmentIndex);
194 localApi.setGlobalOn(true);
195 localApi.setMasterBrightness(percentCommand, config.segmentIndex);
198 case CHANNEL_PRIMARY_COLOR:
199 if (command instanceof HSBType hsbCommand) {
200 primaryColor = hsbCommand;
201 } else if (command instanceof PercentType percentCommand) {
202 primaryColor = new HSBType(primaryColor.getHue(), primaryColor.getSaturation(), percentCommand);
204 localApi.setPrimaryColor(primaryColor, config.segmentIndex);
206 case CHANNEL_SECONDARY_COLOR:
207 if (command instanceof HSBType hsbCommand) {
208 secondaryColor = hsbCommand;
209 } else if (command instanceof PercentType percentCommand) {
210 secondaryColor = new HSBType(secondaryColor.getHue(), secondaryColor.getSaturation(),
213 localApi.setSecondaryColor(secondaryColor, config.segmentIndex);
215 case CHANNEL_THIRD_COLOR:
216 if (command instanceof HSBType hsbCommand) {
217 thirdColor = hsbCommand;
218 } else if (command instanceof PercentType percentCommand) {
219 thirdColor = new HSBType(thirdColor.getHue(), thirdColor.getSaturation(), percentCommand);
221 localApi.setTertiaryColor(thirdColor, config.segmentIndex);
223 case CHANNEL_PALETTES:
224 localApi.setPalette(command.toString(), config.segmentIndex);
227 localApi.setGlobalOn(true);
228 localApi.setEffect(command.toString(), config.segmentIndex);
231 localApi.setFxSpeed((PercentType) command, config.segmentIndex);
233 case CHANNEL_INTENSITY:
234 localApi.setFxIntencity((PercentType) command, config.segmentIndex);
237 } catch (ApiException e) {
238 logger.debug("Exception occured:{}", e.getMessage());
243 public void initialize() {
244 config = getConfigAs(WLedSegmentConfiguration.class);
245 Bridge bridge = getBridge();
246 if (bridge == null) {
247 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge is selected.");
249 WLedBridgeHandler localBridgeHandler = (WLedBridgeHandler) bridge.getHandler();
250 if (localBridgeHandler == null) {
251 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
254 WledApi localAPI = localBridgeHandler.api;
255 if (localAPI != null) {
256 updateStatus(ThingStatus.ONLINE);
257 localBridgeHandler.stateDescriptionProvider
258 .setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_FX), localAPI.getUpdatedFxList());
259 localBridgeHandler.stateDescriptionProvider.setStateOptions(
260 new ChannelUID(getThing().getUID(), CHANNEL_PALETTES), localAPI.getUpdatedPaletteList());
261 if (!localBridgeHandler.hasWhite) {
262 logger.debug("WLED is not setup to use RGBW, so removing un-needed white channels");
263 removeWhiteChannels();
266 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);