]> git.basschouten.com Git - openhab-addons.git/blob
827d5543e3e9c375ae06eebde12852ac513ac6a6
[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     private DeviceType getDeviceModel(ThingTypeUID typeUID) {
84         if (typeUID.equals(THING_TYPE_CEILING)) {
85             return DeviceType.ceiling;
86         } else if (typeUID.equals(THING_TYPE_CEILING1)) {
87             return DeviceType.ceiling1;
88         } else if (typeUID.equals(THING_TYPE_CEILING3)) {
89             return DeviceType.ceiling3;
90         } else if (typeUID.equals(THING_TYPE_CEILING4)) {
91             return DeviceType.ceiling4;
92         } else if (typeUID.equals(THING_TYPE_WONDER)) {
93             return DeviceType.color;
94         } else if (typeUID.equals(THING_TYPE_DOLPHIN)) {
95             return DeviceType.mono;
96         } else if (typeUID.equals(THING_TYPE_CTBULB)) {
97             return DeviceType.ct_bulb;
98         } else if (typeUID.equals(THING_TYPE_STRIPE)) {
99             return DeviceType.stripe;
100         } else if (typeUID.equals(THING_TYPE_DESKLAMP)) {
101             return DeviceType.desklamp;
102         } else {
103             return null;
104         }
105     }
106
107     @Override
108     public void onConnectionStateChanged(ConnectState connectState) {
109         logger.debug("onConnectionStateChanged -> {}", connectState.name());
110         updateStatusHelper(connectState);
111     }
112
113     public void updateStatusHelper(ConnectState connectState) {
114         switch (connectState) {
115             case DISCONNECTED:
116                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Device is offline!");
117                 if (mDevice.isAutoConnect()) {
118                     DeviceManager.sInstance.startDiscovery(5 * 1000);
119                     logger.debug("Thing OFFLINE. Initiated discovery");
120                 }
121                 break;
122             case CONNECTED:
123                 updateStatus(ThingStatus.ONLINE);
124                 mDevice.queryStatus();
125                 break;
126             default:
127                 updateStatus(ThingStatus.UNKNOWN);
128                 break;
129         }
130     }
131
132     @Override
133     public void channelLinked(ChannelUID channelUID) {
134         logger.debug("ChannelLinked -> {}", channelUID.getId());
135         super.channelLinked(channelUID);
136
137         Runnable task = () -> {
138             mDevice.queryStatus();
139         };
140         scheduler.schedule(task, 500, TimeUnit.MILLISECONDS);
141     }
142
143     public void handleCommandHelper(ChannelUID channelUID, Command command, String logInfo) {
144         logger.debug("{}: {}", logInfo, command);
145
146         // If device is disconnected, start discovery to reconnect.
147         if (mDevice.isAutoConnect() && mDevice.getConnectionState() != ConnectState.CONNECTED) {
148             DeviceManager.getInstance().startDiscovery(5 * 1000);
149         }
150         if (command instanceof RefreshType) {
151             logger.debug("Refresh channel: {} Command: {}", channelUID, command);
152
153             DeviceManager.getInstance().startDiscovery(5 * 1000);
154             DeviceStatus s = mDevice.getDeviceStatus();
155
156             switch (channelUID.getId()) {
157                 case CHANNEL_BRIGHTNESS:
158                     updateState(channelUID, new PercentType(s.getBrightness()));
159                     break;
160                 case CHANNEL_COLOR:
161                     updateState(channelUID, HSBType.fromRGB(s.getR(), s.getG(), s.getB()));
162                     break;
163                 case CHANNEL_COLOR_TEMPERATURE:
164                     updateState(channelUID, new PercentType(s.getCt()));
165                     break;
166                 case CHANNEL_BACKGROUND_COLOR:
167                     final HSBType hsbType = new HSBType(new DecimalType(s.getHue()), new PercentType(s.getSat()),
168                             new PercentType(s.getBackgroundBrightness()));
169                     updateState(channelUID, hsbType);
170                     break;
171                 default:
172                     break;
173             }
174             return;
175         }
176         switch (channelUID.getId()) {
177             case CHANNEL_BRIGHTNESS:
178                 if (command instanceof PercentType) {
179                     handlePercentMessage((PercentType) command);
180                 } else if (command instanceof OnOffType) {
181                     handleOnOffCommand((OnOffType) command);
182                 } else if (command instanceof IncreaseDecreaseType) {
183                     handleIncreaseDecreaseBrightnessCommand((IncreaseDecreaseType) command);
184                 }
185                 break;
186             case CHANNEL_COLOR:
187                 if (command instanceof HSBType) {
188                     HSBType hsbCommand = (HSBType) command;
189                     if (hsbCommand.getBrightness().intValue() == 0) {
190                         handleOnOffCommand(OnOffType.OFF);
191                     } else {
192                         handleHSBCommand(hsbCommand);
193                     }
194                 } else if (command instanceof PercentType) {
195                     handlePercentMessage((PercentType) command);
196                 } else if (command instanceof OnOffType) {
197                     handleOnOffCommand((OnOffType) command);
198                 } else if (command instanceof IncreaseDecreaseType) {
199                     handleIncreaseDecreaseBrightnessCommand((IncreaseDecreaseType) command);
200                 }
201                 break;
202             case CHANNEL_COLOR_TEMPERATURE:
203                 if (command instanceof PercentType) {
204                     handleColorTemperatureCommand((PercentType) command);
205                 } else if (command instanceof IncreaseDecreaseType) {
206                     handleIncreaseDecreaseBrightnessCommand((IncreaseDecreaseType) command);
207                 }
208                 break;
209
210             case CHANNEL_BACKGROUND_COLOR:
211                 if (command instanceof HSBType) {
212                     HSBType hsbCommand = (HSBType) command;
213                     handleBackgroundHSBCommand(hsbCommand);
214                 } else if (command instanceof PercentType) {
215                     handleBackgroundBrightnessPercentMessage((PercentType) command);
216                 } else if (command instanceof OnOffType) {
217                     handleBackgroundOnOffCommand((OnOffType) command);
218                 }
219                 break;
220             case CHANNEL_NIGHTLIGHT:
221                 if (command instanceof OnOffType) {
222                     DeviceAction pAction = command == OnOffType.ON ? DeviceAction.nightlight_on
223                             : DeviceAction.nightlight_off;
224                     pAction.putDuration(getDuration());
225                     DeviceManager.getInstance().doAction(deviceId, pAction);
226                 }
227                 break;
228             case CHANNEL_COMMAND:
229                 if (!command.toString().isEmpty()) {
230                     String[] tokens = command.toString().split(";");
231                     String methodAction = tokens[0];
232                     String methodParams = "";
233                     if (tokens.length > 1) {
234                         methodParams = tokens[1];
235                     }
236                     logger.debug("{}: {} {}", logInfo, methodAction, methodParams);
237                     handleCustomCommand(methodAction, methodParams);
238                     updateState(channelUID, new StringType(""));
239                 }
240                 break;
241             default:
242                 break;
243         }
244     }
245
246     void handlePercentMessage(PercentType brightness) {
247         DeviceAction pAction;
248         if (brightness.intValue() == 0) {
249             pAction = DeviceAction.close;
250             pAction.putDuration(getDuration());
251             DeviceManager.getInstance().doAction(deviceId, pAction);
252         } else {
253             if (mDevice.getDeviceStatus().isPowerOff()) {
254                 pAction = DeviceAction.open;
255                 // hard coded to fast open, the duration should apply to brightness increase only
256                 pAction.putDuration(0);
257                 DeviceManager.getInstance().doAction(deviceId, pAction);
258             }
259             pAction = DeviceAction.brightness;
260             pAction.putValue(brightness.intValue());
261             pAction.putDuration(getDuration());
262             DeviceManager.getInstance().doAction(deviceId, pAction);
263         }
264     }
265
266     void handleIncreaseDecreaseBrightnessCommand(IncreaseDecreaseType increaseDecrease) {
267         DeviceAction idbAcation = increaseDecrease == IncreaseDecreaseType.INCREASE ? DeviceAction.increase_bright
268                 : DeviceAction.decrease_bright;
269         idbAcation.putDuration(getDuration());
270         DeviceManager.getInstance().doAction(deviceId, idbAcation);
271     }
272
273     void handleIncreaseDecreaseColorTemperatureCommand(IncreaseDecreaseType increaseDecrease) {
274         DeviceAction idctAcation = increaseDecrease == IncreaseDecreaseType.INCREASE ? DeviceAction.increase_ct
275                 : DeviceAction.decrease_ct;
276         idctAcation.putDuration(getDuration());
277         DeviceManager.getInstance().doAction(deviceId, idctAcation);
278     }
279
280     void handleOnOffCommand(OnOffType onoff) {
281         DeviceAction ofAction = onoff == OnOffType.ON ? DeviceAction.open : DeviceAction.close;
282         ofAction.putDuration(getDuration());
283         DeviceManager.getInstance().doAction(deviceId, ofAction);
284     }
285
286     void handleHSBCommand(HSBType color) {
287         DeviceAction cAction = DeviceAction.color;
288         cAction.putValue(color.getRGB() & 0xFFFFFF);
289         cAction.putDuration(getDuration());
290         DeviceManager.getInstance().doAction(deviceId, cAction);
291     }
292
293     void handleBackgroundHSBCommand(HSBType color) {
294         DeviceAction cAction = DeviceAction.background_color;
295
296         // TODO: actions seem to be an insufficiant abstraction.
297         cAction.putValue(color.getHue() + "," + color.getSaturation());
298         cAction.putDuration(getDuration());
299         DeviceManager.getInstance().doAction(deviceId, cAction);
300     }
301
302     void handleBackgroundBrightnessPercentMessage(PercentType brightness) {
303         DeviceAction pAction;
304
305         pAction = DeviceAction.background_brightness;
306         pAction.putValue(brightness.intValue());
307         pAction.putDuration(getDuration());
308         DeviceManager.getInstance().doAction(deviceId, pAction);
309     }
310
311     private void handleBackgroundOnOffCommand(OnOffType command) {
312         DeviceAction pAction = command == OnOffType.ON ? DeviceAction.background_on : DeviceAction.background_off;
313         pAction.putDuration(getDuration());
314         DeviceManager.getInstance().doAction(deviceId, pAction);
315     }
316
317     void handleColorTemperatureCommand(PercentType ct) {
318         DeviceAction ctAction = DeviceAction.colortemperature;
319         ctAction.putValue(COLOR_TEMPERATURE_STEP * ct.intValue() + COLOR_TEMPERATURE_MINIMUM);
320         ctAction.putDuration(getDuration());
321         DeviceManager.getInstance().doAction(deviceId, ctAction);
322     }
323
324     void handleCustomCommand(String action, String params) {
325         DeviceManager.getInstance().doCustomAction(deviceId, action, params);
326     }
327
328     @Override
329     public void onStatusChanged(DeviceStatus status) {
330         logger.debug("UpdateState->{}", status);
331         updateUI(status);
332     }
333
334     void updateBrightnessAndColorUI(DeviceStatus status) {
335         PercentType brightness = status.isPowerOff() ? PercentType.ZERO : new PercentType(status.getBrightness());
336
337         HSBType tempHsbType = HSBType.fromRGB(status.getR(), status.getG(), status.getB());
338         HSBType hsbType = status.getMode() == DeviceMode.MODE_HSV
339                 ? new HSBType(new DecimalType(status.getHue()), new PercentType(status.getSat()), brightness)
340                 : new HSBType(tempHsbType.getHue(), tempHsbType.getSaturation(), brightness);
341
342         logger.debug("Update Color->{}", hsbType);
343         updateState(CHANNEL_COLOR, hsbType);
344
345         logger.debug("Update CT->{}", status.getCt());
346         updateState(CHANNEL_COLOR_TEMPERATURE,
347                 new PercentType((status.getCt() - COLOR_TEMPERATURE_MINIMUM) / COLOR_TEMPERATURE_STEP));
348     }
349
350     int getDuration() {
351         // Duration should not be null, but just in case do a null check.
352         return getThing().getConfiguration().get(PARAMETER_DURATION) == null ? 500
353                 : ((Number) getThing().getConfiguration().get(PARAMETER_DURATION)).intValue();
354     }
355 }