]> git.basschouten.com Git - openhab-addons.git/blob
70bf65bdd75ed05d52a67d9d9553b12f453374b4
[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.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;
50
51 /**
52  * Handles commands for an HD PowerView Shade
53  *
54  * @author Andy Lintner - Initial contribution
55  * @author Andrew Fiddian-Green - Added support for secondary rail positions
56  */
57 @NonNullByDefault
58 public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
59
60     private final Logger logger = LoggerFactory.getLogger(HDPowerViewShadeHandler.class);
61
62     private static final int REFRESH_DELAY_SEC = 10;
63     private @Nullable ScheduledFuture<?> refreshFuture = null;
64
65     public HDPowerViewShadeHandler(Thing thing) {
66         super(thing);
67     }
68
69     @Override
70     public void initialize() {
71         try {
72             getShadeId();
73         } catch (NumberFormatException e) {
74             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
75                     "Configuration 'id' not a valid integer");
76             return;
77         }
78         if (getBridgeHandler() == null) {
79             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Hub not configured");
80             return;
81         }
82         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
83     }
84
85     @Override
86     public void handleCommand(ChannelUID channelUID, Command command) {
87         if (RefreshType.REFRESH.equals(command)) {
88             requestRefreshShade();
89             return;
90         }
91
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)) {
100                         stopShade();
101                     } else {
102                         logger.warn("Unexpected StopMoveType command");
103                     }
104                 }
105                 break;
106
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);
112                 }
113                 break;
114
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)) {
122                         stopShade();
123                     } else {
124                         logger.warn("Unexpected StopMoveType command");
125                     }
126                 }
127                 break;
128         }
129     }
130
131     /**
132      * Update the state of the channels based on the ShadeData provided
133      *
134      * @param shadeData the ShadeData to be used; may be null
135      */
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));
143         } else {
144             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
145         }
146     }
147
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));
153         } else {
154             updateState(CHANNEL_SHADE_POSITION, UnDefType.UNDEF);
155             updateState(CHANNEL_SHADE_VANE, UnDefType.UNDEF);
156             updateState(CHANNEL_SHADE_SECONDARY_POSITION, UnDefType.UNDEF);
157         }
158     }
159
160     private void updateBatteryLevel(int batteryStatus) {
161         int mappedValue;
162         switch (batteryStatus) {
163             case 1: // Low
164                 mappedValue = 10;
165                 break;
166             case 2: // Medium
167                 mappedValue = 50;
168                 break;
169             case 3: // High
170             case 4: // Plugged in
171                 mappedValue = 100;
172                 break;
173             default: // No status available (0) or invalid
174                 return;
175         }
176         updateState(CHANNEL_SHADE_LOW_BATTERY, batteryStatus == 1 ? OnOffType.ON : OnOffType.OFF);
177         updateState(CHANNEL_SHADE_BATTERY_LEVEL, new DecimalType(mappedValue));
178     }
179
180     private void moveShade(ActuatorClass actuatorClass, CoordinateSystem coordSys, int newPercent) {
181         try {
182             HDPowerViewHubHandler bridge;
183             if ((bridge = getBridgeHandler()) == null) {
184                 throw new HubProcessingException("Missing bridge handler");
185             }
186             HDPowerViewWebTargets webTargets = bridge.getWebTargets();
187             if (webTargets == null) {
188                 throw new HubProcessingException("Web targets not initialized");
189             }
190             int shadeId = getShadeId();
191
192             switch (actuatorClass) {
193                 case PRIMARY_ACTUATOR:
194                     // write the new primary position
195                     webTargets.moveShade(shadeId, ShadePosition.create(coordSys, newPercent));
196                     break;
197                 case SECONDARY_ACTUATOR:
198                     // read the current primary position; default value 100%
199                     int primaryPercent = 100;
200                     Shade shade = webTargets.getShade(shadeId);
201                     if (shade != null) {
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();
209                                 }
210                             }
211                         }
212                     }
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));
216             }
217         } catch (HubProcessingException | NumberFormatException e) {
218             logger.warn("Unexpected error: {}", e.getMessage());
219             return;
220         } catch (HubMaintenanceException e) {
221             // exceptions are logged in HDPowerViewWebTargets
222             return;
223         }
224     }
225
226     private int getShadeId() throws NumberFormatException {
227         String str = getConfigAs(HDPowerViewShadeConfiguration.class).id;
228         if (str == null) {
229             throw new NumberFormatException("null input string");
230         }
231         return Integer.parseInt(str);
232     }
233
234     private void stopShade() {
235         try {
236             HDPowerViewHubHandler bridge;
237             if ((bridge = getBridgeHandler()) == null) {
238                 throw new HubProcessingException("Missing bridge handler");
239             }
240             HDPowerViewWebTargets webTargets = bridge.getWebTargets();
241             if (webTargets == null) {
242                 throw new HubProcessingException("Web targets not initialized");
243             }
244             int shadeId = getShadeId();
245             webTargets.stopShade(shadeId);
246             requestRefreshShade();
247         } catch (HubProcessingException | NumberFormatException e) {
248             logger.warn("Unexpected error: {}", e.getMessage());
249             return;
250         } catch (HubMaintenanceException e) {
251             // exceptions are logged in HDPowerViewWebTargets
252             return;
253         }
254     }
255
256     /**
257      * Request that the shade shall undergo a 'hard' refresh
258      */
259     protected synchronized void requestRefreshShade() {
260         if (refreshFuture == null) {
261             refreshFuture = scheduler.schedule(this::doRefreshShade, REFRESH_DELAY_SEC, TimeUnit.SECONDS);
262         }
263     }
264
265     private void doRefreshShade() {
266         try {
267             HDPowerViewHubHandler bridge;
268             if ((bridge = getBridgeHandler()) == null) {
269                 throw new HubProcessingException("Missing bridge handler");
270             }
271             HDPowerViewWebTargets webTargets = bridge.getWebTargets();
272             if (webTargets == null) {
273                 throw new HubProcessingException("Web targets not initialized");
274             }
275             int shadeId = getShadeId();
276             Shade shade = webTargets.refreshShade(shadeId);
277             if (shade != null) {
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);
282                     }
283                 }
284             }
285         } catch (HubProcessingException | NumberFormatException e) {
286             logger.warn("Unexpected error: {}", e.getMessage());
287         } catch (HubMaintenanceException e) {
288             // exceptions are logged in HDPowerViewWebTargets
289         }
290         refreshFuture = null;
291     }
292 }