]> git.basschouten.com Git - openhab-addons.git/blob
a759c19e9259fb4ee28f2098c8dd8aa026112c47
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.loxone.internal.controls;
14
15 import static org.openhab.binding.loxone.internal.LxBindingConstants.*;
16
17 import java.io.IOException;
18 import java.util.HashSet;
19 import java.util.Set;
20
21 import org.openhab.binding.loxone.internal.types.LxState;
22 import org.openhab.binding.loxone.internal.types.LxUuid;
23 import org.openhab.core.library.types.OnOffType;
24 import org.openhab.core.library.types.PercentType;
25 import org.openhab.core.library.types.StopMoveType;
26 import org.openhab.core.library.types.UpDownType;
27 import org.openhab.core.thing.type.ChannelTypeUID;
28 import org.openhab.core.types.Command;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 /**
33  * A jalousie type of control on Loxone Miniserver.
34  * <p>
35  * According to Loxone API documentation, a jalousie control covers:
36  * <ul>
37  * <li>Blinds</li>
38  * <li>Automatic blinds</li>
39  * <li>Automatic blinds integrated</li>
40  * </ul>
41  * <p>
42  * Jalousie control has three channels:
43  * <ul>
44  * <li>0 (default) - rollershutter position</li>
45  * <li>1 - shading command (always off switch, sending on triggers shading)</li>
46  * <li>2 - automatic shading (on/off switch)</li>
47  * </ul>
48  *
49  * @author Pawel Pieczul - initial contribution
50  *
51  */
52 class LxControlJalousie extends LxControl {
53
54     static class Factory extends LxControlInstance {
55         @Override
56         LxControl create(LxUuid uuid) {
57             return new LxControlJalousie(uuid);
58         }
59
60         @Override
61         String getType() {
62             return "jalousie";
63         }
64     }
65
66     /**
67      * Jalousie is moving up
68      */
69     private static final String STATE_UP = "up";
70     /**
71      * Jalousie is moving down
72      */
73     private static final String STATE_DOWN = "down";
74     /**
75      * The position of the Jalousie, a number from 0 to 1
76      * Jalousie upper position = 0
77      * Jalousie lower position = 1
78      */
79     private static final String STATE_POSITION = "position";
80     /**
81      * Only used by ones with Autopilot
82      */
83     private static final String STATE_AUTO_ACTIVE = "autoactive";
84     /**
85      * Command string used to set control's state to Full Down
86      */
87     private static final String CMD_FULL_DOWN = "FullDown";
88     /**
89      * Command string used to set control's state to Full Up
90      */
91     private static final String CMD_FULL_UP = "FullUp";
92     /**
93      * Command string used to stop rollershutter
94      */
95     private static final String CMD_STOP = "Stop";
96     /**
97      * Command to shade the jalousie
98      */
99     private static final String CMD_SHADE = "shade";
100     /**
101      * Command to enable automatic shading
102      */
103     private static final String CMD_AUTO = "auto";
104     /**
105      * Command to disable automatic shading
106      */
107     private static final String CMD_NO_AUTO = "NoAuto";
108
109     private final Logger logger = LoggerFactory.getLogger(LxControlJalousie.class);
110     private Double targetPosition;
111
112     private LxControlJalousie(LxUuid uuid) {
113         super(uuid);
114     }
115
116     @Override
117     public void initialize(LxControlConfig config) {
118         super.initialize(config);
119         Set<String> blindsTags = new HashSet<>(tags);
120         Set<String> switchTags = new HashSet<>(tags);
121         blindsTags.add("Blinds");
122         switchTags.add("Switchable");
123         addChannel("Rollershutter", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_ROLLERSHUTTER),
124                 defaultChannelLabel, "Rollershutter", blindsTags, this::handleOperateCommands, this::getOperateState);
125         addChannel("Switch", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_SWITCH),
126                 defaultChannelLabel + " / Shade", "Rollershutter shading", switchTags, this::handleShadeCommands,
127                 () -> OnOffType.OFF);
128         addChannel("Switch", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_SWITCH),
129                 defaultChannelLabel + " / Auto Shade", "Rollershutter automatic shading", switchTags,
130                 this::handleAutoShadeCommands, this::getAutoShadeState);
131     }
132
133     private void handleOperateCommands(Command command) throws IOException {
134         logger.debug("Command input {}", command);
135         if (command instanceof PercentType percentCommand) {
136             if (PercentType.ZERO.equals(command)) {
137                 sendAction(CMD_FULL_UP);
138             } else if (PercentType.HUNDRED.equals(command)) {
139                 sendAction(CMD_FULL_DOWN);
140             } else {
141                 moveToPosition(percentCommand.doubleValue() / 100);
142             }
143         } else if (command instanceof UpDownType upDownCommand) {
144             if (upDownCommand == UpDownType.UP) {
145                 sendAction(CMD_FULL_UP);
146             } else {
147                 sendAction(CMD_FULL_DOWN);
148             }
149         } else if (command instanceof StopMoveType stopMoveCommand) {
150             if (stopMoveCommand == StopMoveType.STOP) {
151                 sendAction(CMD_STOP);
152             }
153         }
154     }
155
156     private PercentType getOperateState() {
157         Double value = getStateDoubleValue(STATE_POSITION);
158         if (value != null && value >= 0 && value <= 1) {
159             // state UP or DOWN from Loxone indicates blinds are moving up or down
160             // state UP in openHAB means blinds are fully up (0%) and DOWN means fully down (100%)
161             // so we will update only position and not up or down states
162             // a basic calculation like (value * 100.0) will give significant errors for some fractions, e.g.
163             // 0.29 * 100 = 28.xxxxx which in turn if converted to integer will cause the position to take same value
164             // for two different positions and later jump skipping one value (29 in this example)
165             // for that reason we need to round the results of multiplication to avoid this skipping of percentages
166             return new PercentType((int) Math.round(value * 100.0));
167         }
168         return null;
169     }
170
171     private void handleShadeCommands(Command command) throws IOException {
172         if (command instanceof OnOffType onOffCommand) {
173             if (onOffCommand == OnOffType.ON) {
174                 sendAction(CMD_SHADE);
175             }
176         }
177     }
178
179     private void handleAutoShadeCommands(Command command) throws IOException {
180         if (command instanceof OnOffType onOffCommand) {
181             if (onOffCommand == OnOffType.ON) {
182                 sendAction(CMD_AUTO);
183             } else {
184                 sendAction(CMD_NO_AUTO);
185             }
186         }
187     }
188
189     private OnOffType getAutoShadeState() {
190         Double value = getStateDoubleValue(STATE_AUTO_ACTIVE);
191         if (value != null) {
192             return value == 1.0 ? OnOffType.ON : OnOffType.OFF;
193         }
194         return null;
195     }
196
197     /**
198      * Monitor jalousie position against desired target position and stop it if target position is reached.
199      */
200     @Override
201     public void onStateChange(LxState state) {
202         // check position changes
203         if (STATE_POSITION.equals(state.getName()) && targetPosition != null && targetPosition >= 0
204                 && targetPosition <= 1) {
205             // see in which direction jalousie is moving
206             Object value = state.getStateValue();
207             if (value instanceof Double) {
208                 Double currentPosition = (Double) value;
209                 Double upValue = getStateDoubleValue(STATE_UP);
210                 Double downValue = getStateDoubleValue(STATE_DOWN);
211                 if (upValue != null && downValue != null) {
212                     if (((upValue == 1) && (currentPosition <= targetPosition))
213                             || ((downValue == 1) && (currentPosition >= targetPosition))) {
214                         targetPosition = null;
215                         try {
216                             sendAction(CMD_STOP);
217                         } catch (IOException e) {
218                             logger.debug("Error stopping jalousie when meeting target position.");
219                         }
220                     }
221                 }
222             }
223         } else {
224             super.onStateChange(state);
225         }
226     }
227
228     /**
229      * Move the rollershutter (jalousie) to a desired position.
230      * <p>
231      * The jalousie will start moving in the desired direction based on the current position. It will stop moving once
232      * there is a state update event received with value above/below (depending on direction) or equal to the set
233      * position.
234      *
235      * @param position end position to move jalousie to, floating point number from 0..1 (0-fully closed to 1-fully
236      *            open)
237      * @throws IOException when something went wrong with communication
238      */
239     private void moveToPosition(Double position) throws IOException {
240         Double currentPosition = getStateDoubleValue(STATE_POSITION);
241         if (currentPosition != null && currentPosition >= 0 && currentPosition <= 1) {
242             if (currentPosition > position) {
243                 logger.debug("Moving jalousie up from {} to {}", currentPosition, position);
244                 targetPosition = position;
245                 sendAction(CMD_FULL_UP);
246             } else if (currentPosition < position) {
247                 logger.debug("Moving jalousie down from {} to {}", currentPosition, position);
248                 targetPosition = position;
249                 sendAction(CMD_FULL_DOWN);
250             }
251         }
252     }
253 }