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.hdpowerview.internal.handler;
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;
23 import javax.ws.rs.client.ClientBuilder;
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;
56 * Bridge handler for an HD PowerView Generation 3 Gateway.
58 * @author Andrew Fiddian-Green - Initial contribution
61 public class GatewayBridgeHandler extends BaseBridgeHandler {
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;
67 private final HttpClient httpClient;
68 private final HDPowerViewTranslationProvider translationProvider;
69 private final ClientBuilder clientBuilder;
70 private final SseEventSourceFactory eventSourceFactory;
72 private @Nullable GatewayWebTargets webTargets;
73 private @Nullable ScheduledFuture<?> refreshTask;
75 private boolean scenesLoaded;
76 private boolean propertiesLoaded;
77 private boolean disposing;
79 public GatewayBridgeHandler(Bridge bridge, HttpClient httpClient,
80 HDPowerViewTranslationProvider translationProvider, ClientBuilder clientBuilder,
81 SseEventSourceFactory eventSourceFactory) {
83 this.httpClient = httpClient;
84 this.translationProvider = translationProvider;
85 this.clientBuilder = clientBuilder;
86 this.eventSourceFactory = eventSourceFactory;
90 public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
91 if (childHandler instanceof ShadeThingHandler shadeThingHandler) {
92 refreshShade(shadeThingHandler.getShadeId());
97 public void dispose() {
99 ScheduledFuture<?> future = this.refreshTask;
100 if (future != null) {
103 this.refreshTask = null;
105 GatewayWebTargets webTargets = this.webTargets;
106 if (webTargets != null) {
107 scheduler.submit(() -> disposeWebTargets(webTargets));
108 this.webTargets = null;
112 private void disposeWebTargets(GatewayWebTargets webTargets) {
115 } catch (IOException e) {
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
125 private void doRefresh() {
126 logger.debug("doRefresh()");
128 if (getWebTargets().gatewayRegister()) {
129 updateStatus(ThingStatus.ONLINE);
130 getWebTargets().sseOpen();
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);
141 public ScheduledExecutorService getScheduler() {
146 * Getter for the list of all child shade thing handlers.
148 * @return the list of shade handlers.
149 * @throws IllegalStateException if the bridge is not properly initialized.
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());
158 * Getter for the webTargets.
160 * @return the webTargets.
161 * @throws IllegalStateException if webTargets is not initialized.
163 public GatewayWebTargets getWebTargets() throws IllegalStateException {
164 GatewayWebTargets webTargets = this.webTargets;
165 if (webTargets != null) {
168 throw new IllegalStateException("WebTargets not initialized.");
172 public void handleCommand(ChannelUID channelUID, Command command) {
173 if (RefreshType.REFRESH == command) {
174 scheduler.submit(() -> doRefresh());
177 Channel channel = getThing().getChannel(channelUID.getId());
178 if (channel == null) {
181 ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
182 if (channelTypeUID == null) {
185 if (channelTypeId.equals(channelTypeUID.getId()) && OnOffType.ON == command) {
187 getWebTargets().activateScene(Integer.parseInt(channelUID.getIdWithoutGroup()));
188 } catch (HubProcessingException | IllegalStateException e) {
189 logger.warn("handleCommand() exception:{}, message:{}", e.getClass().getSimpleName(), e.getMessage());
195 public void initialize() {
196 HDPowerViewHubConfiguration config = getConfigAs(HDPowerViewHubConfiguration.class);
197 String host = config.host;
199 if (host == null || host.isEmpty()) {
200 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
201 "@text/offline.conf-error.no-host-address");
205 webTargets = new GatewayWebTargets(this, httpClient, clientBuilder, eventSourceFactory, host);
206 scenesLoaded = false;
207 propertiesLoaded = false;
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.
216 refreshTask = scheduler.scheduleWithFixedDelay(() -> doRefresh(), 0, config.hardRefresh, TimeUnit.MINUTES);
217 updateStatus(ThingStatus.UNKNOWN);
221 * Method that is called when a scene changes state.
223 * @param scene the one that changed.
225 public void onSceneEvent(Scene scene) {
226 // TODO perhaps we should trigger an OH core event here ??
230 * Method that is called when a shade changes state.
232 * @param shade the one that changed.
234 public void onShadeEvent(Shade shade) {
239 for (ShadeThingHandler handler : getShadeThingHandlers()) {
240 if (handler.notify(shade)) {
244 } catch (IllegalStateException e) {
245 logger.warn("onShadeEvent() exception:{}, message:{}", e.getClass().getSimpleName(), e.getMessage(), e);
249 private void refreshProperties() throws HubProcessingException, IllegalStateException {
250 if (propertiesLoaded || disposing) {
253 logger.debug("refreshProperties()");
254 thing.setProperties(getWebTargets().getInformation());
255 propertiesLoaded = true;
259 * Create the dynamic list of scene channels.
261 * @throws HubProcessingException if the web target connection caused an error.
262 * @throws IllegalStateException if this handler is in an illegal state.
264 private void refreshScenes() throws HubProcessingException, IllegalStateException {
265 if (scenesLoaded || disposing) {
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());
279 updateThing(editThing().withChannels(channels).build());
284 * Refresh a single shade.
286 * @param shadeId the id of the shade to be refreshed.
288 private void refreshShade(int shadeId) {
290 Shade shade = getWebTargets().getShade(shadeId);
291 for (ShadeThingHandler handler : getShadeThingHandlers()) {
292 if (handler.notify(shade)) {
296 } catch (HubProcessingException | IllegalStateException e) {
297 logger.warn("refreshShade() exception:{}, message:{}", e.getClass().getSimpleName(), e.getMessage(), e);
302 * Get the full list of shades data and notify each of the thing handlers.
304 * @throws HubProcessingException if the web target connection caused an error.
305 * @throws IllegalStateException if this handler is in an illegal state.
307 private void refreshShades() throws HubProcessingException, IllegalStateException {
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)) {