]> git.basschouten.com Git - openhab-addons.git/blob
4ce3602883061d91c871714d50c7f35a9c170789
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.io.homekit.internal.accessories;
14
15 import static org.openhab.io.homekit.internal.HomekitCharacteristicType.CURRENT_POSITION;
16 import static org.openhab.io.homekit.internal.HomekitCharacteristicType.POSITION_STATE;
17 import static org.openhab.io.homekit.internal.HomekitCharacteristicType.TARGET_POSITION;
18
19 import java.util.EnumMap;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Optional;
23 import java.util.concurrent.CompletableFuture;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.core.items.GroupItem;
28 import org.openhab.core.items.Item;
29 import org.openhab.core.library.items.DimmerItem;
30 import org.openhab.core.library.items.NumberItem;
31 import org.openhab.core.library.items.RollershutterItem;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.PercentType;
34 import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
35 import org.openhab.io.homekit.internal.HomekitCharacteristicType;
36 import org.openhab.io.homekit.internal.HomekitSettings;
37 import org.openhab.io.homekit.internal.HomekitTaggedItem;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
42 import io.github.hapjava.characteristics.impl.windowcovering.PositionStateEnum;
43
44 /**
45  * Common methods for Door, Window and WindowCovering.
46  * 
47  * @author Eugen Freiter - Initial contribution
48  */
49 @NonNullByDefault
50 abstract class AbstractHomekitPositionAccessoryImpl extends AbstractHomekitAccessoryImpl {
51     private final Logger logger = LoggerFactory.getLogger(AbstractHomekitPositionAccessoryImpl.class);
52     protected int closedPosition;
53     protected int openPosition;
54     private final Map<PositionStateEnum, String> positionStateMapping;
55
56     public AbstractHomekitPositionAccessoryImpl(HomekitTaggedItem taggedItem,
57             List<HomekitTaggedItem> mandatoryCharacteristics, HomekitAccessoryUpdater updater,
58             HomekitSettings settings) {
59         super(taggedItem, mandatoryCharacteristics, updater, settings);
60         final String invertedConfig = getAccessoryConfiguration(HomekitTaggedItem.INVERTED, "true");
61         final boolean inverted = invertedConfig.equalsIgnoreCase("yes") || invertedConfig.equalsIgnoreCase("true");
62         closedPosition = inverted ? 0 : 100;
63         openPosition = inverted ? 100 : 0;
64         positionStateMapping = new EnumMap<>(PositionStateEnum.class);
65         positionStateMapping.put(PositionStateEnum.DECREASING, "DECREASING");
66         positionStateMapping.put(PositionStateEnum.INCREASING, "INCREASING");
67         positionStateMapping.put(PositionStateEnum.STOPPED, "STOPPED");
68         updateMapping(POSITION_STATE, positionStateMapping);
69     }
70
71     public CompletableFuture<Integer> getCurrentPosition() {
72         return CompletableFuture.completedFuture(convertPositionState(CURRENT_POSITION, openPosition, closedPosition));
73     }
74
75     public CompletableFuture<PositionStateEnum> getPositionState() {
76         return CompletableFuture
77                 .completedFuture(getKeyFromMapping(POSITION_STATE, positionStateMapping, PositionStateEnum.STOPPED));
78     }
79
80     public CompletableFuture<Integer> getTargetPosition() {
81         return CompletableFuture.completedFuture(convertPositionState(TARGET_POSITION, openPosition, closedPosition));
82     }
83
84     public CompletableFuture<Void> setTargetPosition(int value) {
85         getCharacteristic(TARGET_POSITION).ifPresentOrElse(taggedItem -> {
86             final Item item = taggedItem.getItem();
87             final int targetPosition = convertPosition(value, openPosition);
88
89             if (item instanceof RollershutterItem) {
90                 ((RollershutterItem) item).send(new PercentType(targetPosition));
91             } else if (item instanceof DimmerItem) {
92                 ((DimmerItem) item).send(new PercentType(targetPosition));
93             } else if (item instanceof NumberItem) {
94                 ((NumberItem) item).send(new DecimalType(targetPosition));
95             } else if (item instanceof GroupItem && ((GroupItem) item).getBaseItem() instanceof RollershutterItem) {
96                 ((GroupItem) item).send(new PercentType(targetPosition));
97             } else if (item instanceof GroupItem && ((GroupItem) item).getBaseItem() instanceof DimmerItem) {
98                 ((GroupItem) item).send(new PercentType(targetPosition));
99             } else if (item instanceof GroupItem && ((GroupItem) item).getBaseItem() instanceof NumberItem) {
100                 ((GroupItem) item).send(new DecimalType(targetPosition));
101             } else {
102                 logger.warn(
103                         "Unsupported item type for characteristic {} at accessory {}. Expected Rollershutter, Dimmer or Number item, got {}",
104                         TARGET_POSITION, getName(), item.getClass());
105             }
106         }, () -> {
107             logger.warn("Mandatory characteristic {} not found at accessory {}. ", TARGET_POSITION, getName());
108         });
109         return CompletableFuture.completedFuture(null);
110     }
111
112     public void subscribeCurrentPosition(HomekitCharacteristicChangeCallback callback) {
113         subscribe(CURRENT_POSITION, callback);
114     }
115
116     public void subscribePositionState(HomekitCharacteristicChangeCallback callback) {
117         subscribe(POSITION_STATE, callback);
118     }
119
120     public void subscribeTargetPosition(HomekitCharacteristicChangeCallback callback) {
121         subscribe(TARGET_POSITION, callback);
122     }
123
124     public void unsubscribeCurrentPosition() {
125         unsubscribe(CURRENT_POSITION);
126     }
127
128     public void unsubscribePositionState() {
129         unsubscribe(POSITION_STATE);
130     }
131
132     public void unsubscribeTargetPosition() {
133         unsubscribe(TARGET_POSITION);
134     }
135
136     /**
137      * convert/invert position of door/window/blinds.
138      * openHAB Rollershutter is:
139      * - completely open if position is 0%,
140      * - completely closed if position is 100%.
141      * HomeKit mapping has inverted mapping
142      * From Specification: "For blinds/shades/awnings, a value of 0 indicates a position that permits the least light
143      * and a value
144      * of 100 indicates a position that allows most light.", i.e.
145      * HomeKit Blinds is
146      * - completely open if position is 100%,
147      * - completely closed if position is 0%.
148      *
149      * As openHAB rollershutter item is typically used for window covering, the binding has by default inverting
150      * mapping.
151      * One can override this default behaviour with inverted="false/no" flag. in this cases, openHAB item value will be
152      * sent to HomeKit with no changes.
153      *
154      * @param value source value
155      * @return target value
156      */
157     protected int convertPosition(int value, int openPosition) {
158         return Math.abs(openPosition - value);
159     }
160
161     protected int convertPositionState(HomekitCharacteristicType type, int openPosition, int closedPosition) {
162         @Nullable
163         DecimalType value = null;
164         final Optional<HomekitTaggedItem> taggedItem = getCharacteristic(type);
165         if (taggedItem.isPresent()) {
166             final Item item = taggedItem.get().getItem();
167             if ((item instanceof RollershutterItem) || ((item instanceof DimmerItem))) {
168                 value = item.getStateAs(PercentType.class);
169             } else {
170                 value = item.getStateAs(DecimalType.class);
171             }
172         }
173         return value != null ? convertPosition(value.intValue(), openPosition) : closedPosition;
174     }
175 }