2 * Copyright (c) 2010-2020 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);
67 private long refreshInterval;
68 private long hardRefreshInterval;
70 private @Nullable HDPowerViewWebTargets webTargets;
71 private @Nullable ScheduledFuture<?> pollFuture;
72 private @Nullable ScheduledFuture<?> hardRefreshFuture;
74 private final ChannelTypeUID sceneChannelTypeUID = new ChannelTypeUID(HDPowerViewBindingConstants.BINDING_ID,
75 HDPowerViewBindingConstants.CHANNELTYPE_SCENE_ACTIVATE);
77 public HDPowerViewHubHandler(Bridge bridge) {
82 public void handleCommand(ChannelUID channelUID, Command command) {
83 if (RefreshType.REFRESH.equals(command)) {
84 requestRefreshShades();
88 Channel channel = getThing().getChannel(channelUID.getId());
89 if (channel != null && sceneChannelTypeUID.equals(channel.getChannelTypeUID())) {
90 if (OnOffType.ON.equals(command)) {
92 HDPowerViewWebTargets webTargets = this.webTargets;
93 if (webTargets == null) {
94 throw new ProcessingException("Web targets not initialized");
96 webTargets.activateScene(Integer.parseInt(channelUID.getId()));
97 } catch (HubMaintenanceException e) {
98 // exceptions are logged in HDPowerViewWebTargets
99 } catch (NumberFormatException | ProcessingException e) {
100 logger.debug("Unexpected error {}", e.getMessage());
107 public void initialize() {
108 logger.debug("Initializing hub");
109 HDPowerViewHubConfiguration config = getConfigAs(HDPowerViewHubConfiguration.class);
110 String host = config.host;
112 if (host == null || host.isEmpty()) {
113 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Host address must be set");
117 webTargets = new HDPowerViewWebTargets(ClientBuilder.newClient(), host);
118 refreshInterval = config.refresh;
119 hardRefreshInterval = config.hardRefresh;
123 public @Nullable HDPowerViewWebTargets getWebTargets() {
128 public void handleRemoval() {
129 super.handleRemoval();
134 public void dispose() {
139 private void schedulePoll() {
140 ScheduledFuture<?> future = this.pollFuture;
141 if (future != null) {
142 future.cancel(false);
144 logger.debug("Scheduling poll for 5000ms out, then every {}ms", refreshInterval);
145 this.pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 5000, refreshInterval, TimeUnit.MILLISECONDS);
147 future = this.hardRefreshFuture;
148 if (future != null) {
149 future.cancel(false);
151 if (hardRefreshInterval > 0) {
152 logger.debug("Scheduling hard refresh every {}minutes", hardRefreshInterval);
153 this.hardRefreshFuture = scheduler.scheduleWithFixedDelay(this::requestRefreshShades, 1,
154 hardRefreshInterval, TimeUnit.MINUTES);
158 private synchronized void stopPoll() {
159 ScheduledFuture<?> future = this.pollFuture;
160 if (future != null) {
163 this.pollFuture = null;
165 future = this.hardRefreshFuture;
166 if (future != null) {
169 this.hardRefreshFuture = null;
172 private synchronized void poll() {
174 logger.debug("Polling for state");
177 } catch (JsonParseException e) {
178 logger.warn("Bridge returned a bad JSON response: {}", e.getMessage());
179 } catch (ProcessingException e) {
180 logger.warn("Error connecting to bridge: {}", e.getMessage());
181 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, e.getMessage());
182 } catch (HubMaintenanceException e) {
183 // exceptions are logged in HDPowerViewWebTargets
187 private void pollShades() throws JsonParseException, ProcessingException, HubMaintenanceException {
188 HDPowerViewWebTargets webTargets = this.webTargets;
189 if (webTargets == null) {
190 throw new ProcessingException("Web targets not initialized");
193 Shades shades = webTargets.getShades();
194 if (shades == null) {
195 throw new JsonParseException("Missing 'shades' element");
198 List<ShadeData> shadesData = shades.shadeData;
199 if (shadesData == null) {
200 throw new JsonParseException("Missing 'shades.shadeData' element");
203 updateStatus(ThingStatus.ONLINE);
204 logger.debug("Received data for {} shades", shadesData.size());
206 Map<String, ShadeData> idShadeDataMap = getIdShadeDataMap(shadesData);
207 Map<Thing, String> thingIdMap = getThingIdMap();
208 for (Entry<Thing, String> item : thingIdMap.entrySet()) {
209 Thing thing = item.getKey();
210 String shadeId = item.getValue();
211 ShadeData shadeData = idShadeDataMap.get(shadeId);
212 updateShadeThing(shadeId, thing, shadeData);
216 private void updateShadeThing(String shadeId, Thing thing, @Nullable ShadeData shadeData) {
217 HDPowerViewShadeHandler thingHandler = ((HDPowerViewShadeHandler) thing.getHandler());
218 if (thingHandler == null) {
219 logger.debug("Shade '{}' handler not initialized", shadeId);
222 if (shadeData == null) {
223 logger.debug("Shade '{}' has no data in hub", shadeId);
225 logger.debug("Updating shade '{}'", shadeId);
227 thingHandler.onReceiveUpdate(shadeData);
230 private void pollScenes() throws JsonParseException, ProcessingException, HubMaintenanceException {
231 HDPowerViewWebTargets webTargets = this.webTargets;
232 if (webTargets == null) {
233 throw new ProcessingException("Web targets not initialized");
236 Scenes scenes = webTargets.getScenes();
237 if (scenes == null) {
238 throw new JsonParseException("Missing 'scenes' element");
241 List<Scene> sceneData = scenes.sceneData;
242 if (sceneData == null) {
243 throw new JsonParseException("Missing 'scenes.sceneData' element");
245 logger.debug("Received data for {} scenes", sceneData.size());
247 Map<String, Channel> idChannelMap = getIdChannelMap();
248 for (Scene scene : sceneData) {
249 // remove existing scene channel from the map
250 String sceneId = Integer.toString(scene.id);
251 if (idChannelMap.containsKey(sceneId)) {
252 idChannelMap.remove(sceneId);
253 logger.debug("Keeping channel for existing scene '{}'", sceneId);
255 // create a new scene channel
256 ChannelUID channelUID = new ChannelUID(getThing().getUID(), sceneId);
257 Channel channel = ChannelBuilder.create(channelUID, "Switch").withType(sceneChannelTypeUID)
258 .withLabel(scene.getName()).withDescription("Activates the scene " + scene.getName()).build();
259 updateThing(editThing().withChannel(channel).build());
260 logger.debug("Creating new channel for scene '{}'", sceneId);
264 // remove any previously created channels that no longer exist
265 if (!idChannelMap.isEmpty()) {
266 logger.debug("Removing {} orphan scene channels", idChannelMap.size());
267 List<Channel> allChannels = new ArrayList<>(getThing().getChannels());
268 allChannels.removeAll(idChannelMap.values());
269 updateThing(editThing().withChannels(allChannels).build());
273 private Map<Thing, String> getThingIdMap() {
274 Map<Thing, String> ret = new HashMap<>();
275 for (Thing thing : getThing().getThings()) {
276 String id = thing.getConfiguration().as(HDPowerViewShadeConfiguration.class).id;
277 if (id != null && !id.isEmpty()) {
284 private Map<String, ShadeData> getIdShadeDataMap(List<ShadeData> shadeData) {
285 Map<String, ShadeData> ret = new HashMap<>();
286 for (ShadeData shade : shadeData) {
288 ret.put(Integer.toString(shade.id), shade);
294 private Map<String, Channel> getIdChannelMap() {
295 Map<String, Channel> ret = new HashMap<>();
296 for (Channel channel : getThing().getChannels()) {
297 if (sceneChannelTypeUID.equals(channel.getChannelTypeUID())) {
298 ret.put(channel.getUID().getId(), channel);
304 private void requestRefreshShades() {
305 Map<Thing, String> thingIdMap = getThingIdMap();
306 for (Entry<Thing, String> item : thingIdMap.entrySet()) {
307 Thing thing = item.getKey();
308 ThingHandler handler = thing.getHandler();
309 if (handler instanceof HDPowerViewShadeHandler) {
310 ((HDPowerViewShadeHandler) handler).requestRefreshShade();
312 String shadeId = item.getValue();
313 logger.debug("Shade '{}' handler not initialized", shadeId);