]> git.basschouten.com Git - openhab-addons.git/blob
b8c01af68f49769078d2e125db9080dd94bd986f
[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.hdpowerview.internal.handler;
14
15 import java.io.IOException;
16 import java.util.ArrayList;
17 import java.util.List;
18 import java.util.concurrent.ScheduledExecutorService;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21 import java.util.stream.Collectors;
22
23 import javax.ws.rs.client.ClientBuilder;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.eclipse.jetty.client.HttpClient;
28 import org.openhab.binding.hdpowerview.internal.GatewayWebTargets;
29 import org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants;
30 import org.openhab.binding.hdpowerview.internal.HDPowerViewTranslationProvider;
31 import org.openhab.binding.hdpowerview.internal.config.HDPowerViewHubConfiguration;
32 import org.openhab.binding.hdpowerview.internal.dto.gen3.Scene;
33 import org.openhab.binding.hdpowerview.internal.dto.gen3.Shade;
34 import org.openhab.binding.hdpowerview.internal.exceptions.HubProcessingException;
35 import org.openhab.core.library.CoreItemFactory;
36 import org.openhab.core.library.types.OnOffType;
37 import org.openhab.core.thing.Bridge;
38 import org.openhab.core.thing.Channel;
39 import org.openhab.core.thing.ChannelGroupUID;
40 import org.openhab.core.thing.ChannelUID;
41 import org.openhab.core.thing.Thing;
42 import org.openhab.core.thing.ThingStatus;
43 import org.openhab.core.thing.ThingStatusDetail;
44 import org.openhab.core.thing.binding.BaseBridgeHandler;
45 import org.openhab.core.thing.binding.ThingHandler;
46 import org.openhab.core.thing.binding.builder.ChannelBuilder;
47 import org.openhab.core.thing.type.AutoUpdatePolicy;
48 import org.openhab.core.thing.type.ChannelTypeUID;
49 import org.openhab.core.types.Command;
50 import org.openhab.core.types.RefreshType;
51 import org.osgi.service.jaxrs.client.SseEventSourceFactory;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 /**
56  * Bridge handler for an HD PowerView Generation 3 Gateway.
57  *
58  * @author Andrew Fiddian-Green - Initial contribution
59  */
60 @NonNullByDefault
61 public class GatewayBridgeHandler extends BaseBridgeHandler {
62
63     private final Logger logger = LoggerFactory.getLogger(GatewayBridgeHandler.class);
64     private final String channelTypeId = HDPowerViewBindingConstants.CHANNELTYPE_SCENE_ACTIVATE;
65     private final String channelGroupId = HDPowerViewBindingConstants.CHANNEL_GROUP_SCENES;
66
67     private final HttpClient httpClient;
68     private final HDPowerViewTranslationProvider translationProvider;
69     private final ClientBuilder clientBuilder;
70     private final SseEventSourceFactory eventSourceFactory;
71
72     private @Nullable GatewayWebTargets webTargets;
73     private @Nullable ScheduledFuture<?> refreshTask;
74
75     private boolean scenesLoaded;
76     private boolean propertiesLoaded;
77     private boolean disposing;
78
79     public GatewayBridgeHandler(Bridge bridge, HttpClient httpClient,
80             HDPowerViewTranslationProvider translationProvider, ClientBuilder clientBuilder,
81             SseEventSourceFactory eventSourceFactory) {
82         super(bridge);
83         this.httpClient = httpClient;
84         this.translationProvider = translationProvider;
85         this.clientBuilder = clientBuilder;
86         this.eventSourceFactory = eventSourceFactory;
87     }
88
89     @Override
90     public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
91         if (childHandler instanceof ShadeThingHandler) {
92             refreshShade(((ShadeThingHandler) childHandler).getShadeId());
93         }
94     }
95
96     @Override
97     public void dispose() {
98         disposing = true;
99         ScheduledFuture<?> future = this.refreshTask;
100         if (future != null) {
101             future.cancel(true);
102         }
103         this.refreshTask = null;
104
105         GatewayWebTargets webTargets = this.webTargets;
106         if (webTargets != null) {
107             scheduler.submit(() -> disposeWebTargets(webTargets));
108             this.webTargets = null;
109         }
110     }
111
112     private void disposeWebTargets(GatewayWebTargets webTargets) {
113         try {
114             webTargets.close();
115         } catch (IOException e) {
116         }
117     }
118
119     /**
120      * Refresh the state of all things. Normally the thing's position state is updated by SSE. However we must do a
121      * refresh once on start up in order to get the initial state. Also the other properties (battery, signal strength
122      * etc.) are not updated by SSE. Furthermore we need to do periodic refreshes just in case the SSE connection may
123      * have been lost.
124      */
125     private void doRefresh() {
126         logger.debug("doRefresh()");
127         try {
128             if (getWebTargets().gatewayRegister()) {
129                 updateStatus(ThingStatus.ONLINE);
130                 getWebTargets().sseOpen();
131                 refreshProperties();
132                 refreshShades();
133                 refreshScenes();
134             }
135         } catch (IllegalStateException | HubProcessingException e) {
136             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
137             logger.debug("doRefresh() exception:{}, message:{}", e.getClass().getSimpleName(), e.getMessage(), e);
138         }
139     }
140
141     public ScheduledExecutorService getScheduler() {
142         return scheduler;
143     }
144
145     /**
146      * Getter for the list of all child shade thing handlers.
147      *
148      * @return the list of shade handlers.
149      * @throws IllegalStateException if the bridge is not properly initialized.
150      */
151     private List<ShadeThingHandler> getShadeThingHandlers() throws IllegalStateException {
152         logger.debug("getShadeThingHandlers()");
153         return getThing().getThings().stream().map(Thing::getHandler).filter(ShadeThingHandler.class::isInstance)
154                 .map(ShadeThingHandler.class::cast).collect(Collectors.toList());
155     }
156
157     /**
158      * Getter for the webTargets.
159      *
160      * @return the webTargets.
161      * @throws IllegalStateException if webTargets is not initialized.
162      */
163     public GatewayWebTargets getWebTargets() throws IllegalStateException {
164         GatewayWebTargets webTargets = this.webTargets;
165         if (webTargets != null) {
166             return webTargets;
167         }
168         throw new IllegalStateException("WebTargets not initialized.");
169     }
170
171     @Override
172     public void handleCommand(ChannelUID channelUID, Command command) {
173         if (RefreshType.REFRESH == command) {
174             scheduler.submit(() -> doRefresh());
175             return;
176         }
177         Channel channel = getThing().getChannel(channelUID.getId());
178         if (channel == null) {
179             return;
180         }
181         ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
182         if (channelTypeUID == null) {
183             return;
184         }
185         if (channelTypeId.equals(channelTypeUID.getId()) && OnOffType.ON == command) {
186             try {
187                 getWebTargets().activateScene(Integer.parseInt(channelUID.getIdWithoutGroup()));
188             } catch (HubProcessingException | IllegalStateException e) {
189                 logger.warn("handleCommand() exception:{}, message:{}", e.getClass().getSimpleName(), e.getMessage());
190             }
191         }
192     }
193
194     @Override
195     public void initialize() {
196         HDPowerViewHubConfiguration config = getConfigAs(HDPowerViewHubConfiguration.class);
197         String host = config.host;
198
199         if (host == null || host.isEmpty()) {
200             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
201                     "@text/offline.conf-error.no-host-address");
202             return;
203         }
204
205         webTargets = new GatewayWebTargets(this, httpClient, clientBuilder, eventSourceFactory, host);
206         scenesLoaded = false;
207         propertiesLoaded = false;
208         disposing = false;
209
210         /*
211          * Normally the thing's position state is updated by SSE. However we must do a refresh once on start up in order
212          * to get the initial state. Also the other properties (battery, signal strength etc.) are not updated by SSE.
213          * Furthermore we need to do periodic refreshes just in case the SSE connection may have been lost. So we
214          * schedule the refresh at the 'hardRefresh' interval.
215          */
216         refreshTask = scheduler.scheduleWithFixedDelay(() -> doRefresh(), 0, config.hardRefresh, TimeUnit.MINUTES);
217         updateStatus(ThingStatus.UNKNOWN);
218     }
219
220     /**
221      * Method that is called when a scene changes state.
222      *
223      * @param scene the one that changed.
224      */
225     public void onSceneEvent(Scene scene) {
226         // TODO perhaps we should trigger an OH core event here ??
227     }
228
229     /**
230      * Method that is called when a shade changes state.
231      *
232      * @param shade the one that changed.
233      */
234     public void onShadeEvent(Shade shade) {
235         if (disposing) {
236             return;
237         }
238         try {
239             for (ShadeThingHandler handler : getShadeThingHandlers()) {
240                 if (handler.notify(shade)) {
241                     break;
242                 }
243             }
244         } catch (IllegalStateException e) {
245             logger.warn("onShadeEvent() exception:{}, message:{}", e.getClass().getSimpleName(), e.getMessage(), e);
246         }
247     }
248
249     private void refreshProperties() throws HubProcessingException, IllegalStateException {
250         if (propertiesLoaded || disposing) {
251             return;
252         }
253         logger.debug("refreshProperties()");
254         thing.setProperties(getWebTargets().getInformation());
255         propertiesLoaded = true;
256     }
257
258     /**
259      * Create the dynamic list of scene channels.
260      *
261      * @throws HubProcessingException if the web target connection caused an error.
262      * @throws IllegalStateException if this handler is in an illegal state.
263      */
264     private void refreshScenes() throws HubProcessingException, IllegalStateException {
265         if (scenesLoaded || disposing) {
266             return;
267         }
268         logger.debug("refreshScenes()");
269         ChannelTypeUID typeUID = new ChannelTypeUID(HDPowerViewBindingConstants.BINDING_ID, channelTypeId);
270         ChannelGroupUID groupUID = new ChannelGroupUID(thing.getUID(), channelGroupId);
271         List<Channel> channels = new ArrayList<>();
272         for (Scene scene : getWebTargets().getScenes()) {
273             ChannelUID channelUID = new ChannelUID(groupUID, Integer.toString(scene.getId()));
274             String name = scene.getName();
275             String description = translationProvider.getText("dynamic-channel.scene-activate.description", name);
276             channels.add(ChannelBuilder.create(channelUID, CoreItemFactory.SWITCH).withType(typeUID).withLabel(name)
277                     .withDescription(description).withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build());
278         }
279         updateThing(editThing().withChannels(channels).build());
280         scenesLoaded = true;
281     }
282
283     /**
284      * Refresh a single shade.
285      *
286      * @param shadeId the id of the shade to be refreshed.
287      */
288     private void refreshShade(int shadeId) {
289         try {
290             Shade shade = getWebTargets().getShade(shadeId);
291             for (ShadeThingHandler handler : getShadeThingHandlers()) {
292                 if (handler.notify(shade)) {
293                     break;
294                 }
295             }
296         } catch (HubProcessingException | IllegalStateException e) {
297             logger.warn("refreshShade() exception:{}, message:{}", e.getClass().getSimpleName(), e.getMessage(), e);
298         }
299     }
300
301     /**
302      * Get the full list of shades data and notify each of the thing handlers.
303      *
304      * @throws HubProcessingException if the web target connection caused an error.
305      * @throws IllegalStateException if this handler is in an illegal state.
306      */
307     private void refreshShades() throws HubProcessingException, IllegalStateException {
308         if (disposing) {
309             return;
310         }
311         logger.debug("refreshShades()");
312         List<ShadeThingHandler> handlers = getShadeThingHandlers();
313         for (Shade shade : getWebTargets().getShades()) {
314             for (ShadeThingHandler handler : handlers) {
315                 if (handler.notify(shade)) {
316                     break;
317                 }
318             }
319         }
320     }
321 }