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.yeelight.internal.handler;
15 import static org.openhab.binding.yeelight.internal.YeelightBindingConstants.*;
17 import java.util.concurrent.TimeUnit;
19 import org.openhab.binding.yeelight.internal.lib.device.ConnectState;
20 import org.openhab.binding.yeelight.internal.lib.device.DeviceBase;
21 import org.openhab.binding.yeelight.internal.lib.device.DeviceFactory;
22 import org.openhab.binding.yeelight.internal.lib.device.DeviceStatus;
23 import org.openhab.binding.yeelight.internal.lib.enums.DeviceAction;
24 import org.openhab.binding.yeelight.internal.lib.enums.DeviceMode;
25 import org.openhab.binding.yeelight.internal.lib.enums.DeviceType;
26 import org.openhab.binding.yeelight.internal.lib.listeners.DeviceConnectionStateListener;
27 import org.openhab.binding.yeelight.internal.lib.listeners.DeviceStatusChangeListener;
28 import org.openhab.binding.yeelight.internal.lib.services.DeviceManager;
29 import org.openhab.core.library.types.DecimalType;
30 import org.openhab.core.library.types.HSBType;
31 import org.openhab.core.library.types.IncreaseDecreaseType;
32 import org.openhab.core.library.types.OnOffType;
33 import org.openhab.core.library.types.PercentType;
34 import org.openhab.core.library.types.StringType;
35 import org.openhab.core.thing.ChannelUID;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingStatus;
38 import org.openhab.core.thing.ThingStatusDetail;
39 import org.openhab.core.thing.ThingTypeUID;
40 import org.openhab.core.thing.binding.BaseThingHandler;
41 import org.openhab.core.types.Command;
42 import org.openhab.core.types.RefreshType;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
47 * The {@link YeelightHandlerBase} is responsible for handling commands, which are
48 * sent to one of the channels.
50 * @author Coaster Li - Initial contribution
51 * @author Joe Ho - Added Duration Thing parameter
52 * @author Nikita Pogudalov - Added DeviceType for ceiling 1
54 public abstract class YeelightHandlerBase extends BaseThingHandler
55 implements DeviceConnectionStateListener, DeviceStatusChangeListener {
57 private final Logger logger = LoggerFactory.getLogger(YeelightHandlerBase.class);
58 protected DeviceBase mDevice;
60 // Reading the deviceId from the properties map.
61 private String deviceId = getThing().getConfiguration().get(PARAMETER_DEVICE_ID).toString();
63 public YeelightHandlerBase(Thing thing) {
67 protected void updateUI(DeviceStatus status) {
71 public void initialize() {
72 logger.debug("Initializing, Device ID: {}", deviceId);
73 mDevice = DeviceFactory.build(getDeviceModel(getThing().getThingTypeUID()).name(), deviceId);
74 mDevice.setDeviceName(getThing().getLabel());
75 mDevice.setAutoConnect(true);
76 DeviceManager.getInstance().addDevice(mDevice);
77 mDevice.registerConnectStateListener(this);
78 mDevice.registerStatusChangedListener(this);
79 updateStatusHelper(mDevice.getConnectionState());
80 DeviceManager.getInstance().startDiscovery(15 * 1000);
84 public void dispose() {
88 private DeviceType getDeviceModel(ThingTypeUID typeUID) {
89 if (typeUID.equals(THING_TYPE_CEILING)) {
90 return DeviceType.ceiling;
91 } else if (typeUID.equals(THING_TYPE_CEILING1)) {
92 return DeviceType.ceiling1;
93 } else if (typeUID.equals(THING_TYPE_CEILING3)) {
94 return DeviceType.ceiling3;
95 } else if (typeUID.equals(THING_TYPE_CEILING4)) {
96 return DeviceType.ceiling4;
97 } else if (typeUID.equals(THING_TYPE_WONDER)) {
98 return DeviceType.color;
99 } else if (typeUID.equals(THING_TYPE_DOLPHIN)) {
100 return DeviceType.mono;
101 } else if (typeUID.equals(THING_TYPE_CTBULB)) {
102 return DeviceType.ct_bulb;
103 } else if (typeUID.equals(THING_TYPE_STRIPE)) {
104 return DeviceType.stripe;
105 } else if (typeUID.equals(THING_TYPE_DESKLAMP)) {
106 return DeviceType.desklamp;
113 public void onConnectionStateChanged(ConnectState connectState) {
114 logger.debug("onConnectionStateChanged -> {}", connectState.name());
115 updateStatusHelper(connectState);
118 public void updateStatusHelper(ConnectState connectState) {
119 switch (connectState) {
121 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Device is offline!");
122 if (mDevice.isAutoConnect()) {
123 DeviceManager.sInstance.startDiscovery(5 * 1000);
124 logger.debug("Thing OFFLINE. Initiated discovery");
128 updateStatus(ThingStatus.ONLINE);
129 mDevice.queryStatus();
132 updateStatus(ThingStatus.UNKNOWN);
138 public void channelLinked(ChannelUID channelUID) {
139 logger.debug("ChannelLinked -> {}", channelUID.getId());
140 super.channelLinked(channelUID);
142 Runnable task = () -> {
143 mDevice.queryStatus();
145 scheduler.schedule(task, 500, TimeUnit.MILLISECONDS);
148 public void handleCommandHelper(ChannelUID channelUID, Command command, String logInfo) {
149 logger.debug("{}: {}", logInfo, command);
151 // If device is disconnected, start discovery to reconnect.
152 if (mDevice.isAutoConnect() && mDevice.getConnectionState() != ConnectState.CONNECTED) {
153 DeviceManager.getInstance().startDiscovery(5 * 1000);
155 if (command instanceof RefreshType) {
156 logger.debug("Refresh channel: {} Command: {}", channelUID, command);
158 DeviceManager.getInstance().startDiscovery(5 * 1000);
159 DeviceStatus s = mDevice.getDeviceStatus();
161 switch (channelUID.getId()) {
162 case CHANNEL_BRIGHTNESS:
163 updateState(channelUID, new PercentType(s.getBrightness()));
166 updateState(channelUID, HSBType.fromRGB(s.getR(), s.getG(), s.getB()));
168 case CHANNEL_COLOR_TEMPERATURE:
169 updateState(channelUID, new PercentType(s.getCt()));
171 case CHANNEL_BACKGROUND_COLOR:
172 final HSBType hsbType = new HSBType(new DecimalType(s.getHue()), new PercentType(s.getSat()),
173 new PercentType(s.getBackgroundBrightness()));
174 updateState(channelUID, hsbType);
181 switch (channelUID.getId()) {
182 case CHANNEL_BRIGHTNESS:
183 if (command instanceof PercentType) {
184 handlePercentMessage((PercentType) command);
185 } else if (command instanceof OnOffType) {
186 handleOnOffCommand((OnOffType) command);
187 } else if (command instanceof IncreaseDecreaseType) {
188 handleIncreaseDecreaseBrightnessCommand((IncreaseDecreaseType) command);
192 if (command instanceof HSBType) {
193 HSBType hsbCommand = (HSBType) command;
194 if (hsbCommand.getBrightness().intValue() == 0) {
195 handleOnOffCommand(OnOffType.OFF);
197 handleHSBCommand(hsbCommand);
199 } else if (command instanceof PercentType) {
200 handlePercentMessage((PercentType) command);
201 } else if (command instanceof OnOffType) {
202 handleOnOffCommand((OnOffType) command);
203 } else if (command instanceof IncreaseDecreaseType) {
204 handleIncreaseDecreaseBrightnessCommand((IncreaseDecreaseType) command);
207 case CHANNEL_COLOR_TEMPERATURE:
208 if (command instanceof PercentType) {
209 handleColorTemperatureCommand((PercentType) command);
210 } else if (command instanceof IncreaseDecreaseType) {
211 handleIncreaseDecreaseBrightnessCommand((IncreaseDecreaseType) command);
215 case CHANNEL_BACKGROUND_COLOR:
216 if (command instanceof HSBType) {
217 HSBType hsbCommand = (HSBType) command;
218 handleBackgroundHSBCommand(hsbCommand);
219 } else if (command instanceof PercentType) {
220 handleBackgroundBrightnessPercentMessage((PercentType) command);
221 } else if (command instanceof OnOffType) {
222 handleBackgroundOnOffCommand((OnOffType) command);
225 case CHANNEL_NIGHTLIGHT:
226 if (command instanceof OnOffType) {
227 DeviceAction pAction = command == OnOffType.ON ? DeviceAction.nightlight_on
228 : DeviceAction.nightlight_off;
229 pAction.putDuration(getDuration());
230 DeviceManager.getInstance().doAction(deviceId, pAction);
233 case CHANNEL_COMMAND:
234 if (!command.toString().isEmpty()) {
235 String[] tokens = command.toString().split(";");
236 String methodAction = tokens[0];
237 String methodParams = "";
238 if (tokens.length > 1) {
239 methodParams = tokens[1];
241 logger.debug("{}: {} {}", logInfo, methodAction, methodParams);
242 handleCustomCommand(methodAction, methodParams);
243 updateState(channelUID, new StringType(""));
251 void handlePercentMessage(PercentType brightness) {
252 DeviceAction pAction;
253 if (brightness.intValue() == 0) {
254 pAction = DeviceAction.close;
255 pAction.putDuration(getDuration());
256 DeviceManager.getInstance().doAction(deviceId, pAction);
258 if (mDevice.getDeviceStatus().isPowerOff()) {
259 pAction = DeviceAction.open;
260 // hard coded to fast open, the duration should apply to brightness increase only
261 pAction.putDuration(0);
262 DeviceManager.getInstance().doAction(deviceId, pAction);
264 pAction = DeviceAction.brightness;
265 pAction.putValue(brightness.intValue());
266 pAction.putDuration(getDuration());
267 DeviceManager.getInstance().doAction(deviceId, pAction);
271 void handleIncreaseDecreaseBrightnessCommand(IncreaseDecreaseType increaseDecrease) {
272 DeviceAction idbAcation = increaseDecrease == IncreaseDecreaseType.INCREASE ? DeviceAction.increase_bright
273 : DeviceAction.decrease_bright;
274 idbAcation.putDuration(getDuration());
275 DeviceManager.getInstance().doAction(deviceId, idbAcation);
278 void handleIncreaseDecreaseColorTemperatureCommand(IncreaseDecreaseType increaseDecrease) {
279 DeviceAction idctAcation = increaseDecrease == IncreaseDecreaseType.INCREASE ? DeviceAction.increase_ct
280 : DeviceAction.decrease_ct;
281 idctAcation.putDuration(getDuration());
282 DeviceManager.getInstance().doAction(deviceId, idctAcation);
285 void handleOnOffCommand(OnOffType onoff) {
286 DeviceAction ofAction = onoff == OnOffType.ON ? DeviceAction.open : DeviceAction.close;
287 ofAction.putDuration(getDuration());
288 DeviceManager.getInstance().doAction(deviceId, ofAction);
291 void handleHSBCommand(HSBType color) {
292 DeviceAction cAction = DeviceAction.color;
293 cAction.putValue(color.getRGB() & 0xFFFFFF);
294 cAction.putDuration(getDuration());
295 DeviceManager.getInstance().doAction(deviceId, cAction);
298 void handleBackgroundHSBCommand(HSBType color) {
299 DeviceAction cAction = DeviceAction.background_color;
301 // TODO: actions seem to be an insufficiant abstraction.
302 cAction.putValue(color.getHue() + "," + color.getSaturation());
303 cAction.putDuration(getDuration());
304 DeviceManager.getInstance().doAction(deviceId, cAction);
307 void handleBackgroundBrightnessPercentMessage(PercentType brightness) {
308 DeviceAction pAction;
310 pAction = DeviceAction.background_brightness;
311 pAction.putValue(brightness.intValue());
312 pAction.putDuration(getDuration());
313 DeviceManager.getInstance().doAction(deviceId, pAction);
316 private void handleBackgroundOnOffCommand(OnOffType command) {
317 DeviceAction pAction = command == OnOffType.ON ? DeviceAction.background_on : DeviceAction.background_off;
318 pAction.putDuration(getDuration());
319 DeviceManager.getInstance().doAction(deviceId, pAction);
322 void handleColorTemperatureCommand(PercentType ct) {
323 DeviceAction ctAction = DeviceAction.colortemperature;
324 ctAction.putValue(COLOR_TEMPERATURE_STEP * ct.intValue() + COLOR_TEMPERATURE_MINIMUM);
325 ctAction.putDuration(getDuration());
326 DeviceManager.getInstance().doAction(deviceId, ctAction);
329 void handleCustomCommand(String action, String params) {
330 DeviceManager.getInstance().doCustomAction(deviceId, action, params);
334 public void onStatusChanged(DeviceStatus status) {
335 logger.debug("UpdateState->{}", status);
339 void updateBrightnessAndColorUI(DeviceStatus status) {
340 PercentType brightness = status.isPowerOff() ? PercentType.ZERO : new PercentType(status.getBrightness());
342 HSBType tempHsbType = HSBType.fromRGB(status.getR(), status.getG(), status.getB());
343 HSBType hsbType = status.getMode() == DeviceMode.MODE_HSV
344 ? new HSBType(new DecimalType(status.getHue()), new PercentType(status.getSat()), brightness)
345 : new HSBType(tempHsbType.getHue(), tempHsbType.getSaturation(), brightness);
347 logger.debug("Update Color->{}", hsbType);
348 updateState(CHANNEL_COLOR, hsbType);
350 logger.debug("Update CT->{}", status.getCt());
351 updateState(CHANNEL_COLOR_TEMPERATURE,
352 new PercentType((status.getCt() - COLOR_TEMPERATURE_MINIMUM) / COLOR_TEMPERATURE_STEP));
356 // Duration should not be null, but just in case do a null check.
357 return getThing().getConfiguration().get(PARAMETER_DURATION) == null ? 500
358 : ((Number) getThing().getConfiguration().get(PARAMETER_DURATION)).intValue();