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