]> git.basschouten.com Git - openhab-addons.git/blob
91c68dbef27fd2bff2e6cb5c3b94ac0b2fa10fa9
[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.lib.services;
14
15 import java.awt.Color;
16 import java.io.IOException;
17 import java.net.DatagramPacket;
18 import java.net.InetAddress;
19 import java.net.MulticastSocket;
20 import java.net.NetworkInterface;
21 import java.net.SocketException;
22 import java.net.SocketTimeoutException;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.concurrent.ExecutorService;
29 import java.util.concurrent.Executors;
30 import java.util.stream.Collectors;
31
32 import org.openhab.binding.yeelight.internal.lib.device.DeviceBase;
33 import org.openhab.binding.yeelight.internal.lib.device.DeviceFactory;
34 import org.openhab.binding.yeelight.internal.lib.device.DeviceStatus;
35 import org.openhab.binding.yeelight.internal.lib.device.DeviceWithAmbientLight;
36 import org.openhab.binding.yeelight.internal.lib.device.DeviceWithNightlight;
37 import org.openhab.binding.yeelight.internal.lib.enums.DeviceAction;
38 import org.openhab.binding.yeelight.internal.lib.listeners.DeviceListener;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * The {@link DeviceManager} is a class for managing all devices.
44  *
45  * @author Coaster Li - Initial contribution
46  * @author Joe Ho - Added duration
47  * @author Nikita Pogudalov - Added name for Ceiling 1
48  */
49 public class DeviceManager {
50     private final Logger logger = LoggerFactory.getLogger(DeviceManager.class);
51
52     private static final String TAG = DeviceManager.class.getSimpleName();
53
54     private static final String MULTI_CAST_HOST = "239.255.255.250";
55     private static final int MULTI_CAST_PORT = 1982;
56     private static final String DISCOVERY_MSG = "M-SEARCH * HTTP/1.1\r\n" + "HOST:" + MULTI_CAST_HOST + ":"
57             + MULTI_CAST_PORT + "\r\n" + "MAN:\"ssdp:discover\"\r\n" + "ST:wifi_bulb\r\n";
58
59     private static final int TIMEOUT = 10000;
60
61     public static final DeviceManager sInstance = new DeviceManager();
62     public volatile boolean mSearching = false;
63
64     public Map<String, DeviceBase> mDeviceList = new HashMap<>();
65     public List<DeviceListener> mListeners = new ArrayList<>();
66
67     private ExecutorService executorService = Executors.newCachedThreadPool();
68
69     private DeviceManager() {
70     }
71
72     public static DeviceManager getInstance() {
73         return sInstance;
74     }
75
76     public void registerDeviceListener(DeviceListener listener) {
77         if (!mListeners.contains(listener)) {
78             mListeners.add(listener);
79         }
80     }
81
82     public void unregisterDeviceListener(DeviceListener listener) {
83         mListeners.remove(listener);
84     }
85
86     public void startDiscovery() {
87         startDiscovery(-1);
88     }
89
90     public void startDiscovery(long timeToStop) {
91         searchDevice();
92         if (timeToStop > 0) {
93             new Thread(() -> {
94                 try {
95                     Thread.sleep(timeToStop);
96                 } catch (InterruptedException e) {
97                     logger.debug("Interrupted while sleeping", e);
98                 } finally {
99                     stopDiscovery();
100                 }
101             }).start();
102         }
103     }
104
105     public void stopDiscovery() {
106         mSearching = false;
107     }
108
109     private void searchDevice() {
110         if (mSearching) {
111             logger.debug("{}: Already in discovery, return!", TAG);
112             return;
113         }
114
115         logger.debug("Starting Discovery");
116
117         try {
118             final InetAddress multicastAddress = InetAddress.getByName(MULTI_CAST_HOST);
119             final List<NetworkInterface> networkInterfaces = getNetworkInterfaces();
120
121             mSearching = true;
122             for (final NetworkInterface networkInterface : networkInterfaces) {
123                 logger.debug("Starting Discovery on: {}", networkInterface.getDisplayName());
124
125                 executorService.execute(() -> {
126                     try (MulticastSocket multiSocket = new MulticastSocket(MULTI_CAST_PORT)) {
127                         multiSocket.setSoTimeout(TIMEOUT);
128                         multiSocket.setNetworkInterface(networkInterface);
129                         multiSocket.joinGroup(multicastAddress);
130
131                         DatagramPacket dpSend = new DatagramPacket(DISCOVERY_MSG.getBytes(),
132                                 DISCOVERY_MSG.getBytes().length, multicastAddress, MULTI_CAST_PORT);
133                         multiSocket.send(dpSend);
134
135                         while (mSearching) {
136                             byte[] buf = new byte[1024];
137
138                             DatagramPacket dpRecv = new DatagramPacket(buf, buf.length);
139
140                             try {
141                                 multiSocket.receive(dpRecv);
142                                 byte[] bytes = dpRecv.getData();
143                                 StringBuilder buffer = new StringBuilder();
144                                 for (int i = 0; i < dpRecv.getLength(); i++) {
145                                     // parse /r
146                                     if (bytes[i] == Character.LINE_SEPARATOR) {
147                                         continue;
148                                     }
149                                     buffer.append((char) bytes[i]);
150                                 }
151
152                                 String receivedMessage = buffer.toString();
153
154                                 logger.debug("{}: got message: {}", TAG, receivedMessage);
155                                 // we can skip the request because we aren't interested in our own search broadcast
156                                 // message.
157                                 if (receivedMessage.startsWith("M-SEARCH")) {
158                                     continue;
159                                 }
160
161                                 String[] infos = receivedMessage.split("\n");
162                                 Map<String, String> bulbInfo = new HashMap<>();
163                                 for (String info : infos) {
164                                     int index = info.indexOf(':');
165                                     if (index == -1) {
166                                         continue;
167                                     }
168                                     String key = info.substring(0, index).trim();
169                                     String value = info.substring(index + 1).trim();
170
171                                     bulbInfo.put(key, value);
172                                 }
173                                 logger.debug("{}: got bulbInfo: {}", TAG, bulbInfo);
174                                 if (bulbInfo.containsKey("model") && bulbInfo.containsKey("id")) {
175                                     DeviceBase device = DeviceFactory.build(bulbInfo);
176
177                                     if (null == device) {
178                                         logger.warn("Found unsupported device.");
179                                         continue;
180                                     }
181                                     device.setDeviceName(bulbInfo.getOrDefault("name", ""));
182
183                                     if (mDeviceList.containsKey(device.getDeviceId())) {
184                                         updateDevice(mDeviceList.get(device.getDeviceId()), bulbInfo);
185                                     }
186                                     notifyDeviceFound(device);
187                                 }
188                             } catch (SocketTimeoutException e) {
189                                 logger.debug("Error timeout: {}", e.getMessage(), e);
190                             }
191
192                         }
193                     } catch (Exception e) {
194                         if (!e.getMessage().contains("No IP addresses bound to interface")) {
195                             logger.debug("Error getting ip addresses: {}", e.getMessage(), e);
196                         }
197                     }
198                 });
199             }
200         } catch (IOException e) {
201             logger.debug("Error getting ip addresses: {}", e.getMessage(), e);
202         }
203     }
204
205     private List<NetworkInterface> getNetworkInterfaces() throws SocketException {
206         return Collections.list(NetworkInterface.getNetworkInterfaces()).stream().filter(device -> {
207             try {
208                 return device.isUp() && !device.isLoopback();
209             } catch (SocketException e) {
210                 logger.debug("Failed to get device information", e);
211                 return false;
212             }
213         }).collect(Collectors.toList());
214     }
215
216     private void notifyDeviceFound(DeviceBase device) {
217         for (DeviceListener listener : mListeners) {
218             listener.onDeviceFound(device);
219         }
220     }
221
222     public void doAction(String deviceId, DeviceAction action) {
223         DeviceBase device = mDeviceList.get(deviceId);
224         if (device != null) {
225             switch (action) {
226                 case open:
227                     device.open(action.intDuration());
228                     break;
229                 case close:
230                     device.close(action.intDuration());
231                     break;
232                 case brightness:
233                     device.setBrightness(action.intValue(), action.intDuration());
234                     break;
235                 case color:
236                     device.setColor(action.intValue(), action.intDuration());
237                     break;
238                 case colortemperature:
239                     device.setCT(action.intValue(), action.intDuration());
240                     break;
241                 case increase_bright:
242                     device.increaseBrightness(action.intDuration());
243                     break;
244                 case decrease_bright:
245                     device.decreaseBrightness(action.intDuration());
246                     break;
247                 case increase_ct:
248                     device.increaseCt(action.intDuration());
249                     break;
250                 case decrease_ct:
251                     device.decreaseCt(action.intDuration());
252                     break;
253                 case background_color:
254                     if (device instanceof DeviceWithAmbientLight light) {
255                         final String[] split = action.strValue().split(",");
256
257                         light.setBackgroundColor(Integer.parseInt(split[0]), Integer.parseInt(split[1]),
258                                 action.intDuration());
259                     }
260                     break;
261                 case background_brightness:
262                     if (device instanceof DeviceWithAmbientLight light) {
263                         light.setBackgroundBrightness(action.intValue(), action.intDuration());
264                     }
265                     break;
266                 case background_on:
267                     if (device instanceof DeviceWithAmbientLight light) {
268                         light.setBackgroundPower(true, action.intDuration());
269                     }
270                     break;
271                 case background_off:
272                     if (device instanceof DeviceWithAmbientLight light) {
273                         light.setBackgroundPower(false, action.intDuration());
274                     }
275                     break;
276                 case nightlight_off:
277                     if (device instanceof DeviceWithNightlight nightlight) {
278                         nightlight.toggleNightlightMode(false);
279                     }
280                     break;
281                 case nightlight_on:
282                     if (device instanceof DeviceWithNightlight nightlight) {
283                         nightlight.toggleNightlightMode(true);
284                     }
285                     break;
286                 default:
287                     break;
288             }
289         }
290     }
291
292     public void doCustomAction(String deviceId, String action, String params) {
293         DeviceBase device = mDeviceList.get(deviceId);
294         device.sendCustomCommand(action, params);
295     }
296
297     public void addDevice(DeviceBase device) {
298         mDeviceList.put(device.getDeviceId(), device);
299     }
300
301     public void updateDevice(DeviceBase device, Map<String, String> bulbInfo) {
302         String[] addressInfo = bulbInfo.get("Location").split(":");
303         device.setAddress(addressInfo[1].substring(2));
304         device.setPort(Integer.parseInt(addressInfo[2]));
305         device.setOnline(true);
306         DeviceStatus status = device.getDeviceStatus();
307         String rgb = bulbInfo.get("rgb");
308         if (rgb != null) {
309             Color color = new Color(Integer.parseInt(rgb));
310             status.setR(color.getRed());
311             status.setG(color.getGreen());
312             status.setB(color.getBlue());
313         }
314         String ct = bulbInfo.get("ct");
315         if (ct != null) {
316             status.setCt(Integer.parseInt(ct));
317         }
318         String hue = bulbInfo.get("hue");
319         if (hue != null) {
320             status.setHue(Integer.parseInt(hue));
321         }
322         String sat = bulbInfo.get("sat");
323         if (sat != null) {
324             status.setSat(Integer.parseInt(sat));
325         }
326     }
327
328     public static String getDefaultName(DeviceBase device) {
329         if (device.getDeviceModel() != null && !"".equals(device.getDeviceName())) {
330             return device.getDeviceName();
331         }
332         switch (device.getDeviceType()) {
333             case ceiling:
334                 return "Yeelight LED Ceiling";
335             case ceiling1:
336             case ceiling3:
337             case ceil26:
338             case ceiling11:
339                 return "Yeelight LED Ceiling with night mode";
340             case ceiling4:
341                 return "Yeelight LED Ceiling with ambient light";
342             case color:
343             case color4:
344                 return "Yeelight Color LED Bulb";
345             case mono:
346                 return "Yeelight White LED Bulb";
347             case ct_bulb:
348                 return "Yeelight White LED Bulb v2";
349             case stripe:
350             case strip6:
351                 return "Yeelight Color LED Stripe";
352             case desklamp:
353                 return "Yeelight Mi LED Desk Lamp";
354             default:
355                 return "";
356         }
357     }
358 }