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