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.services;
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;
28 import java.util.concurrent.ExecutorService;
29 import java.util.concurrent.Executors;
30 import java.util.stream.Collectors;
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;
43 * The {@link DeviceManager} is a class for managing all devices.
45 * @author Coaster Li - Initial contribution
46 * @author Joe Ho - Added duration
47 * @author Nikita Pogudalov - Added name for Ceiling 1
49 public class DeviceManager {
50 private final Logger logger = LoggerFactory.getLogger(DeviceManager.class);
52 private static final String TAG = DeviceManager.class.getSimpleName();
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";
59 private static final int TIMEOUT = 10000;
61 public static final DeviceManager sInstance = new DeviceManager();
62 public volatile boolean mSearching = false;
64 public Map<String, DeviceBase> mDeviceList = new HashMap<>();
65 public List<DeviceListener> mListeners = new ArrayList<>();
67 private ExecutorService executorService = Executors.newCachedThreadPool();
69 private DeviceManager() {
72 public static DeviceManager getInstance() {
76 public void registerDeviceListener(DeviceListener listener) {
77 if (!mListeners.contains(listener)) {
78 mListeners.add(listener);
82 public void unregisterDeviceListener(DeviceListener listener) {
83 mListeners.remove(listener);
86 public void startDiscovery() {
90 public void startDiscovery(long timeToStop) {
95 Thread.sleep(timeToStop);
96 } catch (InterruptedException e) {
97 logger.debug("Interrupted while sleeping", e);
105 public void stopDiscovery() {
109 private void searchDevice() {
111 logger.debug("{}: Already in discovery, return!", TAG);
115 logger.debug("Starting Discovery");
118 final InetAddress multicastAddress = InetAddress.getByName(MULTI_CAST_HOST);
119 final List<NetworkInterface> networkInterfaces = getNetworkInterfaces();
122 for (final NetworkInterface networkInterface : networkInterfaces) {
123 logger.debug("Starting Discovery on: {}", networkInterface.getDisplayName());
125 executorService.execute(() -> {
126 try (MulticastSocket multiSocket = new MulticastSocket(MULTI_CAST_PORT)) {
127 multiSocket.setSoTimeout(TIMEOUT);
128 multiSocket.setNetworkInterface(networkInterface);
129 multiSocket.joinGroup(multicastAddress);
131 DatagramPacket dpSend = new DatagramPacket(DISCOVERY_MSG.getBytes(),
132 DISCOVERY_MSG.getBytes().length, multicastAddress, MULTI_CAST_PORT);
133 multiSocket.send(dpSend);
136 byte[] buf = new byte[1024];
138 DatagramPacket dpRecv = new DatagramPacket(buf, buf.length);
141 multiSocket.receive(dpRecv);
142 byte[] bytes = dpRecv.getData();
143 StringBuilder buffer = new StringBuilder();
144 for (int i = 0; i < dpRecv.getLength(); i++) {
146 if (bytes[i] == Character.LINE_SEPARATOR) {
149 buffer.append((char) bytes[i]);
152 String receivedMessage = buffer.toString();
154 logger.debug("{}: got message: {}", TAG, receivedMessage);
155 // we can skip the request because we aren't interested in our own search broadcast
157 if (receivedMessage.startsWith("M-SEARCH")) {
161 String[] infos = receivedMessage.split("\n");
162 Map<String, String> bulbInfo = new HashMap<>();
163 for (String info : infos) {
164 int index = info.indexOf(':');
168 String key = info.substring(0, index).trim();
169 String value = info.substring(index + 1).trim();
171 bulbInfo.put(key, value);
173 logger.debug("{}: got bulbInfo: {}", TAG, bulbInfo);
174 if (bulbInfo.containsKey("model") && bulbInfo.containsKey("id")) {
175 DeviceBase device = DeviceFactory.build(bulbInfo);
177 if (null == device) {
178 logger.warn("Found unsupported device.");
181 device.setDeviceName(bulbInfo.getOrDefault("name", ""));
183 if (mDeviceList.containsKey(device.getDeviceId())) {
184 updateDevice(mDeviceList.get(device.getDeviceId()), bulbInfo);
186 notifyDeviceFound(device);
188 } catch (SocketTimeoutException e) {
189 logger.debug("Error timeout: {}", e.getMessage(), e);
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);
200 } catch (IOException e) {
201 logger.debug("Error getting ip addresses: {}", e.getMessage(), e);
205 private List<NetworkInterface> getNetworkInterfaces() throws SocketException {
206 return Collections.list(NetworkInterface.getNetworkInterfaces()).stream().filter(device -> {
208 return device.isUp() && !device.isLoopback();
209 } catch (SocketException e) {
210 logger.debug("Failed to get device information", e);
213 }).collect(Collectors.toList());
216 private void notifyDeviceFound(DeviceBase device) {
217 for (DeviceListener listener : mListeners) {
218 listener.onDeviceFound(device);
222 public void doAction(String deviceId, DeviceAction action) {
223 DeviceBase device = mDeviceList.get(deviceId);
224 if (device != null) {
227 device.open(action.intDuration());
230 device.close(action.intDuration());
233 device.setBrightness(action.intValue(), action.intDuration());
236 device.setColor(action.intValue(), action.intDuration());
238 case colortemperature:
239 device.setCT(action.intValue(), action.intDuration());
241 case increase_bright:
242 device.increaseBrightness(action.intDuration());
244 case decrease_bright:
245 device.decreaseBrightness(action.intDuration());
248 device.increaseCt(action.intDuration());
251 device.decreaseCt(action.intDuration());
253 case background_color:
254 if (device instanceof DeviceWithAmbientLight) {
255 final String[] split = action.strValue().split(",");
257 ((DeviceWithAmbientLight) device).setBackgroundColor(Integer.parseInt(split[0]),
258 Integer.parseInt(split[1]), action.intDuration());
261 case background_brightness:
262 if (device instanceof DeviceWithAmbientLight) {
263 ((DeviceWithAmbientLight) device).setBackgroundBrightness(action.intValue(),
264 action.intDuration());
268 if (device instanceof DeviceWithAmbientLight) {
269 ((DeviceWithAmbientLight) device).setBackgroundPower(true, action.intDuration());
273 if (device instanceof DeviceWithAmbientLight) {
274 ((DeviceWithAmbientLight) device).setBackgroundPower(false, action.intDuration());
278 if (device instanceof DeviceWithNightlight) {
279 ((DeviceWithNightlight) device).toggleNightlightMode(false);
283 if (device instanceof DeviceWithNightlight) {
284 ((DeviceWithNightlight) device).toggleNightlightMode(true);
293 public void doCustomAction(String deviceId, String action, String params) {
294 DeviceBase device = mDeviceList.get(deviceId);
295 device.sendCustomCommand(action, params);
298 public void addDevice(DeviceBase device) {
299 mDeviceList.put(device.getDeviceId(), device);
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");
310 Color color = new Color(Integer.parseInt(rgb));
311 status.setR(color.getRed());
312 status.setG(color.getGreen());
313 status.setB(color.getBlue());
315 String ct = bulbInfo.get("ct");
317 status.setCt(Integer.parseInt(ct));
319 String hue = bulbInfo.get("hue");
321 status.setHue(Integer.parseInt(hue));
323 String sat = bulbInfo.get("sat");
325 status.setSat(Integer.parseInt(sat));
329 public static String getDefaultName(DeviceBase device) {
330 if (device.getDeviceModel() != null && !device.getDeviceName().equals("")) {
331 return device.getDeviceName();
333 switch (device.getDeviceType()) {
335 return "Yeelight LED Ceiling";
340 return "Yeelight LED Ceiling with night mode";
342 return "Yeelight LED Ceiling with ambient light";
345 return "Yeelight Color LED Bulb";
347 return "Yeelight White LED Bulb";
349 return "Yeelight White LED Bulb v2";
352 return "Yeelight Color LED Stripe";
354 return "Yeelight Mi LED Desk Lamp";