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.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.dto.GetHostFirmwareRequest;
31 import org.openhab.binding.lifx.internal.dto.GetVersionRequest;
32 import org.openhab.binding.lifx.internal.dto.GetWifiFirmwareRequest;
33 import org.openhab.binding.lifx.internal.dto.Packet;
34 import org.openhab.binding.lifx.internal.dto.StateHostFirmwareResponse;
35 import org.openhab.binding.lifx.internal.dto.StateVersionResponse;
36 import org.openhab.binding.lifx.internal.dto.StateWifiFirmwareResponse;
37 import org.openhab.binding.lifx.internal.fields.MACAddress;
38 import org.openhab.binding.lifx.internal.handler.LifxLightHandler.CurrentLightState;
39 import org.openhab.binding.lifx.internal.listener.LifxPropertiesUpdateListener;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
44 * The {@link LifxLightPropertiesUpdater} updates the light properties when a light goes online. When packets get lost
45 * the requests are resent when the {@code UPDATE_INTERVAL} elapses.
47 * @author Wouter Born - Initial contribution
50 public class LifxLightPropertiesUpdater {
52 private final Logger logger = LoggerFactory.getLogger(LifxLightPropertiesUpdater.class);
54 private static final int UPDATE_INTERVAL = 15;
56 private final String logId;
57 private final @Nullable InetSocketAddress ipAddress;
58 private final @Nullable MACAddress macAddress;
59 private final CurrentLightState currentLightState;
60 private final LifxLightCommunicationHandler communicationHandler;
62 private final List<LifxPropertiesUpdateListener> propertiesUpdateListeners = new CopyOnWriteArrayList<>();
64 private final List<Packet> requestPackets = Arrays.asList(new GetVersionRequest(), new GetHostFirmwareRequest(),
65 new GetWifiFirmwareRequest());
66 private final Set<Integer> receivedPacketTypes = new HashSet<>();
68 private final ReentrantLock lock = new ReentrantLock();
69 private final ScheduledExecutorService scheduler;
70 private @Nullable ScheduledFuture<?> updateJob;
72 private final Map<String, String> properties = new HashMap<>();
73 private boolean updating;
74 private boolean wasOnline;
76 public LifxLightPropertiesUpdater(LifxLightContext context, LifxLightCommunicationHandler communicationHandler) {
77 this.logId = context.getLogId();
78 this.macAddress = context.getConfiguration().getMACAddress();
79 this.ipAddress = context.getConfiguration().getHost();
80 this.currentLightState = context.getCurrentLightState();
81 this.scheduler = context.getScheduler();
82 this.communicationHandler = communicationHandler;
85 public void updateProperties() {
86 if (propertiesUpdateListeners.isEmpty()) {
87 logger.debug("{} : Not updating properties because there are no listeners", logId);
94 boolean isOnline = currentLightState.isOnline();
97 logger.debug("{} : Updating light properties", logId);
99 receivedPacketTypes.clear();
101 updateHostProperty();
102 updateMACAddressProperty();
103 sendPropertyRequestPackets();
104 } else if (updating && !receivedAllResponsePackets()) {
105 logger.debug("{} : Resending requests for missing response packets", logId);
106 sendPropertyRequestPackets();
110 wasOnline = isOnline;
111 } catch (Exception e) {
112 logger.error("Error occurred while polling online state of a light ({})", logId, e);
118 private void updateHostProperty() {
119 InetSocketAddress host = communicationHandler.getIpAddress();
124 properties.put(LifxBindingConstants.PROPERTY_HOST, host.getHostString());
128 private void updateMACAddressProperty() {
129 MACAddress mac = communicationHandler.getMACAddress();
134 properties.put(LifxBindingConstants.PROPERTY_MAC_ADDRESS, mac.getAsLabel());
138 private void sendPropertyRequestPackets() {
139 for (Packet packet : requestPackets) {
140 if (!receivedPacketTypes.contains(packet.expectedResponses()[0])) {
141 communicationHandler.sendPacket(packet);
146 public void handleResponsePacket(Packet packet) {
151 if (packet instanceof StateVersionResponse) {
152 long productId = ((StateVersionResponse) packet).getProduct();
153 properties.put(LifxBindingConstants.PROPERTY_PRODUCT_ID, Long.toString(productId));
155 long productVersion = ((StateVersionResponse) packet).getVersion();
156 properties.put(LifxBindingConstants.PROPERTY_PRODUCT_VERSION, Long.toString(productVersion));
159 LifxProduct product = LifxProduct.getProductFromProductID(productId);
160 properties.put(LifxBindingConstants.PROPERTY_PRODUCT_NAME, product.getName());
161 properties.put(LifxBindingConstants.PROPERTY_VENDOR_ID, Long.toString(product.getVendor().getID()));
162 properties.put(LifxBindingConstants.PROPERTY_VENDOR_NAME, product.getVendor().getName());
163 } catch (IllegalArgumentException e) {
164 logger.debug("{} : Light has an unsupported product ID: {}", logId, productId);
167 receivedPacketTypes.add(packet.getPacketType());
168 } else if (packet instanceof StateHostFirmwareResponse) {
169 String hostVersion = ((StateHostFirmwareResponse) packet).getVersion().toString();
170 properties.put(LifxBindingConstants.PROPERTY_HOST_VERSION, hostVersion);
171 receivedPacketTypes.add(packet.getPacketType());
172 } else if (packet instanceof StateWifiFirmwareResponse) {
173 String wifiVersion = ((StateWifiFirmwareResponse) packet).getVersion().toString();
174 properties.put(LifxBindingConstants.PROPERTY_WIFI_VERSION, wifiVersion);
175 receivedPacketTypes.add(packet.getPacketType());
178 if (receivedAllResponsePackets()) {
180 propertiesUpdateListeners.forEach(listener -> listener.handlePropertiesUpdate(properties));
181 logger.debug("{} : Finished updating light properties", logId);
185 private boolean receivedAllResponsePackets() {
186 return requestPackets.size() == receivedPacketTypes.size();
189 public void addPropertiesUpdateListener(LifxPropertiesUpdateListener listener) {
190 propertiesUpdateListeners.add(listener);
193 public void removePropertiesUpdateListener(LifxPropertiesUpdateListener listener) {
194 propertiesUpdateListeners.remove(listener);
197 public void start() {
200 communicationHandler.addResponsePacketListener(this::handleResponsePacket);
201 ScheduledFuture<?> localUpdateJob = updateJob;
202 if (localUpdateJob == null || localUpdateJob.isCancelled()) {
203 updateJob = scheduler.scheduleWithFixedDelay(this::updateProperties, 0, UPDATE_INTERVAL,
206 } catch (Exception e) {
207 logger.error("Error occurred while starting properties update job for a light ({})", logId, e);
216 communicationHandler.removeResponsePacketListener(this::handleResponsePacket);
217 ScheduledFuture<?> localUpdateJob = updateJob;
218 if (localUpdateJob != null && !localUpdateJob.isCancelled()) {
219 localUpdateJob.cancel(true);
222 } catch (Exception e) {
223 logger.error("Error occurred while stopping properties update job for a light ({})", logId, e);