2 * Copyright (c) 2010-2024 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.ACTIVE_STATUS;
16 import static org.openhab.io.homekit.internal.HomekitCharacteristicType.INUSE_STATUS;
17 import static org.openhab.io.homekit.internal.HomekitCharacteristicType.REMAINING_DURATION;
19 import java.util.HashMap;
20 import java.util.List;
22 import java.util.concurrent.CompletableFuture;
23 import java.util.concurrent.Executors;
24 import java.util.concurrent.ScheduledExecutorService;
25 import java.util.concurrent.ScheduledFuture;
26 import java.util.concurrent.TimeUnit;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.core.items.GenericItem;
30 import org.openhab.core.library.items.SwitchItem;
31 import org.openhab.core.library.types.DecimalType;
32 import org.openhab.core.library.types.OnOffType;
33 import org.openhab.core.types.RefreshType;
34 import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
35 import org.openhab.io.homekit.internal.HomekitCharacteristicType;
36 import org.openhab.io.homekit.internal.HomekitException;
37 import org.openhab.io.homekit.internal.HomekitSettings;
38 import org.openhab.io.homekit.internal.HomekitTaggedItem;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
42 import io.github.hapjava.accessories.HomekitAccessory;
43 import io.github.hapjava.accessories.ValveAccessory;
44 import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
45 import io.github.hapjava.characteristics.impl.common.ActiveEnum;
46 import io.github.hapjava.characteristics.impl.common.InUseEnum;
47 import io.github.hapjava.characteristics.impl.valve.RemainingDurationCharacteristic;
48 import io.github.hapjava.characteristics.impl.valve.ValveTypeEnum;
49 import io.github.hapjava.services.impl.ValveService;
53 * @author Tim Harper - Initial contribution
54 * @author Eugen Freiter - timer implementation
56 public class HomekitValveImpl extends AbstractHomekitAccessoryImpl implements ValveAccessory {
57 private final Logger logger = LoggerFactory.getLogger(HomekitValveImpl.class);
58 private static final String CONFIG_VALVE_TYPE = "ValveType";
59 private static final String CONFIG_VALVE_TYPE_DEPRECATED = "homekitValveType";
60 public static final String CONFIG_DEFAULT_DURATION = "homekitDefaultDuration";
61 private static final String CONFIG_TIMER = "homekitTimer";
63 private static final Map<String, ValveTypeEnum> CONFIG_VALVE_TYPE_MAPPING = new HashMap<>() {
65 put("GENERIC", ValveTypeEnum.GENERIC);
66 put("IRRIGATION", ValveTypeEnum.IRRIGATION);
67 put("SHOWER", ValveTypeEnum.SHOWER);
68 put("FAUCET", ValveTypeEnum.WATER_FAUCET);
71 private final BooleanItemReader inUseReader;
72 private final BooleanItemReader activeReader;
73 private final ScheduledExecutorService timerService = Executors.newSingleThreadScheduledExecutor();
74 private ScheduledFuture<?> valveTimer;
75 private final boolean homekitTimer;
76 private ValveTypeEnum valveType;
78 public HomekitValveImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
79 HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
80 super(taggedItem, mandatoryCharacteristics, updater, settings);
81 inUseReader = createBooleanReader(INUSE_STATUS);
82 activeReader = createBooleanReader(ACTIVE_STATUS);
83 homekitTimer = getAccessoryConfigurationAsBoolean(CONFIG_TIMER, false);
87 public void init() throws HomekitException {
89 ValveService service = new ValveService(this);
92 var remainingDurationCharacteristic = getCharacteristic(RemainingDurationCharacteristic.class);
94 if (homekitTimer && remainingDurationCharacteristic.isEmpty()) {
95 addRemainingDurationCharacteristic(getRootAccessory(), getUpdater(), service);
97 String valveTypeConfig = getAccessoryConfiguration(CONFIG_VALVE_TYPE, "GENERIC");
98 valveTypeConfig = getAccessoryConfiguration(CONFIG_VALVE_TYPE_DEPRECATED, valveTypeConfig);
99 var valveType = CONFIG_VALVE_TYPE_MAPPING.get(valveTypeConfig.toUpperCase());
100 this.valveType = valveType != null ? valveType : ValveTypeEnum.GENERIC;
103 private void addRemainingDurationCharacteristic(HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater,
104 ValveService service) {
105 logger.trace("addRemainingDurationCharacteristic for {}", taggedItem);
106 service.addOptionalCharacteristic(new RemainingDurationCharacteristic(() -> {
107 int remainingTime = 0;
108 ScheduledFuture<?> future = valveTimer;
109 if (future != null && !future.isDone()) {
110 remainingTime = Math.toIntExact(future.getDelay(TimeUnit.SECONDS));
112 return CompletableFuture.completedFuture(remainingTime);
113 }, HomekitCharacteristicFactory.getSubscriber(taggedItem, REMAINING_DURATION, updater),
114 HomekitCharacteristicFactory.getUnsubscriber(taggedItem, REMAINING_DURATION, updater)));
118 * return duration set by home app at corresponding OH items. if ot set, then return the default duration from
123 private int getDuration() {
125 final @Nullable DecimalType durationState = getStateAs(HomekitCharacteristicType.DURATION, DecimalType.class);
126 if (durationState != null) {
127 duration = durationState.intValue();
132 private void startTimer() {
133 int duration = getDuration();
134 logger.trace("start timer for duration {}", duration);
137 valveTimer = timerService.schedule(() -> {
138 logger.trace("valve timer is over. switching off the valve");
140 // let home app refresh the remaining duration, which is 0
141 ((GenericItem) getRootAccessory().getItem()).send(RefreshType.REFRESH);
142 }, duration, TimeUnit.SECONDS);
143 logger.trace("started valve timer for {} seconds.", duration);
145 logger.debug("valve timer not started as duration = 0");
149 private void stopTimer() {
150 ScheduledFuture<?> future = valveTimer;
151 if (future != null && !future.isDone()) {
157 public CompletableFuture<ActiveEnum> getValveActive() {
158 return CompletableFuture
159 .completedFuture(this.activeReader.getValue() ? ActiveEnum.ACTIVE : ActiveEnum.INACTIVE);
163 public CompletableFuture<Void> setValveActive(ActiveEnum state) {
164 getItem(ACTIVE_STATUS, SwitchItem.class).ifPresent(item -> {
165 item.send(OnOffType.from(state == ActiveEnum.ACTIVE));
167 if ((state == ActiveEnum.ACTIVE)) {
172 // let home app refresh the remaining duration
173 ((GenericItem) getRootAccessory().getItem()).send(RefreshType.REFRESH);
176 return CompletableFuture.completedFuture(null);
179 private void switchOffValve() {
180 getItem(ACTIVE_STATUS, SwitchItem.class).ifPresent(item -> item.send(OnOffType.OFF));
184 public void subscribeValveActive(HomekitCharacteristicChangeCallback callback) {
185 subscribe(ACTIVE_STATUS, callback);
189 public void unsubscribeValveActive() {
190 unsubscribe(ACTIVE_STATUS);
194 public CompletableFuture<InUseEnum> getValveInUse() {
195 return CompletableFuture.completedFuture(inUseReader.getValue() ? InUseEnum.IN_USE : InUseEnum.NOT_IN_USE);
199 public void subscribeValveInUse(HomekitCharacteristicChangeCallback callback) {
200 subscribe(INUSE_STATUS, callback);
204 public void unsubscribeValveInUse() {
205 unsubscribe(INUSE_STATUS);
209 public CompletableFuture<ValveTypeEnum> getValveType() {
210 return CompletableFuture.completedFuture(valveType);
214 public void subscribeValveType(HomekitCharacteristicChangeCallback callback) {
215 // nothing changes here
219 public void unsubscribeValveType() {
220 // nothing changes here
224 public boolean isLinkable(HomekitAccessory parentAccessory) {
225 // When part of an irrigation system, the valve type _must_ be irrigation.
226 if (parentAccessory instanceof HomekitIrrigationSystemImpl) {
227 valveType = ValveTypeEnum.IRRIGATION;