2 * Copyright (c) 2010-2023 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.milight.internal.handler;
15 import java.net.DatagramSocket;
16 import java.net.InetAddress;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.binding.milight.internal.MilightBindingConstants;
22 import org.openhab.binding.milight.internal.MilightThingState;
23 import org.openhab.binding.milight.internal.protocol.QueuedSend;
24 import org.openhab.core.library.types.DecimalType;
25 import org.openhab.core.library.types.HSBType;
26 import org.openhab.core.library.types.IncreaseDecreaseType;
27 import org.openhab.core.library.types.OnOffType;
28 import org.openhab.core.library.types.PercentType;
29 import org.openhab.core.thing.Bridge;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingStatusDetail;
34 import org.openhab.core.thing.ThingStatusInfo;
35 import org.openhab.core.thing.binding.BaseThingHandler;
36 import org.openhab.core.types.Command;
37 import org.openhab.core.types.RefreshType;
38 import org.openhab.core.types.UnDefType;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
43 * The {@link AbstractLedHandler} is responsible for handling commands, which are
44 * sent to one of the channels.
46 * @author David Graeff - Initial contribution
49 public abstract class AbstractLedHandler extends BaseThingHandler implements LedHandlerInterface {
50 private final Logger logger = LoggerFactory.getLogger(AbstractLedHandler.class);
52 protected final QueuedSend sendQueue;
53 /** Each bulb type including zone has to be unique. -> Each type has an offset. */
54 protected final int typeOffset;
55 protected final MilightThingState state = new MilightThingState();
56 protected LedHandlerConfig config = new LedHandlerConfig();
57 protected int port = 0;
59 protected @NonNullByDefault({}) InetAddress address;
60 protected @NonNullByDefault({}) DatagramSocket socket;
62 protected int delayTimeMS = 50;
63 protected int repeatTimes = 3;
65 protected int bridgeOffset;
68 * A bulb always belongs to a zone in the milight universe and we need a way to queue commands for being send.
71 * @param sendQueue The send queue.
72 * @param typeOffset Each bulb type including its zone has to be unique. To realise this, each type has an offset.
74 public AbstractLedHandler(Thing thing, QueuedSend sendQueue, int typeOffset) {
76 this.typeOffset = typeOffset;
77 this.sendQueue = sendQueue;
81 public void handleCommand(ChannelUID channelUID, Command command) {
82 if (command instanceof RefreshType) {
83 switch (channelUID.getId()) {
84 case MilightBindingConstants.CHANNEL_COLOR:
85 updateState(channelUID, new HSBType(new DecimalType(state.hue360),
86 new PercentType(state.saturation), new PercentType(state.brightness)));
88 case MilightBindingConstants.CHANNEL_BRIGHTNESS:
89 updateState(channelUID, new PercentType(state.brightness));
91 case MilightBindingConstants.CHANNEL_SATURATION:
92 updateState(channelUID, new PercentType(state.saturation));
94 case MilightBindingConstants.CHANNEL_TEMP:
95 updateState(channelUID, new PercentType(state.colorTemperature));
97 case MilightBindingConstants.CHANNEL_ANIMATION_MODE:
98 updateState(channelUID, new DecimalType(state.animationMode));
104 switch (channelUID.getId()) {
105 case MilightBindingConstants.CHANNEL_COLOR: {
106 if (command instanceof HSBType hsb) {
107 this.setHSB(hsb.getHue().intValue(), hsb.getSaturation().intValue(), hsb.getBrightness().intValue(),
109 updateState(MilightBindingConstants.CHANNEL_SATURATION, new PercentType(state.saturation));
110 } else if (command instanceof OnOffType hsb) {
111 this.setPower(hsb == OnOffType.ON, state);
112 } else if (command instanceof PercentType p) {
113 this.setBrightness(p.intValue(), state);
114 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
115 this.changeBrightness(increaseDecreaseCommand == IncreaseDecreaseType.INCREASE ? 1 : -1, state);
118 "CHANNEL_COLOR channel only supports OnOffType/IncreaseDecreaseType/HSBType/PercentType");
120 updateState(MilightBindingConstants.CHANNEL_BRIGHTNESS, new PercentType(state.brightness));
123 case MilightBindingConstants.CHANNEL_NIGHTMODE: {
124 this.nightMode(state);
125 updateState(channelUID, UnDefType.UNDEF);
128 case MilightBindingConstants.CHANNEL_WHITEMODE: {
129 this.whiteMode(state);
130 updateState(channelUID, UnDefType.UNDEF);
133 case MilightBindingConstants.CHANNEL_BRIGHTNESS: {
134 if (command instanceof OnOffType onOffCommand) {
135 this.setPower(onOffCommand == OnOffType.ON, state);
136 } else if (command instanceof DecimalType decimalCommand) {
137 this.setBrightness(decimalCommand.intValue(), state);
138 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
139 this.changeBrightness(increaseDecreaseCommand == IncreaseDecreaseType.INCREASE ? 1 : -1, state);
141 logger.error("CHANNEL_BRIGHTNESS channel only supports OnOffType/IncreaseDecreaseType/DecimalType");
143 updateState(MilightBindingConstants.CHANNEL_COLOR, new HSBType(new DecimalType(state.hue360),
144 new PercentType(state.saturation), new PercentType(state.brightness)));
148 case MilightBindingConstants.CHANNEL_SATURATION: {
149 if (command instanceof OnOffType onOffCommand) {
150 this.setSaturation((onOffCommand == OnOffType.ON) ? 100 : 0, state);
151 } else if (command instanceof DecimalType decimalCommand) {
152 this.setSaturation(decimalCommand.intValue(), state);
153 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
154 this.changeSaturation(increaseDecreaseCommand == IncreaseDecreaseType.INCREASE ? 1 : -1, state);
156 logger.error("CHANNEL_SATURATION channel only supports OnOffType/IncreaseDecreaseType/DecimalType");
158 updateState(MilightBindingConstants.CHANNEL_COLOR, new HSBType(new DecimalType(state.hue360),
159 new PercentType(state.saturation), new PercentType(state.brightness)));
163 case MilightBindingConstants.CHANNEL_TEMP: {
164 if (command instanceof OnOffType onOffCommand) {
165 this.setColorTemperature((onOffCommand == OnOffType.ON) ? 100 : 0, state);
166 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
167 this.changeColorTemperature(increaseDecreaseCommand == IncreaseDecreaseType.INCREASE ? 1 : -1,
169 } else if (command instanceof DecimalType d) {
170 this.setColorTemperature(d.intValue(), state);
172 logger.error("CHANNEL_TEMP channel only supports OnOffType/IncreaseDecreaseType/DecimalType");
176 case MilightBindingConstants.CHANNEL_SPEED_REL: {
177 if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
178 if (increaseDecreaseCommand == IncreaseDecreaseType.INCREASE) {
179 this.changeSpeed(1, state);
180 } else if (increaseDecreaseCommand == IncreaseDecreaseType.DECREASE) {
181 this.changeSpeed(-1, state);
184 logger.error("CHANNEL_SPEED channel only supports IncreaseDecreaseType");
188 case MilightBindingConstants.CHANNEL_ANIMATION_MODE: {
189 if (command instanceof DecimalType decimalCommand) {
190 this.setLedMode(decimalCommand.intValue(), state);
192 logger.error("Animation mode channel only supports DecimalType");
196 case MilightBindingConstants.CHANNEL_ANIMATION_MODE_REL: {
197 if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
198 if (increaseDecreaseCommand == IncreaseDecreaseType.INCREASE) {
199 this.nextAnimationMode(state);
200 } else if (increaseDecreaseCommand == IncreaseDecreaseType.DECREASE) {
201 this.previousAnimationMode(state);
204 logger.error("Relative animation mode channel only supports IncreaseDecreaseType");
209 logger.error("Channel unknown {}", channelUID.getId());
214 * Return the bride handler.
216 public @Nullable AbstractBridgeHandler getBridgeHandler() {
217 Bridge bridge = getBridge();
218 if (bridge == null) {
221 return (AbstractBridgeHandler) bridge.getHandler();
225 * Return the bridge status.
227 public ThingStatusInfo getBridgeStatus() {
228 Bridge b = getBridge();
230 return b.getStatusInfo();
232 return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null);
237 * Generates a unique command id for the {@see QueuedSend}. It incorporates the bridge, zone, bulb type and command
240 * @param commandCategory The category of the command.
244 public int uidc(int commandCategory) {
245 return (bridgeOffset + config.zone + typeOffset + 1) * 64 + commandCategory;
248 protected void start(AbstractBridgeHandler handler) {
252 public void bridgeStatusChanged(@NonNull ThingStatusInfo bridgeStatusInfo) {
253 if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
254 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
257 if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
258 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
262 AbstractBridgeHandler h = getBridgeHandler();
264 logger.warn("Bridge handler not found!");
267 final InetAddress inetAddress = h.address;
268 if (inetAddress == null) {
269 logger.warn("Bridge handler has not yet determined the IP address!");
274 configUpdated(h, inetAddress);
276 if (h.getThing().getStatus() == ThingStatus.ONLINE) {
277 updateStatus(ThingStatus.ONLINE);
280 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
285 * Called by the bridge if a configuration update happened after initialisation has been done
287 * @param h The bridge handler
289 public void configUpdated(AbstractBridgeHandler h, InetAddress address) {
290 this.port = h.config.port;
291 this.address = address;
292 this.socket = h.socket;
293 this.delayTimeMS = h.config.delayTime;
294 this.repeatTimes = h.config.repeat;
295 this.bridgeOffset = h.bridgeOffset;
299 public void initialize() {
300 config = getConfigAs(LedHandlerConfig.class);
301 bridgeStatusChanged(getBridgeStatus());