]> git.basschouten.com Git - openhab-addons.git/blob
aede260fa6a5055da144b3517fe7411ecdb04b66
[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.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;
41
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;
50
51 /**
52  *
53  * @author Tim Harper - Initial contribution
54  * @author Eugen Freiter - timer implementation
55  */
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";
62
63     private static final Map<String, ValveTypeEnum> CONFIG_VALVE_TYPE_MAPPING = new HashMap<>() {
64         {
65             put("GENERIC", ValveTypeEnum.GENERIC);
66             put("IRRIGATION", ValveTypeEnum.IRRIGATION);
67             put("SHOWER", ValveTypeEnum.SHOWER);
68             put("FAUCET", ValveTypeEnum.WATER_FAUCET);
69         }
70     };
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;
77
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);
84     }
85
86     @Override
87     public void init() throws HomekitException {
88         super.init();
89         ValveService service = new ValveService(this);
90         addService(service);
91
92         var remainingDurationCharacteristic = getCharacteristic(RemainingDurationCharacteristic.class);
93
94         if (homekitTimer && remainingDurationCharacteristic.isEmpty()) {
95             addRemainingDurationCharacteristic(getRootAccessory(), getUpdater(), service);
96         }
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;
101     }
102
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));
111             }
112             return CompletableFuture.completedFuture(remainingTime);
113         }, HomekitCharacteristicFactory.getSubscriber(taggedItem, REMAINING_DURATION, updater),
114                 HomekitCharacteristicFactory.getUnsubscriber(taggedItem, REMAINING_DURATION, updater)));
115     }
116
117     /**
118      * return duration set by home app at corresponding OH items. if ot set, then return the default duration from
119      * configuration.
120      * 
121      * @return duraion
122      */
123     private int getDuration() {
124         int duration = 0;
125         final @Nullable DecimalType durationState = getStateAs(HomekitCharacteristicType.DURATION, DecimalType.class);
126         if (durationState != null) {
127             duration = durationState.intValue();
128         }
129         return duration;
130     }
131
132     private void startTimer() {
133         int duration = getDuration();
134         logger.trace("start timer for duration {}", duration);
135         if (duration > 0) {
136             stopTimer();
137             valveTimer = timerService.schedule(() -> {
138                 logger.trace("valve timer is over. switching off the valve");
139                 switchOffValve();
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);
144         } else {
145             logger.debug("valve timer not started as duration = 0");
146         }
147     }
148
149     private void stopTimer() {
150         ScheduledFuture<?> future = valveTimer;
151         if (future != null && !future.isDone()) {
152             future.cancel(true);
153         }
154     }
155
156     @Override
157     public CompletableFuture<ActiveEnum> getValveActive() {
158         return CompletableFuture
159                 .completedFuture(this.activeReader.getValue() ? ActiveEnum.ACTIVE : ActiveEnum.INACTIVE);
160     }
161
162     @Override
163     public CompletableFuture<Void> setValveActive(ActiveEnum state) {
164         getItem(ACTIVE_STATUS, SwitchItem.class).ifPresent(item -> {
165             item.send(OnOffType.from(state == ActiveEnum.ACTIVE));
166             if (homekitTimer) {
167                 if ((state == ActiveEnum.ACTIVE)) {
168                     startTimer();
169                 } else {
170                     stopTimer();
171                 }
172                 // let home app refresh the remaining duration
173                 ((GenericItem) getRootAccessory().getItem()).send(RefreshType.REFRESH);
174             }
175         });
176         return CompletableFuture.completedFuture(null);
177     }
178
179     private void switchOffValve() {
180         getItem(ACTIVE_STATUS, SwitchItem.class).ifPresent(item -> item.send(OnOffType.OFF));
181     }
182
183     @Override
184     public void subscribeValveActive(HomekitCharacteristicChangeCallback callback) {
185         subscribe(ACTIVE_STATUS, callback);
186     }
187
188     @Override
189     public void unsubscribeValveActive() {
190         unsubscribe(ACTIVE_STATUS);
191     }
192
193     @Override
194     public CompletableFuture<InUseEnum> getValveInUse() {
195         return CompletableFuture.completedFuture(inUseReader.getValue() ? InUseEnum.IN_USE : InUseEnum.NOT_IN_USE);
196     }
197
198     @Override
199     public void subscribeValveInUse(HomekitCharacteristicChangeCallback callback) {
200         subscribe(INUSE_STATUS, callback);
201     }
202
203     @Override
204     public void unsubscribeValveInUse() {
205         unsubscribe(INUSE_STATUS);
206     }
207
208     @Override
209     public CompletableFuture<ValveTypeEnum> getValveType() {
210         return CompletableFuture.completedFuture(valveType);
211     }
212
213     @Override
214     public void subscribeValveType(HomekitCharacteristicChangeCallback callback) {
215         // nothing changes here
216     }
217
218     @Override
219     public void unsubscribeValveType() {
220         // nothing changes here
221     }
222
223     @Override
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;
228             return true;
229         }
230         return false;
231     }
232 }