]> git.basschouten.com Git - openhab-addons.git/blob
42514597bafbf3694ae4acf711f265f045b32344
[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.NotSupportedException;
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.HubProcessingException;
29 import org.openhab.binding.hdpowerview.internal.api.ActuatorClass;
30 import org.openhab.binding.hdpowerview.internal.api.CoordinateSystem;
31 import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
32 import org.openhab.binding.hdpowerview.internal.api.responses.Shade;
33 import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData;
34 import org.openhab.binding.hdpowerview.internal.config.HDPowerViewShadeConfiguration;
35 import org.openhab.core.library.types.DecimalType;
36 import org.openhab.core.library.types.OnOffType;
37 import org.openhab.core.library.types.PercentType;
38 import org.openhab.core.library.types.QuantityType;
39 import org.openhab.core.library.types.StopMoveType;
40 import org.openhab.core.library.types.UpDownType;
41 import org.openhab.core.library.unit.Units;
42 import org.openhab.core.thing.ChannelUID;
43 import org.openhab.core.thing.Thing;
44 import org.openhab.core.thing.ThingStatus;
45 import org.openhab.core.thing.ThingStatusDetail;
46 import org.openhab.core.types.Command;
47 import org.openhab.core.types.RefreshType;
48 import org.openhab.core.types.State;
49 import org.openhab.core.types.UnDefType;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 /**
54  * Handles commands for an HD PowerView Shade
55  *
56  * @author Andy Lintner - Initial contribution
57  * @author Andrew Fiddian-Green - Added support for secondary rail positions
58  */
59 @NonNullByDefault
60 public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
61
62     private enum RefreshKind {
63         POSITION,
64         BATTERY_LEVEL
65     }
66
67     private final Logger logger = LoggerFactory.getLogger(HDPowerViewShadeHandler.class);
68
69     private static final int REFRESH_DELAY_SEC = 10;
70     private @Nullable ScheduledFuture<?> refreshPositionFuture = null;
71     private @Nullable ScheduledFuture<?> refreshBatteryLevelFuture = null;
72
73     public HDPowerViewShadeHandler(Thing thing) {
74         super(thing);
75     }
76
77     @Override
78     public void initialize() {
79         try {
80             getShadeId();
81         } catch (NumberFormatException e) {
82             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
83                     "Configuration 'id' not a valid integer");
84             return;
85         }
86         if (getBridgeHandler() == null) {
87             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Hub not configured");
88             return;
89         }
90         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
91     }
92
93     @Override
94     public void handleCommand(ChannelUID channelUID, Command command) {
95         if (RefreshType.REFRESH.equals(command)) {
96             requestRefreshShadePosition();
97             return;
98         }
99
100         switch (channelUID.getId()) {
101             case CHANNEL_SHADE_POSITION:
102                 if (command instanceof PercentType) {
103                     moveShade(PRIMARY_ACTUATOR, ZERO_IS_CLOSED, ((PercentType) command).intValue());
104                 } else if (command instanceof UpDownType) {
105                     moveShade(PRIMARY_ACTUATOR, ZERO_IS_CLOSED, UpDownType.UP.equals(command) ? 0 : 100);
106                 } else if (command instanceof StopMoveType) {
107                     if (StopMoveType.STOP.equals(command)) {
108                         stopShade();
109                     } else {
110                         logger.warn("Unexpected StopMoveType command");
111                     }
112                 }
113                 break;
114
115             case CHANNEL_SHADE_VANE:
116                 if (command instanceof PercentType) {
117                     moveShade(PRIMARY_ACTUATOR, VANE_COORDS, ((PercentType) command).intValue());
118                 } else if (command instanceof OnOffType) {
119                     moveShade(PRIMARY_ACTUATOR, VANE_COORDS, OnOffType.ON.equals(command) ? 100 : 0);
120                 }
121                 break;
122
123             case CHANNEL_SHADE_SECONDARY_POSITION:
124                 if (command instanceof PercentType) {
125                     moveShade(SECONDARY_ACTUATOR, ZERO_IS_OPEN, ((PercentType) command).intValue());
126                 } else if (command instanceof UpDownType) {
127                     moveShade(SECONDARY_ACTUATOR, ZERO_IS_OPEN, UpDownType.UP.equals(command) ? 0 : 100);
128                 } else if (command instanceof StopMoveType) {
129                     if (StopMoveType.STOP.equals(command)) {
130                         stopShade();
131                     } else {
132                         logger.warn("Unexpected StopMoveType command");
133                     }
134                 }
135                 break;
136         }
137     }
138
139     /**
140      * Update the state of the channels based on the ShadeData provided
141      *
142      * @param shadeData the ShadeData to be used; may be null
143      */
144     protected void onReceiveUpdate(@Nullable ShadeData shadeData) {
145         if (shadeData != null) {
146             updateStatus(ThingStatus.ONLINE);
147             updateBindingStates(shadeData.positions);
148             updateBatteryLevel(shadeData.batteryStatus);
149             updateState(CHANNEL_SHADE_BATTERY_VOLTAGE,
150                     shadeData.batteryStrength > 0 ? new QuantityType<>(shadeData.batteryStrength / 10, Units.VOLT)
151                             : UnDefType.UNDEF);
152             updateState(CHANNEL_SHADE_SIGNAL_STRENGTH, new DecimalType(shadeData.signalStrength));
153         } else {
154             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
155         }
156     }
157
158     private void updateBindingStates(@Nullable ShadePosition shadePos) {
159         if (shadePos != null) {
160             updateState(CHANNEL_SHADE_POSITION, shadePos.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED));
161             updateState(CHANNEL_SHADE_VANE, shadePos.getState(PRIMARY_ACTUATOR, VANE_COORDS));
162             updateState(CHANNEL_SHADE_SECONDARY_POSITION, shadePos.getState(SECONDARY_ACTUATOR, ZERO_IS_OPEN));
163         } else {
164             updateState(CHANNEL_SHADE_POSITION, UnDefType.UNDEF);
165             updateState(CHANNEL_SHADE_VANE, UnDefType.UNDEF);
166             updateState(CHANNEL_SHADE_SECONDARY_POSITION, UnDefType.UNDEF);
167         }
168     }
169
170     private void updateBatteryLevel(int batteryStatus) {
171         int mappedValue;
172         switch (batteryStatus) {
173             case 1: // Low
174                 mappedValue = 10;
175                 break;
176             case 2: // Medium
177                 mappedValue = 50;
178                 break;
179             case 3: // High
180             case 4: // Plugged in
181                 mappedValue = 100;
182                 break;
183             default: // No status available (0) or invalid
184                 updateState(CHANNEL_SHADE_LOW_BATTERY, UnDefType.UNDEF);
185                 updateState(CHANNEL_SHADE_BATTERY_LEVEL, UnDefType.UNDEF);
186                 return;
187         }
188         updateState(CHANNEL_SHADE_LOW_BATTERY, batteryStatus == 1 ? OnOffType.ON : OnOffType.OFF);
189         updateState(CHANNEL_SHADE_BATTERY_LEVEL, new DecimalType(mappedValue));
190     }
191
192     private void moveShade(ActuatorClass actuatorClass, CoordinateSystem coordSys, int newPercent) {
193         try {
194             HDPowerViewHubHandler bridge;
195             if ((bridge = getBridgeHandler()) == null) {
196                 throw new HubProcessingException("Missing bridge handler");
197             }
198             HDPowerViewWebTargets webTargets = bridge.getWebTargets();
199             if (webTargets == null) {
200                 throw new HubProcessingException("Web targets not initialized");
201             }
202             int shadeId = getShadeId();
203
204             switch (actuatorClass) {
205                 case PRIMARY_ACTUATOR:
206                     // write the new primary position
207                     webTargets.moveShade(shadeId, ShadePosition.create(coordSys, newPercent));
208                     break;
209                 case SECONDARY_ACTUATOR:
210                     // read the current primary position; default value 100%
211                     int primaryPercent = 100;
212                     Shade shade = webTargets.getShade(shadeId);
213                     if (shade != null) {
214                         ShadeData shadeData = shade.shade;
215                         if (shadeData != null) {
216                             ShadePosition shadePos = shadeData.positions;
217                             if (shadePos != null) {
218                                 State primaryState = shadePos.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
219                                 if (primaryState instanceof PercentType) {
220                                     primaryPercent = ((PercentType) primaryState).intValue();
221                                 }
222                             }
223                         }
224                     }
225                     // write the current primary position, plus the new secondary position
226                     webTargets.moveShade(shadeId,
227                             ShadePosition.create(ZERO_IS_CLOSED, primaryPercent, ZERO_IS_OPEN, newPercent));
228             }
229         } catch (HubProcessingException | NumberFormatException e) {
230             logger.warn("Unexpected error: {}", e.getMessage());
231             return;
232         } catch (HubMaintenanceException e) {
233             // exceptions are logged in HDPowerViewWebTargets
234             return;
235         }
236     }
237
238     private int getShadeId() throws NumberFormatException {
239         String str = getConfigAs(HDPowerViewShadeConfiguration.class).id;
240         if (str == null) {
241             throw new NumberFormatException("null input string");
242         }
243         return Integer.parseInt(str);
244     }
245
246     private void stopShade() {
247         try {
248             HDPowerViewHubHandler bridge;
249             if ((bridge = getBridgeHandler()) == null) {
250                 throw new HubProcessingException("Missing bridge handler");
251             }
252             HDPowerViewWebTargets webTargets = bridge.getWebTargets();
253             if (webTargets == null) {
254                 throw new HubProcessingException("Web targets not initialized");
255             }
256             int shadeId = getShadeId();
257             webTargets.stopShade(shadeId);
258             requestRefreshShadePosition();
259         } catch (HubProcessingException | NumberFormatException e) {
260             logger.warn("Unexpected error: {}", e.getMessage());
261             return;
262         } catch (HubMaintenanceException e) {
263             // exceptions are logged in HDPowerViewWebTargets
264             return;
265         }
266     }
267
268     /**
269      * Request that the shade shall undergo a 'hard' refresh for querying its current position
270      */
271     protected synchronized void requestRefreshShadePosition() {
272         if (refreshPositionFuture == null) {
273             refreshPositionFuture = scheduler.schedule(this::doRefreshShadePosition, REFRESH_DELAY_SEC,
274                     TimeUnit.SECONDS);
275         }
276     }
277
278     /**
279      * Request that the shade shall undergo a 'hard' refresh for querying its battery level state
280      */
281     protected synchronized void requestRefreshShadeBatteryLevel() {
282         if (refreshBatteryLevelFuture == null) {
283             refreshBatteryLevelFuture = scheduler.schedule(this::doRefreshShadeBatteryLevel, REFRESH_DELAY_SEC,
284                     TimeUnit.SECONDS);
285         }
286     }
287
288     private void doRefreshShadePosition() {
289         this.doRefreshShade(RefreshKind.POSITION);
290         refreshPositionFuture = null;
291     }
292
293     private void doRefreshShadeBatteryLevel() {
294         this.doRefreshShade(RefreshKind.BATTERY_LEVEL);
295         refreshBatteryLevelFuture = null;
296     }
297
298     private void doRefreshShade(RefreshKind kind) {
299         try {
300             HDPowerViewHubHandler bridge;
301             if ((bridge = getBridgeHandler()) == null) {
302                 throw new HubProcessingException("Missing bridge handler");
303             }
304             HDPowerViewWebTargets webTargets = bridge.getWebTargets();
305             if (webTargets == null) {
306                 throw new HubProcessingException("Web targets not initialized");
307             }
308             int shadeId = getShadeId();
309             Shade shade;
310             switch (kind) {
311                 case POSITION:
312                     shade = webTargets.refreshShadePosition(shadeId);
313                     break;
314                 case BATTERY_LEVEL:
315                     shade = webTargets.refreshShadeBatteryLevel(shadeId);
316                     break;
317                 default:
318                     throw new NotSupportedException("Unsupported refresh kind " + kind.toString());
319             }
320             if (shade != null) {
321                 ShadeData shadeData = shade.shade;
322                 if (shadeData != null) {
323                     if (Boolean.TRUE.equals(shadeData.timedOut)) {
324                         logger.warn("Shade {} wireless refresh time out", shadeId);
325                     }
326                 }
327             }
328         } catch (HubProcessingException | NumberFormatException e) {
329             logger.warn("Unexpected error: {}", e.getMessage());
330         } catch (HubMaintenanceException e) {
331             // exceptions are logged in HDPowerViewWebTargets
332         }
333     }
334 }