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