]> git.basschouten.com Git - openhab-addons.git/blob
5bbf5fa294c0d6d8e919f17ed1b85e43942415c2
[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 javax.ws.rs.ProcessingException;
23
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;
48
49 /**
50  * Handles commands for an HD PowerView Shade
51  *
52  * @author Andy Lintner - Initial contribution
53  * @author Andrew Fiddian-Green - Added support for secondary rail positions
54  */
55 @NonNullByDefault
56 public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
57
58     private final Logger logger = LoggerFactory.getLogger(HDPowerViewShadeHandler.class);
59
60     private static final int REFRESH_DELAY_SEC = 10;
61     private @Nullable ScheduledFuture<?> refreshFuture = null;
62
63     public HDPowerViewShadeHandler(Thing thing) {
64         super(thing);
65     }
66
67     @Override
68     public void initialize() {
69         try {
70             getShadeId();
71         } catch (NumberFormatException e) {
72             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
73                     "Configuration 'id' not a valid integer");
74             return;
75         }
76         if (getBridgeHandler() == null) {
77             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Hub not configured");
78             return;
79         }
80         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
81     }
82
83     @Override
84     public void handleCommand(ChannelUID channelUID, Command command) {
85         if (RefreshType.REFRESH.equals(command)) {
86             requestRefreshShade();
87             return;
88         }
89
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)) {
98                         stopShade();
99                     } else {
100                         logger.warn("Unexpected StopMoveType command");
101                     }
102                 }
103                 break;
104
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);
110                 }
111                 break;
112
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)) {
120                         stopShade();
121                     } else {
122                         logger.warn("Unexpected StopMoveType command");
123                     }
124                 }
125                 break;
126         }
127     }
128
129     /**
130      * Update the state of the channels based on the ShadeData provided
131      * 
132      * @param shadeData the ShadeData to be used; may be null
133      */
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);
139         } else {
140             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
141         }
142     }
143
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));
149         } else {
150             updateState(CHANNEL_SHADE_POSITION, UnDefType.UNDEF);
151             updateState(CHANNEL_SHADE_VANE, UnDefType.UNDEF);
152             updateState(CHANNEL_SHADE_SECONDARY_POSITION, UnDefType.UNDEF);
153         }
154     }
155
156     private void moveShade(ActuatorClass actuatorClass, CoordinateSystem coordSys, int newPercent) {
157         try {
158             HDPowerViewHubHandler bridge;
159             if ((bridge = getBridgeHandler()) == null) {
160                 throw new ProcessingException("Missing bridge handler");
161             }
162             HDPowerViewWebTargets webTargets = bridge.getWebTargets();
163             if (webTargets == null) {
164                 throw new ProcessingException("Web targets not initialized");
165             }
166             int shadeId = getShadeId();
167
168             switch (actuatorClass) {
169                 case PRIMARY_ACTUATOR:
170                     // write the new primary position
171                     webTargets.moveShade(shadeId, ShadePosition.create(coordSys, newPercent));
172                     break;
173                 case SECONDARY_ACTUATOR:
174                     // read the current primary position; default value 100%
175                     int primaryPercent = 100;
176                     Shade shade = webTargets.getShade(shadeId);
177                     if (shade != null) {
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();
185                                 }
186                             }
187                         }
188                     }
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));
192             }
193         } catch (ProcessingException | NumberFormatException e) {
194             logger.warn("Unexpected error: {}", e.getMessage());
195             return;
196         } catch (HubMaintenanceException e) {
197             // exceptions are logged in HDPowerViewWebTargets
198             return;
199         }
200     }
201
202     private int getShadeId() throws NumberFormatException {
203         String str = getConfigAs(HDPowerViewShadeConfiguration.class).id;
204         if (str == null) {
205             throw new NumberFormatException("null input string");
206         }
207         return Integer.parseInt(str);
208     }
209
210     private void stopShade() {
211         try {
212             HDPowerViewHubHandler bridge;
213             if ((bridge = getBridgeHandler()) == null) {
214                 throw new ProcessingException("Missing bridge handler");
215             }
216             HDPowerViewWebTargets webTargets = bridge.getWebTargets();
217             if (webTargets == null) {
218                 throw new ProcessingException("Web targets not initialized");
219             }
220             int shadeId = getShadeId();
221             webTargets.stopShade(shadeId);
222             requestRefreshShade();
223         } catch (ProcessingException | NumberFormatException e) {
224             logger.warn("Unexpected error: {}", e.getMessage());
225             return;
226         } catch (HubMaintenanceException e) {
227             // exceptions are logged in HDPowerViewWebTargets
228             return;
229         }
230     }
231
232     /**
233      * Request that the shade shall undergo a 'hard' refresh
234      */
235     protected synchronized void requestRefreshShade() {
236         if (refreshFuture == null) {
237             refreshFuture = scheduler.schedule(this::doRefreshShade, REFRESH_DELAY_SEC, TimeUnit.SECONDS);
238         }
239     }
240
241     private void doRefreshShade() {
242         try {
243             HDPowerViewHubHandler bridge;
244             if ((bridge = getBridgeHandler()) == null) {
245                 throw new ProcessingException("Missing bridge handler");
246             }
247             HDPowerViewWebTargets webTargets = bridge.getWebTargets();
248             if (webTargets == null) {
249                 throw new ProcessingException("Web targets not initialized");
250             }
251             int shadeId = getShadeId();
252             Shade shade = webTargets.refreshShade(shadeId);
253             if (shade != null) {
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);
258                     }
259                 }
260             }
261         } catch (ProcessingException | NumberFormatException e) {
262             logger.warn("Unexpected error: {}", e.getMessage());
263         } catch (HubMaintenanceException e) {
264             // exceptions are logged in HDPowerViewWebTargets
265         }
266         refreshFuture = null;
267     }
268 }