2 * Copyright (c) 2010-2021 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.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.List;
19 import java.util.Map.Entry;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
23 import javax.ws.rs.ProcessingException;
24 import javax.ws.rs.client.ClientBuilder;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants;
29 import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets;
30 import org.openhab.binding.hdpowerview.internal.HubMaintenanceException;
31 import org.openhab.binding.hdpowerview.internal.api.responses.Scenes;
32 import org.openhab.binding.hdpowerview.internal.api.responses.Scenes.Scene;
33 import org.openhab.binding.hdpowerview.internal.api.responses.Shades;
34 import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData;
35 import org.openhab.binding.hdpowerview.internal.config.HDPowerViewHubConfiguration;
36 import org.openhab.binding.hdpowerview.internal.config.HDPowerViewShadeConfiguration;
37 import org.openhab.core.library.types.OnOffType;
38 import org.openhab.core.thing.Bridge;
39 import org.openhab.core.thing.Channel;
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.ChannelTypeUID;
48 import org.openhab.core.types.Command;
49 import org.openhab.core.types.RefreshType;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
53 import com.google.gson.JsonParseException;
56 * The {@link HDPowerViewHubHandler} is responsible for handling commands, which
57 * are sent to one of the channels.
59 * @author Andy Lintner - Initial contribution
60 * @author Andrew Fiddian-Green - Added support for secondary rail positions
63 public class HDPowerViewHubHandler extends BaseBridgeHandler {
65 private final Logger logger = LoggerFactory.getLogger(HDPowerViewHubHandler.class);
66 private final ClientBuilder clientBuilder;
68 private long refreshInterval;
69 private long hardRefreshInterval;
71 private @Nullable HDPowerViewWebTargets webTargets;
72 private @Nullable ScheduledFuture<?> pollFuture;
73 private @Nullable ScheduledFuture<?> hardRefreshFuture;
75 private final ChannelTypeUID sceneChannelTypeUID = new ChannelTypeUID(HDPowerViewBindingConstants.BINDING_ID,
76 HDPowerViewBindingConstants.CHANNELTYPE_SCENE_ACTIVATE);
78 public HDPowerViewHubHandler(Bridge bridge, ClientBuilder clientBuilder) {
80 this.clientBuilder = clientBuilder;
84 public void handleCommand(ChannelUID channelUID, Command command) {
85 if (RefreshType.REFRESH.equals(command)) {
86 requestRefreshShades();
90 Channel channel = getThing().getChannel(channelUID.getId());
91 if (channel != null && sceneChannelTypeUID.equals(channel.getChannelTypeUID())) {
92 if (OnOffType.ON.equals(command)) {
94 HDPowerViewWebTargets webTargets = this.webTargets;
95 if (webTargets == null) {
96 throw new ProcessingException("Web targets not initialized");
98 webTargets.activateScene(Integer.parseInt(channelUID.getId()));
99 } catch (HubMaintenanceException e) {
100 // exceptions are logged in HDPowerViewWebTargets
101 } catch (NumberFormatException | ProcessingException e) {
102 logger.debug("Unexpected error {}", e.getMessage());
109 public void initialize() {
110 logger.debug("Initializing hub");
111 HDPowerViewHubConfiguration config = getConfigAs(HDPowerViewHubConfiguration.class);
112 String host = config.host;
114 if (host == null || host.isEmpty()) {
115 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Host address must be set");
119 webTargets = new HDPowerViewWebTargets(clientBuilder.build(), host);
120 refreshInterval = config.refresh;
121 hardRefreshInterval = config.hardRefresh;
125 public @Nullable HDPowerViewWebTargets getWebTargets() {
130 public void handleRemoval() {
131 super.handleRemoval();
136 public void dispose() {
141 private void schedulePoll() {
142 ScheduledFuture<?> future = this.pollFuture;
143 if (future != null) {
144 future.cancel(false);
146 logger.debug("Scheduling poll for 5000ms out, then every {}ms", refreshInterval);
147 this.pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 5000, refreshInterval, TimeUnit.MILLISECONDS);
149 future = this.hardRefreshFuture;
150 if (future != null) {
151 future.cancel(false);
153 if (hardRefreshInterval > 0) {
154 logger.debug("Scheduling hard refresh every {}minutes", hardRefreshInterval);
155 this.hardRefreshFuture = scheduler.scheduleWithFixedDelay(this::requestRefreshShades, 1,
156 hardRefreshInterval, TimeUnit.MINUTES);
160 private synchronized void stopPoll() {
161 ScheduledFuture<?> future = this.pollFuture;
162 if (future != null) {
165 this.pollFuture = null;
167 future = this.hardRefreshFuture;
168 if (future != null) {
171 this.hardRefreshFuture = null;
174 private synchronized void poll() {
176 logger.debug("Polling for state");
179 } catch (JsonParseException e) {
180 logger.warn("Bridge returned a bad JSON response: {}", e.getMessage());
181 } catch (ProcessingException e) {
182 logger.warn("Error connecting to bridge: {}", e.getMessage());
183 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, e.getMessage());
184 } catch (HubMaintenanceException e) {
185 // exceptions are logged in HDPowerViewWebTargets
189 private void pollShades() throws JsonParseException, ProcessingException, HubMaintenanceException {
190 HDPowerViewWebTargets webTargets = this.webTargets;
191 if (webTargets == null) {
192 throw new ProcessingException("Web targets not initialized");
195 Shades shades = webTargets.getShades();
196 if (shades == null) {
197 throw new JsonParseException("Missing 'shades' element");
200 List<ShadeData> shadesData = shades.shadeData;
201 if (shadesData == null) {
202 throw new JsonParseException("Missing 'shades.shadeData' element");
205 updateStatus(ThingStatus.ONLINE);
206 logger.debug("Received data for {} shades", shadesData.size());
208 Map<String, ShadeData> idShadeDataMap = getIdShadeDataMap(shadesData);
209 Map<Thing, String> thingIdMap = getThingIdMap();
210 for (Entry<Thing, String> item : thingIdMap.entrySet()) {
211 Thing thing = item.getKey();
212 String shadeId = item.getValue();
213 ShadeData shadeData = idShadeDataMap.get(shadeId);
214 updateShadeThing(shadeId, thing, shadeData);
218 private void updateShadeThing(String shadeId, Thing thing, @Nullable ShadeData shadeData) {
219 HDPowerViewShadeHandler thingHandler = ((HDPowerViewShadeHandler) thing.getHandler());
220 if (thingHandler == null) {
221 logger.debug("Shade '{}' handler not initialized", shadeId);
224 if (shadeData == null) {
225 logger.debug("Shade '{}' has no data in hub", shadeId);
227 logger.debug("Updating shade '{}'", shadeId);
229 thingHandler.onReceiveUpdate(shadeData);
232 private void pollScenes() throws JsonParseException, ProcessingException, HubMaintenanceException {
233 HDPowerViewWebTargets webTargets = this.webTargets;
234 if (webTargets == null) {
235 throw new ProcessingException("Web targets not initialized");
238 Scenes scenes = webTargets.getScenes();
239 if (scenes == null) {
240 throw new JsonParseException("Missing 'scenes' element");
243 List<Scene> sceneData = scenes.sceneData;
244 if (sceneData == null) {
245 throw new JsonParseException("Missing 'scenes.sceneData' element");
247 logger.debug("Received data for {} scenes", sceneData.size());
249 Map<String, Channel> idChannelMap = getIdChannelMap();
250 for (Scene scene : sceneData) {
251 // remove existing scene channel from the map
252 String sceneId = Integer.toString(scene.id);
253 if (idChannelMap.containsKey(sceneId)) {
254 idChannelMap.remove(sceneId);
255 logger.debug("Keeping channel for existing scene '{}'", sceneId);
257 // create a new scene channel
258 ChannelUID channelUID = new ChannelUID(getThing().getUID(), sceneId);
259 Channel channel = ChannelBuilder.create(channelUID, "Switch").withType(sceneChannelTypeUID)
260 .withLabel(scene.getName()).withDescription("Activates the scene " + scene.getName()).build();
261 updateThing(editThing().withChannel(channel).build());
262 logger.debug("Creating new channel for scene '{}'", sceneId);
266 // remove any previously created channels that no longer exist
267 if (!idChannelMap.isEmpty()) {
268 logger.debug("Removing {} orphan scene channels", idChannelMap.size());
269 List<Channel> allChannels = new ArrayList<>(getThing().getChannels());
270 allChannels.removeAll(idChannelMap.values());
271 updateThing(editThing().withChannels(allChannels).build());
275 private Map<Thing, String> getThingIdMap() {
276 Map<Thing, String> ret = new HashMap<>();
277 for (Thing thing : getThing().getThings()) {
278 String id = thing.getConfiguration().as(HDPowerViewShadeConfiguration.class).id;
279 if (id != null && !id.isEmpty()) {
286 private Map<String, ShadeData> getIdShadeDataMap(List<ShadeData> shadeData) {
287 Map<String, ShadeData> ret = new HashMap<>();
288 for (ShadeData shade : shadeData) {
290 ret.put(Integer.toString(shade.id), shade);
296 private Map<String, Channel> getIdChannelMap() {
297 Map<String, Channel> ret = new HashMap<>();
298 for (Channel channel : getThing().getChannels()) {
299 if (sceneChannelTypeUID.equals(channel.getChannelTypeUID())) {
300 ret.put(channel.getUID().getId(), channel);
306 private void requestRefreshShades() {
307 Map<Thing, String> thingIdMap = getThingIdMap();
308 for (Entry<Thing, String> item : thingIdMap.entrySet()) {
309 Thing thing = item.getKey();
310 ThingHandler handler = thing.getHandler();
311 if (handler instanceof HDPowerViewShadeHandler) {
312 ((HDPowerViewShadeHandler) handler).requestRefreshShade();
314 String shadeId = item.getValue();
315 logger.debug("Shade '{}' handler not initialized", shadeId);