2 * Copyright (c) 2010-2022 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.io.homekit.internal.accessories;
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;
19 import java.util.EnumMap;
20 import java.util.List;
22 import java.util.Optional;
23 import java.util.concurrent.CompletableFuture;
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;
41 import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
42 import io.github.hapjava.characteristics.impl.windowcovering.PositionStateEnum;
45 * Common methods for Door, Window and WindowCovering.
47 * @author Eugen Freiter - Initial contribution
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;
56 public AbstractHomekitPositionAccessoryImpl(HomekitTaggedItem taggedItem,
57 List<HomekitTaggedItem> mandatoryCharacteristics, HomekitAccessoryUpdater updater,
58 HomekitSettings settings) {
59 super(taggedItem, mandatoryCharacteristics, updater, settings);
60 final boolean inverted = getAccessoryConfigurationAsBoolean(HomekitTaggedItem.INVERTED, true);
61 closedPosition = inverted ? 0 : 100;
62 openPosition = inverted ? 100 : 0;
63 positionStateMapping = new EnumMap<>(PositionStateEnum.class);
64 positionStateMapping.put(PositionStateEnum.DECREASING, "DECREASING");
65 positionStateMapping.put(PositionStateEnum.INCREASING, "INCREASING");
66 positionStateMapping.put(PositionStateEnum.STOPPED, "STOPPED");
67 updateMapping(POSITION_STATE, positionStateMapping);
70 public CompletableFuture<Integer> getCurrentPosition() {
71 return CompletableFuture.completedFuture(convertPositionState(CURRENT_POSITION, openPosition, closedPosition));
74 public CompletableFuture<PositionStateEnum> getPositionState() {
75 return CompletableFuture
76 .completedFuture(getKeyFromMapping(POSITION_STATE, positionStateMapping, PositionStateEnum.STOPPED));
79 public CompletableFuture<Integer> getTargetPosition() {
80 return CompletableFuture.completedFuture(convertPositionState(TARGET_POSITION, openPosition, closedPosition));
83 public CompletableFuture<Void> setTargetPosition(int value) {
84 getCharacteristic(TARGET_POSITION).ifPresentOrElse(taggedItem -> {
85 final Item item = taggedItem.getItem();
86 final int targetPosition = convertPosition(value, openPosition);
88 if (item instanceof RollershutterItem) {
89 ((RollershutterItem) item).send(new PercentType(targetPosition));
90 } else if (item instanceof DimmerItem) {
91 ((DimmerItem) item).send(new PercentType(targetPosition));
92 } else if (item instanceof NumberItem) {
93 ((NumberItem) item).send(new DecimalType(targetPosition));
94 } else if (item instanceof GroupItem && ((GroupItem) item).getBaseItem() instanceof RollershutterItem) {
95 ((GroupItem) item).send(new PercentType(targetPosition));
96 } else if (item instanceof GroupItem && ((GroupItem) item).getBaseItem() instanceof DimmerItem) {
97 ((GroupItem) item).send(new PercentType(targetPosition));
98 } else if (item instanceof GroupItem && ((GroupItem) item).getBaseItem() instanceof NumberItem) {
99 ((GroupItem) item).send(new DecimalType(targetPosition));
102 "Unsupported item type for characteristic {} at accessory {}. Expected Rollershutter, Dimmer or Number item, got {}",
103 TARGET_POSITION, getName(), item.getClass());
106 logger.warn("Mandatory characteristic {} not found at accessory {}. ", TARGET_POSITION, getName());
108 return CompletableFuture.completedFuture(null);
111 public void subscribeCurrentPosition(HomekitCharacteristicChangeCallback callback) {
112 subscribe(CURRENT_POSITION, callback);
115 public void subscribePositionState(HomekitCharacteristicChangeCallback callback) {
116 subscribe(POSITION_STATE, callback);
119 public void subscribeTargetPosition(HomekitCharacteristicChangeCallback callback) {
120 subscribe(TARGET_POSITION, callback);
123 public void unsubscribeCurrentPosition() {
124 unsubscribe(CURRENT_POSITION);
127 public void unsubscribePositionState() {
128 unsubscribe(POSITION_STATE);
131 public void unsubscribeTargetPosition() {
132 unsubscribe(TARGET_POSITION);
136 * convert/invert position of door/window/blinds.
137 * openHAB Rollershutter is:
138 * - completely open if position is 0%,
139 * - completely closed if position is 100%.
140 * HomeKit mapping has inverted mapping
141 * From Specification: "For blinds/shades/awnings, a value of 0 indicates a position that permits the least light
143 * of 100 indicates a position that allows most light.", i.e.
145 * - completely open if position is 100%,
146 * - completely closed if position is 0%.
148 * As openHAB rollershutter item is typically used for window covering, the binding has by default inverting
150 * One can override this default behaviour with inverted="false/no" flag. in this cases, openHAB item value will be
151 * sent to HomeKit with no changes.
153 * @param value source value
154 * @return target value
156 protected int convertPosition(int value, int openPosition) {
157 return Math.abs(openPosition - value);
160 protected int convertPositionState(HomekitCharacteristicType type, int openPosition, int closedPosition) {
162 DecimalType value = null;
163 final Optional<HomekitTaggedItem> taggedItem = getCharacteristic(type);
164 if (taggedItem.isPresent()) {
165 final Item item = taggedItem.get().getItem();
166 final Item baseItem = taggedItem.get().getBaseItem();
167 if (baseItem instanceof RollershutterItem || baseItem instanceof DimmerItem) {
168 value = item.getStateAs(PercentType.class);
170 value = item.getStateAs(DecimalType.class);
173 return value != null ? convertPosition(value.intValue(), openPosition) : closedPosition;