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 static org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants.*;
16 import static org.openhab.binding.hdpowerview.internal.api.ActuatorClass.*;
17 import static org.openhab.binding.hdpowerview.internal.api.CoordinateSystem.*;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
22 import javax.ws.rs.ProcessingException;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets;
27 import org.openhab.binding.hdpowerview.internal.HubMaintenanceException;
28 import org.openhab.binding.hdpowerview.internal.api.ActuatorClass;
29 import org.openhab.binding.hdpowerview.internal.api.CoordinateSystem;
30 import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
31 import org.openhab.binding.hdpowerview.internal.api.responses.Shade;
32 import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData;
33 import org.openhab.binding.hdpowerview.internal.config.HDPowerViewShadeConfiguration;
34 import org.openhab.core.library.types.OnOffType;
35 import org.openhab.core.library.types.PercentType;
36 import org.openhab.core.library.types.StopMoveType;
37 import org.openhab.core.library.types.UpDownType;
38 import org.openhab.core.thing.ChannelUID;
39 import org.openhab.core.thing.Thing;
40 import org.openhab.core.thing.ThingStatus;
41 import org.openhab.core.thing.ThingStatusDetail;
42 import org.openhab.core.types.Command;
43 import org.openhab.core.types.RefreshType;
44 import org.openhab.core.types.State;
45 import org.openhab.core.types.UnDefType;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * Handles commands for an HD PowerView Shade
52 * @author Andy Lintner - Initial contribution
53 * @author Andrew Fiddian-Green - Added support for secondary rail positions
56 public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
58 private final Logger logger = LoggerFactory.getLogger(HDPowerViewShadeHandler.class);
60 private static final int REFRESH_DELAY_SEC = 10;
61 private @Nullable ScheduledFuture<?> refreshFuture = null;
63 public HDPowerViewShadeHandler(Thing thing) {
68 public void initialize() {
71 } catch (NumberFormatException e) {
72 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
73 "Configuration 'id' not a valid integer");
76 if (getBridgeHandler() == null) {
77 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Hub not configured");
80 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
84 public void handleCommand(ChannelUID channelUID, Command command) {
85 if (RefreshType.REFRESH.equals(command)) {
86 requestRefreshShade();
90 switch (channelUID.getId()) {
91 case CHANNEL_SHADE_POSITION:
92 if (command instanceof PercentType) {
93 moveShade(PRIMARY_ACTUATOR, ZERO_IS_CLOSED, ((PercentType) command).intValue());
94 } else if (command instanceof UpDownType) {
95 moveShade(PRIMARY_ACTUATOR, ZERO_IS_CLOSED, UpDownType.UP.equals(command) ? 0 : 100);
96 } else if (command instanceof StopMoveType) {
97 if (StopMoveType.STOP.equals(command)) {
100 logger.warn("Unexpected StopMoveType command");
105 case CHANNEL_SHADE_VANE:
106 if (command instanceof PercentType) {
107 moveShade(PRIMARY_ACTUATOR, VANE_COORDS, ((PercentType) command).intValue());
108 } else if (command instanceof OnOffType) {
109 moveShade(PRIMARY_ACTUATOR, VANE_COORDS, OnOffType.ON.equals(command) ? 100 : 0);
113 case CHANNEL_SHADE_SECONDARY_POSITION:
114 if (command instanceof PercentType) {
115 moveShade(SECONDARY_ACTUATOR, ZERO_IS_OPEN, ((PercentType) command).intValue());
116 } else if (command instanceof UpDownType) {
117 moveShade(SECONDARY_ACTUATOR, ZERO_IS_OPEN, UpDownType.UP.equals(command) ? 0 : 100);
118 } else if (command instanceof StopMoveType) {
119 if (StopMoveType.STOP.equals(command)) {
122 logger.warn("Unexpected StopMoveType command");
130 * Update the state of the channels based on the ShadeData provided
132 * @param shadeData the ShadeData to be used; may be null
134 protected void onReceiveUpdate(@Nullable ShadeData shadeData) {
135 if (shadeData != null) {
136 updateStatus(ThingStatus.ONLINE);
137 updateBindingStates(shadeData.positions);
138 updateState(CHANNEL_SHADE_LOW_BATTERY, shadeData.batteryStatus < 2 ? OnOffType.ON : OnOffType.OFF);
140 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
144 private void updateBindingStates(@Nullable ShadePosition shadePos) {
145 if (shadePos != null) {
146 updateState(CHANNEL_SHADE_POSITION, shadePos.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED));
147 updateState(CHANNEL_SHADE_VANE, shadePos.getState(PRIMARY_ACTUATOR, VANE_COORDS));
148 updateState(CHANNEL_SHADE_SECONDARY_POSITION, shadePos.getState(SECONDARY_ACTUATOR, ZERO_IS_OPEN));
150 updateState(CHANNEL_SHADE_POSITION, UnDefType.UNDEF);
151 updateState(CHANNEL_SHADE_VANE, UnDefType.UNDEF);
152 updateState(CHANNEL_SHADE_SECONDARY_POSITION, UnDefType.UNDEF);
156 private void moveShade(ActuatorClass actuatorClass, CoordinateSystem coordSys, int newPercent) {
158 HDPowerViewHubHandler bridge;
159 if ((bridge = getBridgeHandler()) == null) {
160 throw new ProcessingException("Missing bridge handler");
162 HDPowerViewWebTargets webTargets = bridge.getWebTargets();
163 if (webTargets == null) {
164 throw new ProcessingException("Web targets not initialized");
166 int shadeId = getShadeId();
168 switch (actuatorClass) {
169 case PRIMARY_ACTUATOR:
170 // write the new primary position
171 webTargets.moveShade(shadeId, ShadePosition.create(coordSys, newPercent));
173 case SECONDARY_ACTUATOR:
174 // read the current primary position; default value 100%
175 int primaryPercent = 100;
176 Shade shade = webTargets.getShade(shadeId);
178 ShadeData shadeData = shade.shade;
179 if (shadeData != null) {
180 ShadePosition shadePos = shadeData.positions;
181 if (shadePos != null) {
182 State primaryState = shadePos.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
183 if (primaryState instanceof PercentType) {
184 primaryPercent = ((PercentType) primaryState).intValue();
189 // write the current primary position, plus the new secondary position
190 webTargets.moveShade(shadeId,
191 ShadePosition.create(ZERO_IS_CLOSED, primaryPercent, ZERO_IS_OPEN, newPercent));
193 } catch (ProcessingException | NumberFormatException e) {
194 logger.warn("Unexpected error: {}", e.getMessage());
196 } catch (HubMaintenanceException e) {
197 // exceptions are logged in HDPowerViewWebTargets
202 private int getShadeId() throws NumberFormatException {
203 String str = getConfigAs(HDPowerViewShadeConfiguration.class).id;
205 throw new NumberFormatException("null input string");
207 return Integer.parseInt(str);
210 private void stopShade() {
212 HDPowerViewHubHandler bridge;
213 if ((bridge = getBridgeHandler()) == null) {
214 throw new ProcessingException("Missing bridge handler");
216 HDPowerViewWebTargets webTargets = bridge.getWebTargets();
217 if (webTargets == null) {
218 throw new ProcessingException("Web targets not initialized");
220 int shadeId = getShadeId();
221 webTargets.stopShade(shadeId);
222 requestRefreshShade();
223 } catch (ProcessingException | NumberFormatException e) {
224 logger.warn("Unexpected error: {}", e.getMessage());
226 } catch (HubMaintenanceException e) {
227 // exceptions are logged in HDPowerViewWebTargets
233 * Request that the shade shall undergo a 'hard' refresh
235 protected synchronized void requestRefreshShade() {
236 if (refreshFuture == null) {
237 refreshFuture = scheduler.schedule(this::doRefreshShade, REFRESH_DELAY_SEC, TimeUnit.SECONDS);
241 private void doRefreshShade() {
243 HDPowerViewHubHandler bridge;
244 if ((bridge = getBridgeHandler()) == null) {
245 throw new ProcessingException("Missing bridge handler");
247 HDPowerViewWebTargets webTargets = bridge.getWebTargets();
248 if (webTargets == null) {
249 throw new ProcessingException("Web targets not initialized");
251 int shadeId = getShadeId();
252 Shade shade = webTargets.refreshShade(shadeId);
254 ShadeData shadeData = shade.shade;
255 if (shadeData != null) {
256 if (Boolean.TRUE.equals(shadeData.timedOut)) {
257 logger.warn("Shade {} wireless refresh time out", shadeId);
261 } catch (ProcessingException | NumberFormatException e) {
262 logger.warn("Unexpected error: {}", e.getMessage());
263 } catch (HubMaintenanceException e) {
264 // exceptions are logged in HDPowerViewWebTargets
266 refreshFuture = null;