]> git.basschouten.com Git - openhab-addons.git/blob
a962671c83624b5924188d36028a8e4fd07df163
[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) {
255                         final String[] split = action.strValue().split(",");
256
257                         ((DeviceWithAmbientLight) device).setBackgroundColor(Integer.parseInt(split[0]),
258                                 Integer.parseInt(split[1]), action.intDuration());
259                     }
260                     break;
261                 case background_brightness:
262                     if (device instanceof DeviceWithAmbientLight) {
263                         ((DeviceWithAmbientLight) device).setBackgroundBrightness(action.intValue(),
264                                 action.intDuration());
265                     }
266                     break;
267                 case background_on:
268                     if (device instanceof DeviceWithAmbientLight) {
269                         ((DeviceWithAmbientLight) device).setBackgroundPower(true, action.intDuration());
270                     }
271                     break;
272                 case background_off:
273                     if (device instanceof DeviceWithAmbientLight) {
274                         ((DeviceWithAmbientLight) device).setBackgroundPower(false, action.intDuration());
275                     }
276                     break;
277                 case nightlight_off:
278                     if (device instanceof DeviceWithNightlight) {
279                         ((DeviceWithNightlight) device).toggleNightlightMode(false);
280                     }
281                     break;
282                 case nightlight_on:
283                     if (device instanceof DeviceWithNightlight) {
284                         ((DeviceWithNightlight) device).toggleNightlightMode(true);
285                     }
286                     break;
287                 default:
288                     break;
289             }
290         }
291     }
292
293     public void doCustomAction(String deviceId, String action, String params) {
294         DeviceBase device = mDeviceList.get(deviceId);
295         device.sendCustomCommand(action, params);
296     }
297
298     public void addDevice(DeviceBase device) {
299         mDeviceList.put(device.getDeviceId(), device);
300     }
301
302     public void updateDevice(DeviceBase device, Map<String, String> bulbInfo) {
303         String[] addressInfo = bulbInfo.get("Location").split(":");
304         device.setAddress(addressInfo[1].substring(2));
305         device.setPort(Integer.parseInt(addressInfo[2]));
306         device.setOnline(true);
307         DeviceStatus status = device.getDeviceStatus();
308         String rgb = bulbInfo.get("rgb");
309         if (rgb != null) {
310             Color color = new Color(Integer.parseInt(rgb));
311             status.setR(color.getRed());
312             status.setG(color.getGreen());
313             status.setB(color.getBlue());
314         }
315         String ct = bulbInfo.get("ct");
316         if (ct != null) {
317             status.setCt(Integer.parseInt(ct));
318         }
319         String hue = bulbInfo.get("hue");
320         if (hue != null) {
321             status.setHue(Integer.parseInt(hue));
322         }
323         String sat = bulbInfo.get("sat");
324         if (sat != null) {
325             status.setSat(Integer.parseInt(sat));
326         }
327     }
328
329     public static String getDefaultName(DeviceBase device) {
330         if (device.getDeviceModel() != null && !device.getDeviceName().equals("")) {
331             return device.getDeviceName();
332         }
333         switch (device.getDeviceType()) {
334             case ceiling:
335                 return "Yeelight LED Ceiling";
336             case ceiling1:
337             case ceiling3:
338                 return "Yeelight LED Ceiling with night mode";
339             case ceiling4:
340                 return "Yeelight LED Ceiling with ambient light";
341             case color:
342             case color4:
343                 return "Yeelight Color LED Bulb";
344             case mono:
345                 return "Yeelight White LED Bulb";
346             case ct_bulb:
347                 return "Yeelight White LED Bulb v2";
348             case stripe:
349             case strip6:
350                 return "Yeelight Color LED Stripe";
351             case desklamp:
352                 return "Yeelight Mi LED Desk Lamp";
353             default:
354                 return "";
355         }
356     }
357 }