]> git.basschouten.com Git - openhab-addons.git/blob
1c0d3fb3e9aa24e598ab265200516195f14830c9
[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 java.math.BigDecimal;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Map.Entry;
21 import java.util.Optional;
22 import java.util.concurrent.CompletableFuture;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.core.items.GenericItem;
27 import org.openhab.core.items.Item;
28 import org.openhab.core.library.types.OnOffType;
29 import org.openhab.core.library.types.OpenClosedType;
30 import org.openhab.core.library.types.StringType;
31 import org.openhab.core.types.State;
32 import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
33 import org.openhab.io.homekit.internal.HomekitCharacteristicType;
34 import org.openhab.io.homekit.internal.HomekitSettings;
35 import org.openhab.io.homekit.internal.HomekitTaggedItem;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 import io.github.hapjava.accessories.HomekitAccessory;
40 import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
41 import io.github.hapjava.services.Service;
42
43 /**
44  * Abstract class for Homekit Accessory implementations, this provides the
45  * accessory metadata using information from the underlying Item.
46  *
47  * @author Andy Lintner - Initial contribution
48  */
49 abstract class AbstractHomekitAccessoryImpl implements HomekitAccessory {
50     private final Logger logger = LoggerFactory.getLogger(AbstractHomekitAccessoryImpl.class);
51     private final List<HomekitTaggedItem> characteristics;
52     private final HomekitTaggedItem accessory;
53     private final HomekitAccessoryUpdater updater;
54     private final HomekitSettings settings;
55     private final List<Service> services;
56
57     public AbstractHomekitAccessoryImpl(HomekitTaggedItem accessory, List<HomekitTaggedItem> characteristics,
58             HomekitAccessoryUpdater updater, HomekitSettings settings) {
59         this.characteristics = characteristics;
60         this.accessory = accessory;
61         this.updater = updater;
62         this.services = new ArrayList<>();
63         this.settings = settings;
64     }
65
66     @NonNullByDefault
67     protected Optional<HomekitTaggedItem> getCharacteristic(HomekitCharacteristicType type) {
68         return characteristics.stream().filter(c -> c.getCharacteristicType() == type).findAny();
69     }
70
71     @Override
72     public int getId() {
73         return accessory.getId();
74     }
75
76     @Override
77     public CompletableFuture<String> getName() {
78         return CompletableFuture.completedFuture(accessory.getItem().getLabel());
79     }
80
81     @Override
82     public CompletableFuture<String> getManufacturer() {
83         return CompletableFuture.completedFuture("none");
84     }
85
86     @Override
87     public CompletableFuture<String> getModel() {
88         return CompletableFuture.completedFuture("none");
89     }
90
91     @Override
92     public CompletableFuture<String> getSerialNumber() {
93         return CompletableFuture.completedFuture(accessory.getItem().getName());
94     }
95
96     @Override
97     public CompletableFuture<String> getFirmwareRevision() {
98         return CompletableFuture.completedFuture("none");
99     }
100
101     @Override
102     public void identify() {
103         // We're not going to support this for now
104     }
105
106     public HomekitTaggedItem getRootAccessory() {
107         return accessory;
108     }
109
110     @Override
111     public Collection<Service> getServices() {
112         return this.services;
113     }
114
115     protected HomekitAccessoryUpdater getUpdater() {
116         return updater;
117     }
118
119     protected HomekitSettings getSettings() {
120         return settings;
121     }
122
123     @NonNullByDefault
124     protected void subscribe(HomekitCharacteristicType characteristicType,
125             HomekitCharacteristicChangeCallback callback) {
126         final Optional<HomekitTaggedItem> characteristic = getCharacteristic(characteristicType);
127         if (characteristic.isPresent()) {
128             getUpdater().subscribe((GenericItem) characteristic.get().getItem(), characteristicType.getTag(), callback);
129         } else {
130             logger.warn("Missing mandatory characteristic {}", characteristicType);
131         }
132     }
133
134     @NonNullByDefault
135     protected void unsubscribe(HomekitCharacteristicType characteristicType) {
136         final Optional<HomekitTaggedItem> characteristic = getCharacteristic(characteristicType);
137         if (characteristic.isPresent()) {
138             getUpdater().unsubscribe((GenericItem) characteristic.get().getItem(), characteristicType.getTag());
139         } else {
140             logger.warn("Missing mandatory characteristic {}", characteristicType);
141         }
142     }
143
144     protected @Nullable State getState(HomekitCharacteristicType characteristic) {
145         final Optional<HomekitTaggedItem> taggedItem = getCharacteristic(characteristic);
146         if (taggedItem.isPresent()) {
147             return taggedItem.get().getItem().getState();
148         }
149         logger.debug("State for characteristic {} at accessory {} cannot be retrieved.", characteristic,
150                 accessory.getName());
151         return null;
152     }
153
154     protected @Nullable <T extends State> T getStateAs(HomekitCharacteristicType characteristic, Class<T> type) {
155         final State state = getState(characteristic);
156         if (state != null) {
157             return state.as(type);
158         }
159         return null;
160     }
161
162     protected @Nullable Double getStateAsTemperature(HomekitCharacteristicType characteristic) {
163         return HomekitCharacteristicFactory.stateAsTemperature(getState(characteristic));
164     }
165
166     @NonNullByDefault
167     protected <T extends Item> Optional<T> getItem(HomekitCharacteristicType characteristic, Class<T> type) {
168         final Optional<HomekitTaggedItem> taggedItem = getCharacteristic(characteristic);
169         if (taggedItem.isPresent()) {
170             final Item item = taggedItem.get().getItem();
171             if (type.isInstance(item)) {
172                 return Optional.of((T) item);
173             } else {
174                 logger.warn("Unsupported item type for characteristic {} at accessory {}. Expected {}, got {}",
175                         characteristic, accessory.getItem().getName(), type, taggedItem.get().getItem().getClass());
176             }
177         } else {
178             logger.warn("Mandatory characteristic {} not found at accessory {}. ", characteristic,
179                     accessory.getItem().getName());
180
181         }
182         return Optional.empty();
183     }
184
185     /**
186      * return configuration attached to the root accessory, e.g. groupItem.
187      * Note: result will be casted to the type of the default value.
188      * The type for number is BigDecimal.
189      *
190      * @param key configuration key
191      * @param defaultValue default value
192      * @param <T> expected type
193      * @return configuration value
194      */
195     @NonNullByDefault
196     protected <T> T getAccessoryConfiguration(String key, T defaultValue) {
197         return accessory.getConfiguration(key, defaultValue);
198     }
199
200     /**
201      * return configuration attached to the root accessory, e.g. groupItem.
202      *
203      * @param key configuration key
204      * @param defaultValue default value
205      * @return configuration value
206      */
207     @NonNullByDefault
208     protected boolean getAccessoryConfigurationAsBoolean(String key, boolean defaultValue) {
209         return accessory.getConfigurationAsBoolean(key, defaultValue);
210     }
211
212     /**
213      * return configuration of the characteristic item, e.g. currentTemperature.
214      * Note: result will be casted to the type of the default value.
215      * The type for number is BigDecimal.
216      *
217      * @param characteristicType characteristic type
218      * @param key configuration key
219      * @param defaultValue default value
220      * @param <T> expected type
221      * @return configuration value
222      */
223     @NonNullByDefault
224     protected <T> T getAccessoryConfiguration(HomekitCharacteristicType characteristicType, String key,
225             T defaultValue) {
226         return getCharacteristic(characteristicType)
227                 .map(homekitTaggedItem -> homekitTaggedItem.getConfiguration(key, defaultValue)).orElse(defaultValue);
228     }
229
230     /**
231      * update mapping with values from item configuration.
232      * it checks for all keys from the mapping whether there is configuration at item with the same key and if yes,
233      * replace the value.
234      *
235      * @param characteristicType characteristicType to identify item
236      * @param map mapping to update
237      * @param customEnumList list to store custom state enumeration
238      */
239     @NonNullByDefault
240     protected <T> void updateMapping(HomekitCharacteristicType characteristicType, Map<T, String> map,
241             @Nullable List<T> customEnumList) {
242         getCharacteristic(characteristicType).ifPresent(c -> {
243             final Map<String, Object> configuration = c.getConfiguration();
244             if (configuration != null) {
245                 map.forEach((k, current_value) -> {
246                     final Object new_value = configuration.get(k.toString());
247                     if (new_value instanceof String) {
248                         map.put(k, (String) new_value);
249                         if (customEnumList != null) {
250                             customEnumList.add(k);
251                         }
252                     }
253                 });
254             }
255         });
256     }
257
258     @NonNullByDefault
259     protected <T> void updateMapping(HomekitCharacteristicType characteristicType, Map<T, String> map) {
260         updateMapping(characteristicType, map, null);
261     }
262
263     /**
264      * takes item state as value and retrieves the key for that value from mapping.
265      * e.g. used to map StringItem value to HomeKit Enum
266      *
267      * @param characteristicType characteristicType to identify item
268      * @param mapping mapping
269      * @param defaultValue default value if nothing found in mapping
270      * @param <T> type of the result derived from
271      * @return key for the value
272      */
273     @NonNullByDefault
274     protected <T> T getKeyFromMapping(HomekitCharacteristicType characteristicType, Map<T, String> mapping,
275             T defaultValue) {
276         final Optional<HomekitTaggedItem> c = getCharacteristic(characteristicType);
277         if (c.isPresent()) {
278             final State state = c.get().getItem().getState();
279             logger.trace("getKeyFromMapping: characteristic {}, state {}, mapping {}", characteristicType.getTag(),
280                     state, mapping);
281             if (state instanceof StringType) {
282                 return mapping.entrySet().stream().filter(entry -> state.toString().equalsIgnoreCase(entry.getValue()))
283                         .findAny().map(Entry::getKey).orElseGet(() -> {
284                             logger.warn(
285                                     "Wrong value {} for {} characteristic of the item {}. Expected one of following {}. Returning {}.",
286                                     state.toString(), characteristicType.getTag(), c.get().getName(), mapping.values(),
287                                     defaultValue);
288                             return defaultValue;
289                         });
290             }
291         }
292         return defaultValue;
293     }
294
295     @NonNullByDefault
296     protected void addCharacteristic(HomekitTaggedItem characteristic) {
297         characteristics.add(characteristic);
298     }
299
300     /**
301      * create boolean reader with ON state mapped to trueOnOffValue or trueOpenClosedValue depending of item type
302      *
303      * @param characteristicType characteristic id
304      * @param trueOnOffValue ON value for switch
305      * @param trueOpenClosedValue ON value for contact
306      * @return boolean read
307      * @throws IncompleteAccessoryException
308      */
309     @NonNullByDefault
310     protected BooleanItemReader createBooleanReader(HomekitCharacteristicType characteristicType,
311             OnOffType trueOnOffValue, OpenClosedType trueOpenClosedValue) throws IncompleteAccessoryException {
312         return new BooleanItemReader(
313                 getItem(characteristicType, GenericItem.class)
314                         .orElseThrow(() -> new IncompleteAccessoryException(characteristicType)),
315                 trueOnOffValue, trueOpenClosedValue);
316     }
317
318     /**
319      * create boolean reader for a number item with ON state mapped to the value of the
320      * item being above a given threshold
321      *
322      * @param characteristicType characteristic id
323      * @param trueThreshold threshold for true of number item
324      * @param invertThreshold result is true if item is less than threshold, instead of more
325      * @return boolean read
326      * @throws IncompleteAccessoryException
327      */
328     @NonNullByDefault
329     protected BooleanItemReader createBooleanReader(HomekitCharacteristicType characteristicType,
330             BigDecimal trueThreshold, boolean invertThreshold) throws IncompleteAccessoryException {
331         final HomekitTaggedItem taggedItem = getCharacteristic(characteristicType)
332                 .orElseThrow(() -> new IncompleteAccessoryException(characteristicType));
333         return new BooleanItemReader(taggedItem.getItem(), taggedItem.isInverted() ? OnOffType.OFF : OnOffType.ON,
334                 taggedItem.isInverted() ? OpenClosedType.CLOSED : OpenClosedType.OPEN, trueThreshold, invertThreshold);
335     }
336
337     /**
338      * create boolean reader with default ON/OFF mapping considering inverted flag
339      *
340      * @param characteristicType characteristic id
341      * @return boolean reader
342      * @throws IncompleteAccessoryException
343      */
344     @NonNullByDefault
345     protected BooleanItemReader createBooleanReader(HomekitCharacteristicType characteristicType)
346             throws IncompleteAccessoryException {
347         final HomekitTaggedItem taggedItem = getCharacteristic(characteristicType)
348                 .orElseThrow(() -> new IncompleteAccessoryException(characteristicType));
349         return new BooleanItemReader(taggedItem.getItem(), taggedItem.isInverted() ? OnOffType.OFF : OnOffType.ON,
350                 taggedItem.isInverted() ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
351     }
352 }