]> git.basschouten.com Git - openhab-addons.git/blob
5b6fdea950058cb563fa5c6c4f51a9bbc644a608
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.yeelight.internal.handler;
14
15 import static org.openhab.binding.yeelight.internal.YeelightBindingConstants.*;
16
17 import java.util.concurrent.TimeUnit;
18
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;
45
46 /**
47  * The {@link YeelightHandlerBase} is responsible for handling commands, which are
48  * sent to one of the channels.
49  *
50  * @author Coaster Li - Initial contribution
51  * @author Joe Ho - Added Duration Thing parameter
52  * @author Nikita Pogudalov - Added DeviceType for ceiling 1
53  */
54 public abstract class YeelightHandlerBase extends BaseThingHandler
55         implements DeviceConnectionStateListener, DeviceStatusChangeListener {
56
57     private final Logger logger = LoggerFactory.getLogger(YeelightHandlerBase.class);
58     protected DeviceBase mDevice;
59
60     // Reading the deviceId from the properties map.
61     private String deviceId = getThing().getConfiguration().get(PARAMETER_DEVICE_ID).toString();
62
63     public YeelightHandlerBase(Thing thing) {
64         super(thing);
65     }
66
67     protected void updateUI(DeviceStatus status) {
68     }
69
70     @Override
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);
81     }
82
83     @Override
84     public void dispose() {
85         mDevice.disconnect();
86     }
87
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;
107         } else {
108             return null;
109         }
110     }
111
112     @Override
113     public void onConnectionStateChanged(ConnectState connectState) {
114         logger.debug("onConnectionStateChanged -> {}", connectState.name());
115         updateStatusHelper(connectState);
116     }
117
118     public void updateStatusHelper(ConnectState connectState) {
119         switch (connectState) {
120             case DISCONNECTED:
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");
125                 }
126                 break;
127             case CONNECTED:
128                 updateStatus(ThingStatus.ONLINE);
129                 mDevice.queryStatus();
130                 break;
131             default:
132                 updateStatus(ThingStatus.UNKNOWN);
133                 break;
134         }
135     }
136
137     @Override
138     public void channelLinked(ChannelUID channelUID) {
139         logger.debug("ChannelLinked -> {}", channelUID.getId());
140         super.channelLinked(channelUID);
141
142         Runnable task = () -> {
143             mDevice.queryStatus();
144         };
145         scheduler.schedule(task, 500, TimeUnit.MILLISECONDS);
146     }
147
148     public void handleCommandHelper(ChannelUID channelUID, Command command, String logInfo) {
149         logger.debug("{}: {}", logInfo, command);
150
151         // If device is disconnected, start discovery to reconnect.
152         if (mDevice.isAutoConnect() && mDevice.getConnectionState() != ConnectState.CONNECTED) {
153             DeviceManager.getInstance().startDiscovery(5 * 1000);
154         }
155         if (command instanceof RefreshType) {
156             logger.debug("Refresh channel: {} Command: {}", channelUID, command);
157
158             DeviceManager.getInstance().startDiscovery(5 * 1000);
159             DeviceStatus s = mDevice.getDeviceStatus();
160
161             switch (channelUID.getId()) {
162                 case CHANNEL_BRIGHTNESS:
163                     updateState(channelUID, new PercentType(s.getBrightness()));
164                     break;
165                 case CHANNEL_COLOR:
166                     updateState(channelUID, HSBType.fromRGB(s.getR(), s.getG(), s.getB()));
167                     break;
168                 case CHANNEL_COLOR_TEMPERATURE:
169                     updateState(channelUID, new PercentType(s.getCt()));
170                     break;
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);
175                     break;
176                 default:
177                     break;
178             }
179             return;
180         }
181         switch (channelUID.getId()) {
182             case CHANNEL_BRIGHTNESS:
183                 if (command instanceof PercentType percentCommand) {
184                     handlePercentMessage(percentCommand);
185                 } else if (command instanceof OnOffType onOffCommand) {
186                     handleOnOffCommand(onOffCommand);
187                 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
188                     handleIncreaseDecreaseBrightnessCommand(increaseDecreaseCommand);
189                 }
190                 break;
191             case CHANNEL_COLOR:
192                 if (command instanceof HSBType hsbCommand) {
193                     if (hsbCommand.getBrightness().intValue() == 0) {
194                         handleOnOffCommand(OnOffType.OFF);
195                     } else {
196                         handleHSBCommand(hsbCommand);
197                     }
198                 } else if (command instanceof PercentType percentCommand) {
199                     handlePercentMessage(percentCommand);
200                 } else if (command instanceof OnOffType onOffCommand) {
201                     handleOnOffCommand(onOffCommand);
202                 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
203                     handleIncreaseDecreaseBrightnessCommand(increaseDecreaseCommand);
204                 }
205                 break;
206             case CHANNEL_COLOR_TEMPERATURE:
207                 if (command instanceof PercentType percentCommand) {
208                     handleColorTemperatureCommand(percentCommand);
209                 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
210                     handleIncreaseDecreaseBrightnessCommand(increaseDecreaseCommand);
211                 }
212                 break;
213
214             case CHANNEL_BACKGROUND_COLOR:
215                 if (command instanceof HSBType hsbCommand) {
216                     handleBackgroundHSBCommand(hsbCommand);
217                 } else if (command instanceof PercentType percentCommand) {
218                     handleBackgroundBrightnessPercentMessage(percentCommand);
219                 } else if (command instanceof OnOffType onOffCommand) {
220                     handleBackgroundOnOffCommand(onOffCommand);
221                 }
222                 break;
223             case CHANNEL_NIGHTLIGHT:
224                 if (command instanceof OnOffType) {
225                     DeviceAction pAction = command == OnOffType.ON ? DeviceAction.nightlight_on
226                             : DeviceAction.nightlight_off;
227                     pAction.putDuration(getDuration());
228                     DeviceManager.getInstance().doAction(deviceId, pAction);
229                 }
230                 break;
231             case CHANNEL_COMMAND:
232                 if (!command.toString().isEmpty()) {
233                     String[] tokens = command.toString().split(";");
234                     String methodAction = tokens[0];
235                     String methodParams = "";
236                     if (tokens.length > 1) {
237                         methodParams = tokens[1];
238                     }
239                     logger.debug("{}: {} {}", logInfo, methodAction, methodParams);
240                     handleCustomCommand(methodAction, methodParams);
241                     updateState(channelUID, new StringType(""));
242                 }
243                 break;
244             default:
245                 break;
246         }
247     }
248
249     void handlePercentMessage(PercentType brightness) {
250         DeviceAction pAction;
251         if (brightness.intValue() == 0) {
252             pAction = DeviceAction.close;
253             pAction.putDuration(getDuration());
254             DeviceManager.getInstance().doAction(deviceId, pAction);
255         } else {
256             if (mDevice.getDeviceStatus().isPowerOff()) {
257                 pAction = DeviceAction.open;
258                 // hard coded to fast open, the duration should apply to brightness increase only
259                 pAction.putDuration(0);
260                 DeviceManager.getInstance().doAction(deviceId, pAction);
261             }
262             pAction = DeviceAction.brightness;
263             pAction.putValue(brightness.intValue());
264             pAction.putDuration(getDuration());
265             DeviceManager.getInstance().doAction(deviceId, pAction);
266         }
267     }
268
269     void handleIncreaseDecreaseBrightnessCommand(IncreaseDecreaseType increaseDecrease) {
270         DeviceAction idbAcation = increaseDecrease == IncreaseDecreaseType.INCREASE ? DeviceAction.increase_bright
271                 : DeviceAction.decrease_bright;
272         idbAcation.putDuration(getDuration());
273         DeviceManager.getInstance().doAction(deviceId, idbAcation);
274     }
275
276     void handleIncreaseDecreaseColorTemperatureCommand(IncreaseDecreaseType increaseDecrease) {
277         DeviceAction idctAcation = increaseDecrease == IncreaseDecreaseType.INCREASE ? DeviceAction.increase_ct
278                 : DeviceAction.decrease_ct;
279         idctAcation.putDuration(getDuration());
280         DeviceManager.getInstance().doAction(deviceId, idctAcation);
281     }
282
283     void handleOnOffCommand(OnOffType onoff) {
284         DeviceAction ofAction = onoff == OnOffType.ON ? DeviceAction.open : DeviceAction.close;
285         ofAction.putDuration(getDuration());
286         DeviceManager.getInstance().doAction(deviceId, ofAction);
287     }
288
289     void handleHSBCommand(HSBType color) {
290         DeviceAction cAction = DeviceAction.color;
291         cAction.putValue(color.getRGB() & 0xFFFFFF);
292         cAction.putDuration(getDuration());
293         DeviceManager.getInstance().doAction(deviceId, cAction);
294     }
295
296     void handleBackgroundHSBCommand(HSBType color) {
297         DeviceAction cAction = DeviceAction.background_color;
298
299         // TODO: actions seem to be an insufficiant abstraction.
300         cAction.putValue(color.getHue() + "," + color.getSaturation());
301         cAction.putDuration(getDuration());
302         DeviceManager.getInstance().doAction(deviceId, cAction);
303     }
304
305     void handleBackgroundBrightnessPercentMessage(PercentType brightness) {
306         DeviceAction pAction;
307
308         pAction = DeviceAction.background_brightness;
309         pAction.putValue(brightness.intValue());
310         pAction.putDuration(getDuration());
311         DeviceManager.getInstance().doAction(deviceId, pAction);
312     }
313
314     private void handleBackgroundOnOffCommand(OnOffType command) {
315         DeviceAction pAction = command == OnOffType.ON ? DeviceAction.background_on : DeviceAction.background_off;
316         pAction.putDuration(getDuration());
317         DeviceManager.getInstance().doAction(deviceId, pAction);
318     }
319
320     void handleColorTemperatureCommand(PercentType ct) {
321         DeviceAction ctAction = DeviceAction.colortemperature;
322         ctAction.putValue(COLOR_TEMPERATURE_STEP * ct.intValue() + COLOR_TEMPERATURE_MINIMUM);
323         ctAction.putDuration(getDuration());
324         DeviceManager.getInstance().doAction(deviceId, ctAction);
325     }
326
327     void handleCustomCommand(String action, String params) {
328         DeviceManager.getInstance().doCustomAction(deviceId, action, params);
329     }
330
331     @Override
332     public void onStatusChanged(DeviceStatus status) {
333         logger.debug("UpdateState->{}", status);
334         updateUI(status);
335     }
336
337     void updateBrightnessAndColorUI(DeviceStatus status) {
338         PercentType brightness = status.isPowerOff() ? PercentType.ZERO : new PercentType(status.getBrightness());
339
340         HSBType tempHsbType = HSBType.fromRGB(status.getR(), status.getG(), status.getB());
341         HSBType hsbType = status.getMode() == DeviceMode.MODE_HSV
342                 ? new HSBType(new DecimalType(status.getHue()), new PercentType(status.getSat()), brightness)
343                 : new HSBType(tempHsbType.getHue(), tempHsbType.getSaturation(), brightness);
344
345         logger.debug("Update Color->{}", hsbType);
346         updateState(CHANNEL_COLOR, hsbType);
347
348         logger.debug("Update CT->{}", status.getCt());
349         updateState(CHANNEL_COLOR_TEMPERATURE,
350                 new PercentType((status.getCt() - COLOR_TEMPERATURE_MINIMUM) / COLOR_TEMPERATURE_STEP));
351     }
352
353     int getDuration() {
354         // Duration should not be null, but just in case do a null check.
355         return getThing().getConfiguration().get(PARAMETER_DURATION) == null ? 500
356                 : ((Number) getThing().getConfiguration().get(PARAMETER_DURATION)).intValue();
357     }
358 }