2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.loxone.internal.controls;
15 import static org.openhab.binding.loxone.internal.LxBindingConstants.*;
17 import java.io.IOException;
18 import java.util.HashSet;
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;
33 * A jalousie type of control on Loxone Miniserver.
35 * According to Loxone API documentation, a jalousie control covers:
38 * <li>Automatic blinds</li>
39 * <li>Automatic blinds integrated</li>
42 * Jalousie control has three channels:
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>
49 * @author Pawel Pieczul - initial contribution
52 class LxControlJalousie extends LxControl {
54 static class Factory extends LxControlInstance {
56 LxControl create(LxUuid uuid) {
57 return new LxControlJalousie(uuid);
67 * Jalousie is moving up
69 private static final String STATE_UP = "up";
71 * Jalousie is moving down
73 private static final String STATE_DOWN = "down";
75 * The position of the Jalousie, a number from 0 to 1
76 * Jalousie upper position = 0
77 * Jalousie lower position = 1
79 private static final String STATE_POSITION = "position";
81 * Only used by ones with Autopilot
83 private static final String STATE_AUTO_ACTIVE = "autoactive";
85 * Command string used to set control's state to Full Down
87 private static final String CMD_FULL_DOWN = "FullDown";
89 * Command string used to set control's state to Full Up
91 private static final String CMD_FULL_UP = "FullUp";
93 * Command string used to stop rollershutter
95 private static final String CMD_STOP = "Stop";
97 * Command to shade the jalousie
99 private static final String CMD_SHADE = "shade";
101 * Command to enable automatic shading
103 private static final String CMD_AUTO = "auto";
105 * Command to disable automatic shading
107 private static final String CMD_NO_AUTO = "NoAuto";
109 private final Logger logger = LoggerFactory.getLogger(LxControlJalousie.class);
110 private Double targetPosition;
112 private LxControlJalousie(LxUuid uuid) {
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);
133 private void handleOperateCommands(Command command) throws IOException {
134 logger.debug("Command input {}", command);
135 if (command instanceof PercentType) {
136 if (PercentType.ZERO.equals(command)) {
137 sendAction(CMD_FULL_UP);
138 } else if (PercentType.HUNDRED.equals(command)) {
139 sendAction(CMD_FULL_DOWN);
141 moveToPosition(((PercentType) command).doubleValue() / 100);
143 } else if (command instanceof UpDownType) {
144 if ((UpDownType) command == UpDownType.UP) {
145 sendAction(CMD_FULL_UP);
147 sendAction(CMD_FULL_DOWN);
149 } else if (command instanceof StopMoveType) {
150 if ((StopMoveType) command == StopMoveType.STOP) {
151 sendAction(CMD_STOP);
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));
171 private void handleShadeCommands(Command command) throws IOException {
172 if (command instanceof OnOffType) {
173 if ((OnOffType) command == OnOffType.ON) {
174 sendAction(CMD_SHADE);
179 private void handleAutoShadeCommands(Command command) throws IOException {
180 if (command instanceof OnOffType) {
181 if ((OnOffType) command == OnOffType.ON) {
182 sendAction(CMD_AUTO);
184 sendAction(CMD_NO_AUTO);
189 private OnOffType getAutoShadeState() {
190 Double value = getStateDoubleValue(STATE_AUTO_ACTIVE);
192 return value == 1.0 ? OnOffType.ON : OnOffType.OFF;
198 * Monitor jalousie position against desired target position and stop it if target position is reached.
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;
216 sendAction(CMD_STOP);
217 } catch (IOException e) {
218 logger.debug("Error stopping jalousie when meeting target position.");
224 super.onStateChange(state);
229 * Move the rollershutter (jalousie) to a desired position.
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
235 * @param position end position to move jalousie to, floating point number from 0..1 (0-fully closed to 1-fully
237 * @throws IOException when something went wrong with communication
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);