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 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 org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets;
25 import org.openhab.binding.hdpowerview.internal.HubMaintenanceException;
26 import org.openhab.binding.hdpowerview.internal.HubProcessingException;
27 import org.openhab.binding.hdpowerview.internal.api.ActuatorClass;
28 import org.openhab.binding.hdpowerview.internal.api.CoordinateSystem;
29 import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
30 import org.openhab.binding.hdpowerview.internal.api.responses.Shade;
31 import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData;
32 import org.openhab.binding.hdpowerview.internal.config.HDPowerViewShadeConfiguration;
33 import org.openhab.core.library.types.DecimalType;
34 import org.openhab.core.library.types.OnOffType;
35 import org.openhab.core.library.types.PercentType;
36 import org.openhab.core.library.types.QuantityType;
37 import org.openhab.core.library.types.StopMoveType;
38 import org.openhab.core.library.types.UpDownType;
39 import org.openhab.core.library.unit.Units;
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.types.Command;
45 import org.openhab.core.types.RefreshType;
46 import org.openhab.core.types.State;
47 import org.openhab.core.types.UnDefType;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
52 * Handles commands for an HD PowerView Shade
54 * @author Andy Lintner - Initial contribution
55 * @author Andrew Fiddian-Green - Added support for secondary rail positions
58 public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
60 private final Logger logger = LoggerFactory.getLogger(HDPowerViewShadeHandler.class);
62 private static final int REFRESH_DELAY_SEC = 10;
63 private @Nullable ScheduledFuture<?> refreshFuture = null;
65 public HDPowerViewShadeHandler(Thing thing) {
70 public void initialize() {
73 } catch (NumberFormatException e) {
74 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
75 "Configuration 'id' not a valid integer");
78 if (getBridgeHandler() == null) {
79 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Hub not configured");
82 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
86 public void handleCommand(ChannelUID channelUID, Command command) {
87 if (RefreshType.REFRESH.equals(command)) {
88 requestRefreshShade();
92 switch (channelUID.getId()) {
93 case CHANNEL_SHADE_POSITION:
94 if (command instanceof PercentType) {
95 moveShade(PRIMARY_ACTUATOR, ZERO_IS_CLOSED, ((PercentType) command).intValue());
96 } else if (command instanceof UpDownType) {
97 moveShade(PRIMARY_ACTUATOR, ZERO_IS_CLOSED, UpDownType.UP.equals(command) ? 0 : 100);
98 } else if (command instanceof StopMoveType) {
99 if (StopMoveType.STOP.equals(command)) {
102 logger.warn("Unexpected StopMoveType command");
107 case CHANNEL_SHADE_VANE:
108 if (command instanceof PercentType) {
109 moveShade(PRIMARY_ACTUATOR, VANE_COORDS, ((PercentType) command).intValue());
110 } else if (command instanceof OnOffType) {
111 moveShade(PRIMARY_ACTUATOR, VANE_COORDS, OnOffType.ON.equals(command) ? 100 : 0);
115 case CHANNEL_SHADE_SECONDARY_POSITION:
116 if (command instanceof PercentType) {
117 moveShade(SECONDARY_ACTUATOR, ZERO_IS_OPEN, ((PercentType) command).intValue());
118 } else if (command instanceof UpDownType) {
119 moveShade(SECONDARY_ACTUATOR, ZERO_IS_OPEN, UpDownType.UP.equals(command) ? 0 : 100);
120 } else if (command instanceof StopMoveType) {
121 if (StopMoveType.STOP.equals(command)) {
124 logger.warn("Unexpected StopMoveType command");
132 * Update the state of the channels based on the ShadeData provided
134 * @param shadeData the ShadeData to be used; may be null
136 protected void onReceiveUpdate(@Nullable ShadeData shadeData) {
137 if (shadeData != null) {
138 updateStatus(ThingStatus.ONLINE);
139 updateBindingStates(shadeData.positions);
140 updateBatteryLevel(shadeData.batteryStatus);
141 updateState(CHANNEL_SHADE_BATTERY_VOLTAGE, new QuantityType<>(shadeData.batteryStrength / 10, Units.VOLT));
142 updateState(CHANNEL_SHADE_SIGNAL_STRENGTH, new DecimalType(shadeData.signalStrength));
144 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
148 private void updateBindingStates(@Nullable ShadePosition shadePos) {
149 if (shadePos != null) {
150 updateState(CHANNEL_SHADE_POSITION, shadePos.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED));
151 updateState(CHANNEL_SHADE_VANE, shadePos.getState(PRIMARY_ACTUATOR, VANE_COORDS));
152 updateState(CHANNEL_SHADE_SECONDARY_POSITION, shadePos.getState(SECONDARY_ACTUATOR, ZERO_IS_OPEN));
154 updateState(CHANNEL_SHADE_POSITION, UnDefType.UNDEF);
155 updateState(CHANNEL_SHADE_VANE, UnDefType.UNDEF);
156 updateState(CHANNEL_SHADE_SECONDARY_POSITION, UnDefType.UNDEF);
160 private void updateBatteryLevel(int batteryStatus) {
162 switch (batteryStatus) {
170 case 4: // Plugged in
173 default: // No status available (0) or invalid
176 updateState(CHANNEL_SHADE_LOW_BATTERY, batteryStatus == 1 ? OnOffType.ON : OnOffType.OFF);
177 updateState(CHANNEL_SHADE_BATTERY_LEVEL, new DecimalType(mappedValue));
180 private void moveShade(ActuatorClass actuatorClass, CoordinateSystem coordSys, int newPercent) {
182 HDPowerViewHubHandler bridge;
183 if ((bridge = getBridgeHandler()) == null) {
184 throw new HubProcessingException("Missing bridge handler");
186 HDPowerViewWebTargets webTargets = bridge.getWebTargets();
187 if (webTargets == null) {
188 throw new HubProcessingException("Web targets not initialized");
190 int shadeId = getShadeId();
192 switch (actuatorClass) {
193 case PRIMARY_ACTUATOR:
194 // write the new primary position
195 webTargets.moveShade(shadeId, ShadePosition.create(coordSys, newPercent));
197 case SECONDARY_ACTUATOR:
198 // read the current primary position; default value 100%
199 int primaryPercent = 100;
200 Shade shade = webTargets.getShade(shadeId);
202 ShadeData shadeData = shade.shade;
203 if (shadeData != null) {
204 ShadePosition shadePos = shadeData.positions;
205 if (shadePos != null) {
206 State primaryState = shadePos.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
207 if (primaryState instanceof PercentType) {
208 primaryPercent = ((PercentType) primaryState).intValue();
213 // write the current primary position, plus the new secondary position
214 webTargets.moveShade(shadeId,
215 ShadePosition.create(ZERO_IS_CLOSED, primaryPercent, ZERO_IS_OPEN, newPercent));
217 } catch (HubProcessingException | NumberFormatException e) {
218 logger.warn("Unexpected error: {}", e.getMessage());
220 } catch (HubMaintenanceException e) {
221 // exceptions are logged in HDPowerViewWebTargets
226 private int getShadeId() throws NumberFormatException {
227 String str = getConfigAs(HDPowerViewShadeConfiguration.class).id;
229 throw new NumberFormatException("null input string");
231 return Integer.parseInt(str);
234 private void stopShade() {
236 HDPowerViewHubHandler bridge;
237 if ((bridge = getBridgeHandler()) == null) {
238 throw new HubProcessingException("Missing bridge handler");
240 HDPowerViewWebTargets webTargets = bridge.getWebTargets();
241 if (webTargets == null) {
242 throw new HubProcessingException("Web targets not initialized");
244 int shadeId = getShadeId();
245 webTargets.stopShade(shadeId);
246 requestRefreshShade();
247 } catch (HubProcessingException | NumberFormatException e) {
248 logger.warn("Unexpected error: {}", e.getMessage());
250 } catch (HubMaintenanceException e) {
251 // exceptions are logged in HDPowerViewWebTargets
257 * Request that the shade shall undergo a 'hard' refresh
259 protected synchronized void requestRefreshShade() {
260 if (refreshFuture == null) {
261 refreshFuture = scheduler.schedule(this::doRefreshShade, REFRESH_DELAY_SEC, TimeUnit.SECONDS);
265 private void doRefreshShade() {
267 HDPowerViewHubHandler bridge;
268 if ((bridge = getBridgeHandler()) == null) {
269 throw new HubProcessingException("Missing bridge handler");
271 HDPowerViewWebTargets webTargets = bridge.getWebTargets();
272 if (webTargets == null) {
273 throw new HubProcessingException("Web targets not initialized");
275 int shadeId = getShadeId();
276 Shade shade = webTargets.refreshShade(shadeId);
278 ShadeData shadeData = shade.shade;
279 if (shadeData != null) {
280 if (Boolean.TRUE.equals(shadeData.timedOut)) {
281 logger.warn("Shade {} wireless refresh time out", shadeId);
285 } catch (HubProcessingException | NumberFormatException e) {
286 logger.warn("Unexpected error: {}", e.getMessage());
287 } catch (HubMaintenanceException e) {
288 // exceptions are logged in HDPowerViewWebTargets
290 refreshFuture = null;