]> git.basschouten.com Git - openhab-addons.git/blob
bacd143f6248843797b02aac080df97c4ed255fd
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.hdpowerview.internal.handler;
14
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.*;
18
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21
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.OnOffType;
34 import org.openhab.core.library.types.PercentType;
35 import org.openhab.core.library.types.QuantityType;
36 import org.openhab.core.library.types.StopMoveType;
37 import org.openhab.core.library.types.UpDownType;
38 import org.openhab.core.library.unit.Units;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingStatusDetail;
43 import org.openhab.core.types.Command;
44 import org.openhab.core.types.RefreshType;
45 import org.openhab.core.types.State;
46 import org.openhab.core.types.UnDefType;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 /**
51  * Handles commands for an HD PowerView Shade
52  *
53  * @author Andy Lintner - Initial contribution
54  * @author Andrew Fiddian-Green - Added support for secondary rail positions
55  */
56 @NonNullByDefault
57 public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
58
59     private final Logger logger = LoggerFactory.getLogger(HDPowerViewShadeHandler.class);
60
61     private static final int REFRESH_DELAY_SEC = 10;
62     private @Nullable ScheduledFuture<?> refreshFuture = null;
63
64     public HDPowerViewShadeHandler(Thing thing) {
65         super(thing);
66     }
67
68     @Override
69     public void initialize() {
70         try {
71             getShadeId();
72         } catch (NumberFormatException e) {
73             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
74                     "Configuration 'id' not a valid integer");
75             return;
76         }
77         if (getBridgeHandler() == null) {
78             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Hub not configured");
79             return;
80         }
81         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
82     }
83
84     @Override
85     public void handleCommand(ChannelUID channelUID, Command command) {
86         if (RefreshType.REFRESH.equals(command)) {
87             requestRefreshShade();
88             return;
89         }
90
91         switch (channelUID.getId()) {
92             case CHANNEL_SHADE_POSITION:
93                 if (command instanceof PercentType) {
94                     moveShade(PRIMARY_ACTUATOR, ZERO_IS_CLOSED, ((PercentType) command).intValue());
95                 } else if (command instanceof UpDownType) {
96                     moveShade(PRIMARY_ACTUATOR, ZERO_IS_CLOSED, UpDownType.UP.equals(command) ? 0 : 100);
97                 } else if (command instanceof StopMoveType) {
98                     if (StopMoveType.STOP.equals(command)) {
99                         stopShade();
100                     } else {
101                         logger.warn("Unexpected StopMoveType command");
102                     }
103                 }
104                 break;
105
106             case CHANNEL_SHADE_VANE:
107                 if (command instanceof PercentType) {
108                     moveShade(PRIMARY_ACTUATOR, VANE_COORDS, ((PercentType) command).intValue());
109                 } else if (command instanceof OnOffType) {
110                     moveShade(PRIMARY_ACTUATOR, VANE_COORDS, OnOffType.ON.equals(command) ? 100 : 0);
111                 }
112                 break;
113
114             case CHANNEL_SHADE_SECONDARY_POSITION:
115                 if (command instanceof PercentType) {
116                     moveShade(SECONDARY_ACTUATOR, ZERO_IS_OPEN, ((PercentType) command).intValue());
117                 } else if (command instanceof UpDownType) {
118                     moveShade(SECONDARY_ACTUATOR, ZERO_IS_OPEN, UpDownType.UP.equals(command) ? 0 : 100);
119                 } else if (command instanceof StopMoveType) {
120                     if (StopMoveType.STOP.equals(command)) {
121                         stopShade();
122                     } else {
123                         logger.warn("Unexpected StopMoveType command");
124                     }
125                 }
126                 break;
127         }
128     }
129
130     /**
131      * Update the state of the channels based on the ShadeData provided
132      *
133      * @param shadeData the ShadeData to be used; may be null
134      */
135     protected void onReceiveUpdate(@Nullable ShadeData shadeData) {
136         if (shadeData != null) {
137             updateStatus(ThingStatus.ONLINE);
138             updateBindingStates(shadeData.positions);
139             updateState(CHANNEL_SHADE_LOW_BATTERY, shadeData.batteryStatus < 2 ? OnOffType.ON : OnOffType.OFF);
140             updateState(CHANNEL_SHADE_BATTERY_VOLTAGE, new QuantityType<>(shadeData.batteryStrength / 10, Units.VOLT));
141         } else {
142             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
143         }
144     }
145
146     private void updateBindingStates(@Nullable ShadePosition shadePos) {
147         if (shadePos != null) {
148             updateState(CHANNEL_SHADE_POSITION, shadePos.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED));
149             updateState(CHANNEL_SHADE_VANE, shadePos.getState(PRIMARY_ACTUATOR, VANE_COORDS));
150             updateState(CHANNEL_SHADE_SECONDARY_POSITION, shadePos.getState(SECONDARY_ACTUATOR, ZERO_IS_OPEN));
151         } else {
152             updateState(CHANNEL_SHADE_POSITION, UnDefType.UNDEF);
153             updateState(CHANNEL_SHADE_VANE, UnDefType.UNDEF);
154             updateState(CHANNEL_SHADE_SECONDARY_POSITION, UnDefType.UNDEF);
155         }
156     }
157
158     private void moveShade(ActuatorClass actuatorClass, CoordinateSystem coordSys, int newPercent) {
159         try {
160             HDPowerViewHubHandler bridge;
161             if ((bridge = getBridgeHandler()) == null) {
162                 throw new HubProcessingException("Missing bridge handler");
163             }
164             HDPowerViewWebTargets webTargets = bridge.getWebTargets();
165             if (webTargets == null) {
166                 throw new HubProcessingException("Web targets not initialized");
167             }
168             int shadeId = getShadeId();
169
170             switch (actuatorClass) {
171                 case PRIMARY_ACTUATOR:
172                     // write the new primary position
173                     webTargets.moveShade(shadeId, ShadePosition.create(coordSys, newPercent));
174                     break;
175                 case SECONDARY_ACTUATOR:
176                     // read the current primary position; default value 100%
177                     int primaryPercent = 100;
178                     Shade shade = webTargets.getShade(shadeId);
179                     if (shade != null) {
180                         ShadeData shadeData = shade.shade;
181                         if (shadeData != null) {
182                             ShadePosition shadePos = shadeData.positions;
183                             if (shadePos != null) {
184                                 State primaryState = shadePos.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
185                                 if (primaryState instanceof PercentType) {
186                                     primaryPercent = ((PercentType) primaryState).intValue();
187                                 }
188                             }
189                         }
190                     }
191                     // write the current primary position, plus the new secondary position
192                     webTargets.moveShade(shadeId,
193                             ShadePosition.create(ZERO_IS_CLOSED, primaryPercent, ZERO_IS_OPEN, newPercent));
194             }
195         } catch (HubProcessingException | NumberFormatException e) {
196             logger.warn("Unexpected error: {}", e.getMessage());
197             return;
198         } catch (HubMaintenanceException e) {
199             // exceptions are logged in HDPowerViewWebTargets
200             return;
201         }
202     }
203
204     private int getShadeId() throws NumberFormatException {
205         String str = getConfigAs(HDPowerViewShadeConfiguration.class).id;
206         if (str == null) {
207             throw new NumberFormatException("null input string");
208         }
209         return Integer.parseInt(str);
210     }
211
212     private void stopShade() {
213         try {
214             HDPowerViewHubHandler bridge;
215             if ((bridge = getBridgeHandler()) == null) {
216                 throw new HubProcessingException("Missing bridge handler");
217             }
218             HDPowerViewWebTargets webTargets = bridge.getWebTargets();
219             if (webTargets == null) {
220                 throw new HubProcessingException("Web targets not initialized");
221             }
222             int shadeId = getShadeId();
223             webTargets.stopShade(shadeId);
224             requestRefreshShade();
225         } catch (HubProcessingException | NumberFormatException e) {
226             logger.warn("Unexpected error: {}", e.getMessage());
227             return;
228         } catch (HubMaintenanceException e) {
229             // exceptions are logged in HDPowerViewWebTargets
230             return;
231         }
232     }
233
234     /**
235      * Request that the shade shall undergo a 'hard' refresh
236      */
237     protected synchronized void requestRefreshShade() {
238         if (refreshFuture == null) {
239             refreshFuture = scheduler.schedule(this::doRefreshShade, REFRESH_DELAY_SEC, TimeUnit.SECONDS);
240         }
241     }
242
243     private void doRefreshShade() {
244         try {
245             HDPowerViewHubHandler bridge;
246             if ((bridge = getBridgeHandler()) == null) {
247                 throw new HubProcessingException("Missing bridge handler");
248             }
249             HDPowerViewWebTargets webTargets = bridge.getWebTargets();
250             if (webTargets == null) {
251                 throw new HubProcessingException("Web targets not initialized");
252             }
253             int shadeId = getShadeId();
254             Shade shade = webTargets.refreshShade(shadeId);
255             if (shade != null) {
256                 ShadeData shadeData = shade.shade;
257                 if (shadeData != null) {
258                     if (Boolean.TRUE.equals(shadeData.timedOut)) {
259                         logger.warn("Shade {} wireless refresh time out", shadeId);
260                     }
261                 }
262             }
263         } catch (HubProcessingException | NumberFormatException e) {
264             logger.warn("Unexpected error: {}", e.getMessage());
265         } catch (HubMaintenanceException e) {
266             // exceptions are logged in HDPowerViewWebTargets
267         }
268         refreshFuture = null;
269     }
270 }