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.lib.device;
15 import java.util.ArrayList;
16 import java.util.List;
18 import java.util.Map.Entry;
20 import org.openhab.binding.yeelight.internal.lib.device.connection.ConnectionBase;
21 import org.openhab.binding.yeelight.internal.lib.enums.ActiveMode;
22 import org.openhab.binding.yeelight.internal.lib.enums.DeviceMode;
23 import org.openhab.binding.yeelight.internal.lib.enums.DeviceType;
24 import org.openhab.binding.yeelight.internal.lib.enums.MethodAction;
25 import org.openhab.binding.yeelight.internal.lib.listeners.DeviceConnectionStateListener;
26 import org.openhab.binding.yeelight.internal.lib.listeners.DeviceStatusChangeListener;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
30 import com.google.gson.JsonArray;
31 import com.google.gson.JsonElement;
32 import com.google.gson.JsonObject;
33 import com.google.gson.JsonParser;
36 * The {@link DeviceBase} is a generic class for all devices.
38 * @author Coaster Li - Initial contribution
39 * @author Daniel Walters - Correct handling of brightness
40 * @author Joe Ho - Added duration to some commands
42 public abstract class DeviceBase {
43 private final Logger logger = LoggerFactory.getLogger(DeviceBase.class);
45 private static final String TAG = DeviceBase.class.getSimpleName();
47 protected String mDeviceId;
48 protected String mDeviceName;
49 protected DeviceType mDeviceType;
50 protected String mAddress;
51 private String[] mSupportProps;
53 private int mFwVersion;
54 protected boolean bIsOnline;
55 protected boolean bIsAutoConnect;
56 protected ConnectionBase mConnection;
57 protected ConnectState mConnectState = ConnectState.DISCONNECTED;
58 protected DeviceStatus mDeviceStatus;
59 private Map<String, Object> mBulbInfo = null;
60 protected List<String> mQueryList = new ArrayList<>();
62 protected int mMinCt, mMaxCt;
64 List<DeviceConnectionStateListener> mConnectionListeners = new ArrayList<>();
65 List<DeviceStatusChangeListener> mStatusChangeListeners = new ArrayList<>();
67 public DeviceBase(String id) {
69 mDeviceStatus = new DeviceStatus();
72 public DeviceBase(String id, boolean isAutoConnect) {
74 this.bIsAutoConnect = isAutoConnect;
77 public void onNotify(String response) {
78 boolean needNotify = true;
79 JsonObject message = JsonParser.parseString(response).getAsJsonObject();
81 if (message.has("method")) {
82 String method = message.get("method").toString().replace("\"", "");
83 if (method.equals("props")) {// Property notify
84 String params = message.get("params").toString();
85 JsonObject propsObject = JsonParser.parseString(params).getAsJsonObject();
86 for (Entry<String, JsonElement> prop : propsObject.entrySet()) {
87 final YeelightDeviceProperty property = YeelightDeviceProperty.fromString(prop.getKey());
88 if (null == property) {
89 logger.debug("Unhandled property: {}", prop.getKey());
95 if (prop.getValue().toString().equals("\"off\"")) {
96 mDeviceStatus.setPowerOff(true);
97 } else if (prop.getValue().toString().equals("\"on\"")) {
98 mDeviceStatus.setPowerOff(false);
102 mDeviceStatus.setBrightness(prop.getValue().getAsInt());
105 mDeviceStatus.setCt(prop.getValue().getAsInt());
106 mDeviceStatus.setMode(DeviceMode.MODE_SUNHINE);
109 mDeviceStatus.setMode(DeviceMode.MODE_COLOR);
110 int color = prop.getValue().getAsInt();
111 mDeviceStatus.setColor(color);
113 mDeviceStatus.setR((color >> 16) & 0xFF);
114 mDeviceStatus.setG((color >> 8) & 0xFF);
115 mDeviceStatus.setB(color & 0xFF);
119 mDeviceStatus.setMode(DeviceMode.MODE_HSV);
120 mDeviceStatus.setHue(prop.getValue().getAsInt());
123 mDeviceStatus.setMode(DeviceMode.MODE_HSV);
124 mDeviceStatus.setSat(prop.getValue().getAsInt());
127 switch (prop.getValue().getAsInt()) {
128 case DeviceStatus.MODE_COLOR:
129 mDeviceStatus.setMode(DeviceMode.MODE_COLOR);
131 case DeviceStatus.MODE_COLORTEMPERATURE:
132 mDeviceStatus.setMode(DeviceMode.MODE_SUNHINE);
134 case DeviceStatus.MODE_HSV:
135 mDeviceStatus.setMode(DeviceMode.MODE_HSV);
142 mDeviceStatus.setIsFlowing(prop.getValue().getAsInt() == 1);
145 // {"method":"props","params":{"flow_params":"0,0,1000,1,15935488,31,1000,1,13366016,31,1000,1,62370,31,1000,1,7995635,31"}}
146 String[] flowStrs = prop.getValue().toString().replace("\"", "").split(",");
147 if (flowStrs.length > 2 && (flowStrs.length - 2) % 4 == 0) {
148 mDeviceStatus.setFlowCount(Integer.parseInt(flowStrs[0]));
149 mDeviceStatus.setFlowEndAction(Integer.parseInt(flowStrs[1]));
150 if (mDeviceStatus.getFlowItems() == null) {
151 mDeviceStatus.setFlowItems(new ArrayList<>());
153 mDeviceStatus.getFlowItems().clear();
154 for (int i = 0; i < ((flowStrs.length - 2) / 4); i++) {
155 ColorFlowItem item = new ColorFlowItem();
156 item.duration = Integer.valueOf(flowStrs[4 * i + 2]);
157 item.mode = Integer.valueOf(flowStrs[4 * i + 3]);
158 item.value = Integer.valueOf(flowStrs[4 * i + 4]);
159 item.brightness = Integer.valueOf(flowStrs[4 * i + 5]);
160 mDeviceStatus.getFlowItems().add(item);
165 int delayOff = prop.getValue().getAsInt();
166 if (delayOff > 0 && delayOff <= 60) {
167 mDeviceStatus.setDelayOff(delayOff);
169 mDeviceStatus.setDelayOff(DeviceStatus.DEFAULT_NO_DELAY);
173 mDeviceStatus.setMusicOn(prop.getValue().getAsInt() == 1);
176 mDeviceName = prop.getValue().toString();
179 int color = prop.getValue().getAsInt();
180 mDeviceStatus.setBackgroundR((color >> 16) & 0xFF);
181 mDeviceStatus.setBackgroundG((color >> 8) & 0xFF);
182 mDeviceStatus.setBackgroundB(color & 0xFF);
186 mDeviceStatus.setBackgroundHue(prop.getValue().getAsInt());
190 mDeviceStatus.setBackgroundSat(prop.getValue().getAsInt());
193 mDeviceStatus.setBackgroundBrightness(prop.getValue().getAsInt());
196 if ("\"off\"".equals(prop.getValue().toString())) {
197 mDeviceStatus.setBackgroundIsPowerOff(true);
198 } else if ("\"on\"".equals(prop.getValue().toString())) {
199 mDeviceStatus.setBackgroundIsPowerOff(false);
203 // when the light is switched from nightlight-mode to sunlight-mode it will send nl_br:0
204 // therefore we have to ignore the case.
205 final int intValue = prop.getValue().getAsInt();
207 mDeviceStatus.setBrightness(intValue);
211 int activeModeInt = prop.getValue().getAsInt();
212 final ActiveMode activeMode = ActiveMode.values()[activeModeInt];
213 mDeviceStatus.setActiveMode(activeMode);
216 logger.debug("Maybe unsupported property: {} - {}", property, prop.getKey());
222 } else if (message.has("id") && message.has("result")) {
223 // no method, but result : ["ok"]
224 JsonArray result = message.get("result").getAsJsonArray();
225 if (result.get(0).toString().equals("\"ok\"")) {
226 logger.info("######### this is control command response, don't need to notify status change!");
232 logger.info("status = {}", mDeviceStatus);
233 for (DeviceStatusChangeListener statusChangeListener : mStatusChangeListeners) {
234 statusChangeListener.onStatusChanged(mDeviceStatus);
237 } catch (Exception e) {
238 logger.debug("Exception", e);
242 public void open(int duration) {
244 new DeviceMethod(MethodAction.SWITCH, new Object[] { "on", DeviceMethod.EFFECT_SMOOTH, duration }));
247 public void close(int duration) {
249 new DeviceMethod(MethodAction.SWITCH, new Object[] { "off", DeviceMethod.EFFECT_SMOOTH, duration }));
252 public void decreaseBrightness(int duration) {
253 int bright = getDeviceStatus().getBrightness() - 10;
257 setBrightness(bright, duration);
261 public void increaseBrightness(int duration) {
262 int bright = getDeviceStatus().getBrightness() + 10;
266 setBrightness(bright, duration);
269 public void setBrightness(int brightness, int duration) {
270 mConnection.invoke(MethodFactory.buildBrightnessMethd(brightness, DeviceMethod.EFFECT_SMOOTH, duration));
273 public void setColor(int color, int duration) {
274 mConnection.invoke(MethodFactory.buildRgbMethod(color, DeviceMethod.EFFECT_SMOOTH, duration));
277 public void increaseCt(int duration) {
278 int ct = getDeviceStatus().getCt() - ((mMaxCt - mMinCt) / 10);
285 public void decreaseCt(int duration) {
286 int ct = getDeviceStatus().getCt() + ((mMaxCt - mMinCt) / 10);
293 public void setCT(int ct, int duration) {
294 mConnection.invoke(MethodFactory.buildCTMethod(ct, DeviceMethod.EFFECT_SMOOTH, duration));
297 public void connect() {
298 setConnectionState(ConnectState.CONNECTTING);
299 mConnection.connect();
302 public void setConnectionState(ConnectState connectState) {
303 logger.debug("{}: set connection state to: {}", TAG, connectState.name());
304 if (connectState == ConnectState.DISCONNECTED) {
307 if (mConnectState != connectState) {
308 mConnectState = connectState;
309 if (mConnectionListeners != null) {
310 for (DeviceConnectionStateListener listener : mConnectionListeners) {
311 listener.onConnectionStateChanged(mConnectState);
317 public void sendCustomCommand(String action, String params) {
318 mConnection.invokeCustom(new DeviceMethod(action, params));
321 // ===================== setter and getter=====================
323 public String getDeviceId() {
327 public void setDeviceName(String name) {
331 public String getDeviceName() {
335 public DeviceType getDeviceType() {
339 public String getDeviceModel() {
340 return mDeviceType.name();
343 public String getAddress() {
347 public void setAddress(String address) {
348 this.mAddress = address;
351 public int getPort() {
355 public void setPort(int port) {
359 public boolean isAutoConnect() {
360 return bIsAutoConnect;
363 public int getFwVersion() {
367 public void setFwVersion(int fwVersion) {
368 this.mFwVersion = fwVersion;
371 public Map<String, Object> getBulbInfo() {
375 public void setBulbInfo(Map<String, Object> bulbInfo) {
376 this.mBulbInfo = bulbInfo;
379 public String[] getSupportProps() {
380 return mSupportProps;
383 public void setSupportProps(String[] supportProps) {
384 this.mSupportProps = supportProps;
387 public void setAutoConnect(boolean isAutoConnect) {
388 if (bIsAutoConnect != isAutoConnect) {
389 this.bIsAutoConnect = isAutoConnect;
394 public DeviceStatus getDeviceStatus() {
395 return mDeviceStatus;
398 public void registerConnectStateListener(DeviceConnectionStateListener listener) {
399 mConnectionListeners.add(listener);
402 public void registerStatusChangedListener(DeviceStatusChangeListener listener) {
403 mStatusChangeListeners.add(listener);
406 public void setOnline(boolean isOnline) {
407 bIsOnline = isOnline;
411 public boolean isOnline() {
415 public ConnectState getConnectionState() {
416 return mConnectState;
419 public void queryStatus() {
420 DeviceMethod cmd = MethodFactory.buildQuery(this);
421 mQueryList.add(cmd.getCmdId());
422 mConnection.invoke(cmd);
425 private void checkAutoConnect() {
427 "{}: CheckAutoConnect: online: {}, autoConnect: {}, connection state: {}, device = {}, device id: {}",
428 TAG, bIsOnline, bIsAutoConnect, mConnectState.name(), this, this.getDeviceId());
429 if (bIsOnline && bIsAutoConnect && mConnectState == ConnectState.DISCONNECTED) {