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;
17 import java.net.SocketException;
19 import org.eclipse.jdt.annotation.NonNull;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.milight.internal.MilightBindingConstants;
23 import org.openhab.binding.milight.internal.MilightThingState;
24 import org.openhab.binding.milight.internal.protocol.QueuedSend;
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.ChannelUID;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingStatusDetail;
35 import org.openhab.core.thing.ThingStatusInfo;
36 import org.openhab.core.thing.binding.BaseThingHandler;
37 import org.openhab.core.types.Command;
38 import org.openhab.core.types.RefreshType;
39 import org.openhab.core.types.UnDefType;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
44 * The {@link AbstractLedHandler} is responsible for handling commands, which are
45 * sent to one of the channels.
47 * @author David Graeff - Initial contribution
50 public abstract class AbstractLedHandler extends BaseThingHandler implements LedHandlerInterface {
51 private final Logger logger = LoggerFactory.getLogger(AbstractLedHandler.class);
53 protected final QueuedSend sendQueue;
54 /** Each bulb type including zone has to be unique. -> Each type has an offset. */
55 protected final int typeOffset;
56 protected final MilightThingState state = new MilightThingState();
57 protected LedHandlerConfig config = new LedHandlerConfig();
58 protected int port = 0;
60 protected @NonNullByDefault({}) InetAddress address;
61 protected @NonNullByDefault({}) DatagramSocket socket;
63 protected int delayTimeMS = 50;
64 protected int repeatTimes = 3;
66 protected int bridgeOffset;
69 * A bulb always belongs to a zone in the milight universe and we need a way to queue commands for being send.
71 * @param typeOffset Each bulb type including its zone has to be unique. To realise this, each type has an offset.
72 * @param sendQueue The send queue.
73 * @param zone A zone, usually 0 means all bulbs of the same type. [0-4]
74 * @throws SocketException
76 public AbstractLedHandler(Thing thing, QueuedSend sendQueue, int typeOffset) {
78 this.typeOffset = typeOffset;
79 this.sendQueue = sendQueue;
83 public void handleCommand(ChannelUID channelUID, Command command) {
84 if (command instanceof RefreshType) {
85 switch (channelUID.getId()) {
86 case MilightBindingConstants.CHANNEL_COLOR:
87 updateState(channelUID, new HSBType(new DecimalType(state.hue360),
88 new PercentType(state.saturation), new PercentType(state.brightness)));
90 case MilightBindingConstants.CHANNEL_BRIGHTNESS:
91 updateState(channelUID, new PercentType(state.brightness));
93 case MilightBindingConstants.CHANNEL_SATURATION:
94 updateState(channelUID, new PercentType(state.saturation));
96 case MilightBindingConstants.CHANNEL_TEMP:
97 updateState(channelUID, new PercentType(state.colorTemperature));
99 case MilightBindingConstants.CHANNEL_ANIMATION_MODE:
100 updateState(channelUID, new DecimalType(state.animationMode));
106 switch (channelUID.getId()) {
107 case MilightBindingConstants.CHANNEL_COLOR: {
108 if (command instanceof HSBType hsb) {
109 this.setHSB(hsb.getHue().intValue(), hsb.getSaturation().intValue(), hsb.getBrightness().intValue(),
111 updateState(MilightBindingConstants.CHANNEL_SATURATION, new PercentType(state.saturation));
112 } else if (command instanceof OnOffType hsb) {
113 this.setPower(hsb == OnOffType.ON, state);
114 } else if (command instanceof PercentType p) {
115 this.setBrightness(p.intValue(), state);
116 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
117 this.changeBrightness(increaseDecreaseCommand == IncreaseDecreaseType.INCREASE ? 1 : -1, state);
120 "CHANNEL_COLOR channel only supports OnOffType/IncreaseDecreaseType/HSBType/PercentType");
122 updateState(MilightBindingConstants.CHANNEL_BRIGHTNESS, new PercentType(state.brightness));
125 case MilightBindingConstants.CHANNEL_NIGHTMODE: {
126 this.nightMode(state);
127 updateState(channelUID, UnDefType.UNDEF);
130 case MilightBindingConstants.CHANNEL_WHITEMODE: {
131 this.whiteMode(state);
132 updateState(channelUID, UnDefType.UNDEF);
135 case MilightBindingConstants.CHANNEL_BRIGHTNESS: {
136 if (command instanceof OnOffType onOffCommand) {
137 this.setPower(onOffCommand == OnOffType.ON, state);
138 } else if (command instanceof DecimalType decimalCommand) {
139 this.setBrightness(decimalCommand.intValue(), state);
140 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
141 this.changeBrightness(increaseDecreaseCommand == IncreaseDecreaseType.INCREASE ? 1 : -1, state);
143 logger.error("CHANNEL_BRIGHTNESS channel only supports OnOffType/IncreaseDecreaseType/DecimalType");
145 updateState(MilightBindingConstants.CHANNEL_COLOR, new HSBType(new DecimalType(state.hue360),
146 new PercentType(state.saturation), new PercentType(state.brightness)));
150 case MilightBindingConstants.CHANNEL_SATURATION: {
151 if (command instanceof OnOffType onOffCommand) {
152 this.setSaturation((onOffCommand == OnOffType.ON) ? 100 : 0, state);
153 } else if (command instanceof DecimalType decimalCommand) {
154 this.setSaturation(decimalCommand.intValue(), state);
155 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
156 this.changeSaturation(increaseDecreaseCommand == IncreaseDecreaseType.INCREASE ? 1 : -1, state);
158 logger.error("CHANNEL_SATURATION channel only supports OnOffType/IncreaseDecreaseType/DecimalType");
160 updateState(MilightBindingConstants.CHANNEL_COLOR, new HSBType(new DecimalType(state.hue360),
161 new PercentType(state.saturation), new PercentType(state.brightness)));
165 case MilightBindingConstants.CHANNEL_TEMP: {
166 if (command instanceof OnOffType onOffCommand) {
167 this.setColorTemperature((onOffCommand == OnOffType.ON) ? 100 : 0, state);
168 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
169 this.changeColorTemperature(increaseDecreaseCommand == IncreaseDecreaseType.INCREASE ? 1 : -1,
171 } else if (command instanceof DecimalType d) {
172 this.setColorTemperature(d.intValue(), state);
174 logger.error("CHANNEL_TEMP channel only supports OnOffType/IncreaseDecreaseType/DecimalType");
178 case MilightBindingConstants.CHANNEL_SPEED_REL: {
179 if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
180 if (increaseDecreaseCommand == IncreaseDecreaseType.INCREASE) {
181 this.changeSpeed(1, state);
182 } else if (increaseDecreaseCommand == IncreaseDecreaseType.DECREASE) {
183 this.changeSpeed(-1, state);
186 logger.error("CHANNEL_SPEED channel only supports IncreaseDecreaseType");
190 case MilightBindingConstants.CHANNEL_ANIMATION_MODE: {
191 if (command instanceof DecimalType decimalCommand) {
192 this.setLedMode(decimalCommand.intValue(), state);
194 logger.error("Animation mode channel only supports DecimalType");
198 case MilightBindingConstants.CHANNEL_ANIMATION_MODE_REL: {
199 if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
200 if (increaseDecreaseCommand == IncreaseDecreaseType.INCREASE) {
201 this.nextAnimationMode(state);
202 } else if (increaseDecreaseCommand == IncreaseDecreaseType.DECREASE) {
203 this.previousAnimationMode(state);
206 logger.error("Relative animation mode channel only supports IncreaseDecreaseType");
211 logger.error("Channel unknown {}", channelUID.getId());
216 * Return the bride handler.
218 public @Nullable AbstractBridgeHandler getBridgeHandler() {
219 Bridge bridge = getBridge();
220 if (bridge == null) {
223 return (AbstractBridgeHandler) bridge.getHandler();
227 * Return the bridge status.
229 public ThingStatusInfo getBridgeStatus() {
230 Bridge b = getBridge();
232 return b.getStatusInfo();
234 return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null);
239 * Generates a unique command id for the {@see QueuedSend}. It incorporates the bridge, zone, bulb type and command
242 * @param commandCategory The category of the command.
246 public int uidc(int commandCategory) {
247 return (bridgeOffset + config.zone + typeOffset + 1) * 64 + commandCategory;
250 protected void start(AbstractBridgeHandler handler) {
254 public void bridgeStatusChanged(@NonNull ThingStatusInfo bridgeStatusInfo) {
255 if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
256 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
259 if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
260 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
264 AbstractBridgeHandler h = getBridgeHandler();
266 logger.warn("Bridge handler not found!");
269 final InetAddress inetAddress = h.address;
270 if (inetAddress == null) {
271 logger.warn("Bridge handler has not yet determined the IP address!");
276 configUpdated(h, inetAddress);
278 if (h.getThing().getStatus() == ThingStatus.ONLINE) {
279 updateStatus(ThingStatus.ONLINE);
282 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
287 * Called by the bridge if a configuration update happened after initialisation has been done
289 * @param h The bridge handler
291 public void configUpdated(AbstractBridgeHandler h, InetAddress address) {
292 this.port = h.config.port;
293 this.address = address;
294 this.socket = h.socket;
295 this.delayTimeMS = h.config.delayTime;
296 this.repeatTimes = h.config.repeat;
297 this.bridgeOffset = h.bridgeOffset;
301 public void initialize() {
302 config = getConfigAs(LedHandlerConfig.class);
303 bridgeStatusChanged(getBridgeStatus());