]> git.basschouten.com Git - openhab-addons.git/blob
634f651096d137d3b615902910eeb3c470ecd9ab
[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.lifx.internal;
14
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;
20 import java.util.Map;
21 import java.util.Set;
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;
27
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;
42
43 /**
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.
46  *
47  * @author Wouter Born - Initial contribution
48  */
49 @NonNullByDefault
50 public class LifxLightPropertiesUpdater {
51
52     private final Logger logger = LoggerFactory.getLogger(LifxLightPropertiesUpdater.class);
53
54     private static final int UPDATE_INTERVAL = 15;
55
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;
61
62     private final List<LifxPropertiesUpdateListener> propertiesUpdateListeners = new CopyOnWriteArrayList<>();
63
64     private final List<Packet> requestPackets = Arrays.asList(new GetVersionRequest(), new GetHostFirmwareRequest(),
65             new GetWifiFirmwareRequest());
66     private final Set<Integer> receivedPacketTypes = new HashSet<>();
67
68     private final ReentrantLock lock = new ReentrantLock();
69     private final ScheduledExecutorService scheduler;
70     private @Nullable ScheduledFuture<?> updateJob;
71
72     private final Map<String, String> properties = new HashMap<>();
73     private boolean updating;
74     private boolean wasOnline;
75
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;
83     }
84
85     public void updateProperties() {
86         if (propertiesUpdateListeners.isEmpty()) {
87             logger.debug("{} : Not updating properties because there are no listeners", logId);
88             return;
89         }
90
91         try {
92             lock.lock();
93
94             boolean isOnline = currentLightState.isOnline();
95             if (isOnline) {
96                 if (!wasOnline) {
97                     logger.debug("{} : Updating light properties", logId);
98                     properties.clear();
99                     receivedPacketTypes.clear();
100                     updating = true;
101                     updateHostProperty();
102                     updateMACAddressProperty();
103                     sendPropertyRequestPackets();
104                 } else if (updating && !receivedAllResponsePackets()) {
105                     logger.debug("{} : Resending requests for missing response packets", logId);
106                     sendPropertyRequestPackets();
107                 }
108             }
109
110             wasOnline = isOnline;
111         } catch (Exception e) {
112             logger.error("Error occurred while polling online state of a light ({})", logId, e);
113         } finally {
114             lock.unlock();
115         }
116     }
117
118     private void updateHostProperty() {
119         InetSocketAddress host = communicationHandler.getIpAddress();
120         if (host == null) {
121             host = ipAddress;
122         }
123         if (host != null) {
124             properties.put(LifxBindingConstants.PROPERTY_HOST, host.getHostString());
125         }
126     }
127
128     private void updateMACAddressProperty() {
129         MACAddress mac = communicationHandler.getMACAddress();
130         if (mac == null) {
131             mac = macAddress;
132         }
133         if (mac != null) {
134             properties.put(LifxBindingConstants.PROPERTY_MAC_ADDRESS, mac.getAsLabel());
135         }
136     }
137
138     private void sendPropertyRequestPackets() {
139         for (Packet packet : requestPackets) {
140             if (!receivedPacketTypes.contains(packet.expectedResponses()[0])) {
141                 communicationHandler.sendPacket(packet);
142             }
143         }
144     }
145
146     public void handleResponsePacket(Packet packet) {
147         if (!updating) {
148             return;
149         }
150
151         if (packet instanceof StateVersionResponse response) {
152             long productId = response.getProduct();
153             properties.put(LifxBindingConstants.PROPERTY_PRODUCT_ID, Long.toString(productId));
154
155             long productVersion = response.getVersion();
156             properties.put(LifxBindingConstants.PROPERTY_PRODUCT_VERSION, Long.toString(productVersion));
157
158             try {
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);
165             }
166
167             receivedPacketTypes.add(packet.getPacketType());
168         } else if (packet instanceof StateHostFirmwareResponse response) {
169             String hostVersion = response.getVersion().toString();
170             properties.put(LifxBindingConstants.PROPERTY_HOST_VERSION, hostVersion);
171             receivedPacketTypes.add(packet.getPacketType());
172         } else if (packet instanceof StateWifiFirmwareResponse response) {
173             String wifiVersion = response.getVersion().toString();
174             properties.put(LifxBindingConstants.PROPERTY_WIFI_VERSION, wifiVersion);
175             receivedPacketTypes.add(packet.getPacketType());
176         }
177
178         if (receivedAllResponsePackets()) {
179             updating = false;
180             propertiesUpdateListeners.forEach(listener -> listener.handlePropertiesUpdate(properties));
181             logger.debug("{} : Finished updating light properties", logId);
182         }
183     }
184
185     private boolean receivedAllResponsePackets() {
186         return requestPackets.size() == receivedPacketTypes.size();
187     }
188
189     public void addPropertiesUpdateListener(LifxPropertiesUpdateListener listener) {
190         propertiesUpdateListeners.add(listener);
191     }
192
193     public void removePropertiesUpdateListener(LifxPropertiesUpdateListener listener) {
194         propertiesUpdateListeners.remove(listener);
195     }
196
197     public void start() {
198         try {
199             lock.lock();
200             communicationHandler.addResponsePacketListener(this::handleResponsePacket);
201             ScheduledFuture<?> localUpdateJob = updateJob;
202             if (localUpdateJob == null || localUpdateJob.isCancelled()) {
203                 updateJob = scheduler.scheduleWithFixedDelay(this::updateProperties, 0, UPDATE_INTERVAL,
204                         TimeUnit.SECONDS);
205             }
206         } catch (Exception e) {
207             logger.error("Error occurred while starting properties update job for a light ({})", logId, e);
208         } finally {
209             lock.unlock();
210         }
211     }
212
213     public void stop() {
214         try {
215             lock.lock();
216             communicationHandler.removeResponsePacketListener(this::handleResponsePacket);
217             ScheduledFuture<?> localUpdateJob = updateJob;
218             if (localUpdateJob != null && !localUpdateJob.isCancelled()) {
219                 localUpdateJob.cancel(true);
220                 updateJob = null;
221             }
222         } catch (Exception e) {
223             logger.error("Error occurred while stopping properties update job for a light ({})", logId, e);
224         } finally {
225             lock.unlock();
226         }
227     }
228 }