]> git.basschouten.com Git - openhab-addons.git/blob
f3a0141a188713d2d18e25a08d2b423fda37893e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.ACTIVE_STATUS;
16 import static org.openhab.io.homekit.internal.HomekitCharacteristicType.INUSE_STATUS;
17 import static org.openhab.io.homekit.internal.HomekitCharacteristicType.REMAINING_DURATION;
18
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
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;
27
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.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.accessories.HomekitAccessory;
42 import io.github.hapjava.accessories.ValveAccessory;
43 import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
44 import io.github.hapjava.characteristics.impl.common.ActiveEnum;
45 import io.github.hapjava.characteristics.impl.common.InUseEnum;
46 import io.github.hapjava.characteristics.impl.valve.RemainingDurationCharacteristic;
47 import io.github.hapjava.characteristics.impl.valve.ValveTypeEnum;
48 import io.github.hapjava.services.impl.ValveService;
49
50 /**
51  *
52  * @author Tim Harper - Initial contribution
53  * @author Eugen Freiter - timer implementation
54  */
55 public class HomekitValveImpl extends AbstractHomekitAccessoryImpl implements ValveAccessory {
56     private final Logger logger = LoggerFactory.getLogger(HomekitValveImpl.class);
57     private static final String CONFIG_VALVE_TYPE = "ValveType";
58     private static final String CONFIG_VALVE_TYPE_DEPRECATED = "homekitValveType";
59     public static final String CONFIG_DEFAULT_DURATION = "homekitDefaultDuration";
60     private static final String CONFIG_TIMER = "homekitTimer";
61
62     private static final Map<String, ValveTypeEnum> CONFIG_VALVE_TYPE_MAPPING = new HashMap<>() {
63         {
64             put("GENERIC", ValveTypeEnum.GENERIC);
65             put("IRRIGATION", ValveTypeEnum.IRRIGATION);
66             put("SHOWER", ValveTypeEnum.SHOWER);
67             put("FAUCET", ValveTypeEnum.WATER_FAUCET);
68         }
69     };
70     private final BooleanItemReader inUseReader;
71     private final BooleanItemReader activeReader;
72     private final ScheduledExecutorService timerService = Executors.newSingleThreadScheduledExecutor();
73     private ScheduledFuture<?> valveTimer;
74     private final boolean homekitTimer;
75     private ValveTypeEnum valveType;
76
77     public HomekitValveImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
78             HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
79         super(taggedItem, mandatoryCharacteristics, updater, settings);
80         inUseReader = createBooleanReader(INUSE_STATUS);
81         activeReader = createBooleanReader(ACTIVE_STATUS);
82         ValveService service = new ValveService(this);
83         getServices().add(service);
84         homekitTimer = getAccessoryConfigurationAsBoolean(CONFIG_TIMER, false);
85         if (homekitTimer) {
86             addRemainingDurationCharacteristic(taggedItem, updater, service);
87         }
88         String valveTypeConfig = getAccessoryConfiguration(CONFIG_VALVE_TYPE, "GENERIC");
89         valveTypeConfig = getAccessoryConfiguration(CONFIG_VALVE_TYPE_DEPRECATED, valveTypeConfig);
90         var valveType = CONFIG_VALVE_TYPE_MAPPING.get(valveTypeConfig.toUpperCase());
91         this.valveType = valveType != null ? valveType : ValveTypeEnum.GENERIC;
92     }
93
94     private void addRemainingDurationCharacteristic(HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater,
95             ValveService service) {
96         logger.trace("addRemainingDurationCharacteristic for {}", taggedItem);
97         service.addOptionalCharacteristic(new RemainingDurationCharacteristic(() -> {
98             int remainingTime = 0;
99             ScheduledFuture<?> future = valveTimer;
100             if (future != null && !future.isDone()) {
101                 remainingTime = Math.toIntExact(future.getDelay(TimeUnit.SECONDS));
102             }
103             return CompletableFuture.completedFuture(remainingTime);
104         }, HomekitCharacteristicFactory.getSubscriber(taggedItem, REMAINING_DURATION, updater),
105                 HomekitCharacteristicFactory.getUnsubscriber(taggedItem, REMAINING_DURATION, updater)));
106     }
107
108     /**
109      * return duration set by home app at corresponding OH items. if ot set, then return the default duration from
110      * configuration.
111      * 
112      * @return duraion
113      */
114     private int getDuration() {
115         int duration = 0;
116         final @Nullable DecimalType durationState = getStateAs(HomekitCharacteristicType.DURATION, DecimalType.class);
117         if (durationState != null) {
118             duration = durationState.intValue();
119         }
120         return duration;
121     }
122
123     private void startTimer() {
124         int duration = getDuration();
125         logger.trace("start timer for duration {}", duration);
126         if (duration > 0) {
127             stopTimer();
128             valveTimer = timerService.schedule(() -> {
129                 logger.trace("valve timer is over. switching off the valve");
130                 switchOffValve();
131                 // let home app refresh the remaining duration, which is 0
132                 ((GenericItem) getRootAccessory().getItem()).send(RefreshType.REFRESH);
133             }, duration, TimeUnit.SECONDS);
134             logger.trace("started valve timer for {} seconds.", duration);
135         } else {
136             logger.debug("valve timer not started as duration = 0");
137         }
138     }
139
140     private void stopTimer() {
141         ScheduledFuture<?> future = valveTimer;
142         if (future != null && !future.isDone()) {
143             future.cancel(true);
144         }
145     }
146
147     @Override
148     public CompletableFuture<ActiveEnum> getValveActive() {
149         return CompletableFuture
150                 .completedFuture(this.activeReader.getValue() ? ActiveEnum.ACTIVE : ActiveEnum.INACTIVE);
151     }
152
153     @Override
154     public CompletableFuture<Void> setValveActive(ActiveEnum state) {
155         getItem(ACTIVE_STATUS, SwitchItem.class).ifPresent(item -> {
156             item.send(OnOffType.from(state == ActiveEnum.ACTIVE));
157             if (homekitTimer) {
158                 if ((state == ActiveEnum.ACTIVE)) {
159                     startTimer();
160                 } else {
161                     stopTimer();
162                 }
163                 // let home app refresh the remaining duration
164                 ((GenericItem) getRootAccessory().getItem()).send(RefreshType.REFRESH);
165             }
166         });
167         return CompletableFuture.completedFuture(null);
168     }
169
170     private void switchOffValve() {
171         getItem(ACTIVE_STATUS, SwitchItem.class).ifPresent(item -> item.send(OnOffType.OFF));
172     }
173
174     @Override
175     public void subscribeValveActive(HomekitCharacteristicChangeCallback callback) {
176         subscribe(ACTIVE_STATUS, callback);
177     }
178
179     @Override
180     public void unsubscribeValveActive() {
181         unsubscribe(ACTIVE_STATUS);
182     }
183
184     @Override
185     public CompletableFuture<InUseEnum> getValveInUse() {
186         return CompletableFuture.completedFuture(inUseReader.getValue() ? InUseEnum.IN_USE : InUseEnum.NOT_IN_USE);
187     }
188
189     @Override
190     public void subscribeValveInUse(HomekitCharacteristicChangeCallback callback) {
191         subscribe(INUSE_STATUS, callback);
192     }
193
194     @Override
195     public void unsubscribeValveInUse() {
196         unsubscribe(INUSE_STATUS);
197     }
198
199     @Override
200     public CompletableFuture<ValveTypeEnum> getValveType() {
201         return CompletableFuture.completedFuture(valveType);
202     }
203
204     @Override
205     public void subscribeValveType(HomekitCharacteristicChangeCallback callback) {
206         // nothing changes here
207     }
208
209     @Override
210     public void unsubscribeValveType() {
211         // nothing changes here
212     }
213
214     @Override
215     public boolean isLinkable(HomekitAccessory parentAccessory) {
216         // When part of an irrigation system, the valve type _must_ be irrigation.
217         if (parentAccessory instanceof HomekitIrrigationSystemImpl) {
218             valveType = ValveTypeEnum.IRRIGATION;
219             return true;
220         }
221         return false;
222     }
223 }