2 * Copyright (c) 2010-2020 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.lifx.internal;
15 import java.net.InetSocketAddress;
16 import java.util.Arrays;
17 import java.util.HashMap;
18 import java.util.HashSet;
19 import java.util.List;
22 import java.util.concurrent.CopyOnWriteArrayList;
23 import java.util.concurrent.ScheduledExecutorService;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26 import java.util.concurrent.locks.ReentrantLock;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.lifx.internal.fields.MACAddress;
31 import org.openhab.binding.lifx.internal.handler.LifxLightHandler.CurrentLightState;
32 import org.openhab.binding.lifx.internal.listener.LifxPropertiesUpdateListener;
33 import org.openhab.binding.lifx.internal.protocol.GetHostFirmwareRequest;
34 import org.openhab.binding.lifx.internal.protocol.GetVersionRequest;
35 import org.openhab.binding.lifx.internal.protocol.GetWifiFirmwareRequest;
36 import org.openhab.binding.lifx.internal.protocol.Packet;
37 import org.openhab.binding.lifx.internal.protocol.Product;
38 import org.openhab.binding.lifx.internal.protocol.StateHostFirmwareResponse;
39 import org.openhab.binding.lifx.internal.protocol.StateVersionResponse;
40 import org.openhab.binding.lifx.internal.protocol.StateWifiFirmwareResponse;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * The {@link LifxLightPropertiesUpdater} updates the light properties when a light goes online. When packets get lost
46 * the requests are resent when the {@code UPDATE_INTERVAL} elapses.
48 * @author Wouter Born - Update light properties when online
51 public class LifxLightPropertiesUpdater {
53 private final Logger logger = LoggerFactory.getLogger(LifxLightPropertiesUpdater.class);
55 private static final int UPDATE_INTERVAL = 15;
57 private final String logId;
58 private final @Nullable InetSocketAddress ipAddress;
59 private final @Nullable MACAddress macAddress;
60 private final CurrentLightState currentLightState;
61 private final LifxLightCommunicationHandler communicationHandler;
63 private final List<LifxPropertiesUpdateListener> propertiesUpdateListeners = new CopyOnWriteArrayList<>();
65 private final List<Packet> requestPackets = Arrays.asList(new GetVersionRequest(), new GetHostFirmwareRequest(),
66 new GetWifiFirmwareRequest());
67 private final Set<Integer> receivedPacketTypes = new HashSet<>();
69 private final ReentrantLock lock = new ReentrantLock();
70 private final ScheduledExecutorService scheduler;
71 private @Nullable ScheduledFuture<?> updateJob;
73 private final Map<String, String> properties = new HashMap<>();
74 private boolean updating;
75 private boolean wasOnline;
77 public LifxLightPropertiesUpdater(LifxLightContext context, LifxLightCommunicationHandler communicationHandler) {
78 this.logId = context.getLogId();
79 this.macAddress = context.getConfiguration().getMACAddress();
80 this.ipAddress = context.getConfiguration().getHost();
81 this.currentLightState = context.getCurrentLightState();
82 this.scheduler = context.getScheduler();
83 this.communicationHandler = communicationHandler;
86 public void updateProperties() {
87 if (propertiesUpdateListeners.isEmpty()) {
88 logger.debug("{} : Not updating properties because there are no listeners", logId);
95 boolean isOnline = currentLightState.isOnline();
98 logger.debug("{} : Updating light properties", logId);
100 receivedPacketTypes.clear();
102 updateHostProperty();
103 updateMACAddressProperty();
104 sendPropertyRequestPackets();
105 } else if (updating && !receivedAllResponsePackets()) {
106 logger.debug("{} : Resending requests for missing response packets", logId);
107 sendPropertyRequestPackets();
111 wasOnline = isOnline;
112 } catch (Exception e) {
113 logger.error("Error occurred while polling online state of a light ({})", logId, e);
119 private void updateHostProperty() {
120 InetSocketAddress host = communicationHandler.getIpAddress();
125 properties.put(LifxBindingConstants.PROPERTY_HOST, host.getHostString());
129 private void updateMACAddressProperty() {
130 MACAddress mac = communicationHandler.getMACAddress();
135 properties.put(LifxBindingConstants.PROPERTY_MAC_ADDRESS, mac.getAsLabel());
139 private void sendPropertyRequestPackets() {
140 for (Packet packet : requestPackets) {
141 if (!receivedPacketTypes.contains(packet.expectedResponses()[0])) {
142 communicationHandler.sendPacket(packet);
147 public void handleResponsePacket(Packet packet) {
152 if (packet instanceof StateVersionResponse) {
153 long productId = ((StateVersionResponse) packet).getProduct();
154 properties.put(LifxBindingConstants.PROPERTY_PRODUCT_ID, Long.toString(productId));
156 long productVersion = ((StateVersionResponse) packet).getVersion();
157 properties.put(LifxBindingConstants.PROPERTY_PRODUCT_VERSION, Long.toString(productVersion));
160 Product product = Product.getProductFromProductID(productId);
161 properties.put(LifxBindingConstants.PROPERTY_PRODUCT_NAME, product.getName());
162 properties.put(LifxBindingConstants.PROPERTY_VENDOR_ID, Long.toString(product.getVendor().getID()));
163 properties.put(LifxBindingConstants.PROPERTY_VENDOR_NAME, product.getVendor().getName());
164 } catch (IllegalArgumentException e) {
165 logger.debug("{} : Light has an unsupported product ID: {}", logId, productId);
168 receivedPacketTypes.add(packet.getPacketType());
169 } else if (packet instanceof StateHostFirmwareResponse) {
170 String hostVersion = ((StateHostFirmwareResponse) packet).getVersion().toString();
171 properties.put(LifxBindingConstants.PROPERTY_HOST_VERSION, hostVersion);
172 receivedPacketTypes.add(packet.getPacketType());
173 } else if (packet instanceof StateWifiFirmwareResponse) {
174 String wifiVersion = ((StateWifiFirmwareResponse) packet).getVersion().toString();
175 properties.put(LifxBindingConstants.PROPERTY_WIFI_VERSION, wifiVersion);
176 receivedPacketTypes.add(packet.getPacketType());
179 if (receivedAllResponsePackets()) {
181 propertiesUpdateListeners.forEach(listener -> listener.handlePropertiesUpdate(properties));
182 logger.debug("{} : Finished updating light properties", logId);
186 private boolean receivedAllResponsePackets() {
187 return requestPackets.size() == receivedPacketTypes.size();
190 public void addPropertiesUpdateListener(LifxPropertiesUpdateListener listener) {
191 propertiesUpdateListeners.add(listener);
194 public void removePropertiesUpdateListener(LifxPropertiesUpdateListener listener) {
195 propertiesUpdateListeners.remove(listener);
198 public void start() {
201 communicationHandler.addResponsePacketListener(this::handleResponsePacket);
202 ScheduledFuture<?> localUpdateJob = updateJob;
203 if (localUpdateJob == null || localUpdateJob.isCancelled()) {
204 updateJob = scheduler.scheduleWithFixedDelay(this::updateProperties, 0, UPDATE_INTERVAL,
207 } catch (Exception e) {
208 logger.error("Error occurred while starting properties update job for a light ({})", logId, e);
217 communicationHandler.removeResponsePacketListener(this::handleResponsePacket);
218 ScheduledFuture<?> localUpdateJob = updateJob;
219 if (localUpdateJob != null && !localUpdateJob.isCancelled()) {
220 localUpdateJob.cancel(true);
223 } catch (Exception e) {
224 logger.error("Error occurred while stopping properties update job for a light ({})", logId, e);