]> git.basschouten.com Git - openhab-addons.git/blob
6204524401a2b6535c5cdc9dd02070d9f4f08ab4
[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 State getState(HomekitCharacteristicType characteristic) {
144         final Optional<HomekitTaggedItem> taggedItem = getCharacteristic(characteristic);
145         if (taggedItem.isPresent()) {
146             return taggedItem.get().getItem().getState();
147         }
148         logger.debug("State for characteristic {} at accessory {} cannot be retrieved.", characteristic,
149                 accessory.getName());
150         return null;
151     }
152
153     protected @Nullable <T extends State> T getStateAs(HomekitCharacteristicType characteristic, Class<T> type) {
154         final State state = getState(characteristic);
155         if (state != null) {
156             return state.as(type);
157         }
158         return null;
159     }
160
161     protected @Nullable Double getStateAsTemperature(HomekitCharacteristicType characteristic) {
162         return HomekitCharacteristicFactory.stateAsTemperature(getState(characteristic));
163     }
164
165     @NonNullByDefault
166     protected <T extends Item> Optional<T> getItem(HomekitCharacteristicType characteristic, Class<T> type) {
167         final Optional<HomekitTaggedItem> taggedItem = getCharacteristic(characteristic);
168         if (taggedItem.isPresent()) {
169             final Item item = taggedItem.get().getItem();
170             if (type.isInstance(item)) {
171                 return Optional.of((T) item);
172             } else {
173                 logger.warn("Unsupported item type for characteristic {} at accessory {}. Expected {}, got {}",
174                         characteristic, accessory.getItem().getName(), type, taggedItem.get().getItem().getClass());
175             }
176         } else {
177             logger.warn("Mandatory characteristic {} not found at accessory {}. ", characteristic,
178                     accessory.getItem().getName());
179
180         }
181         return Optional.empty();
182     }
183
184     /**
185      * return configuration attached to the root accessory, e.g. groupItem.
186      * Note: result will be casted to the type of the default value.
187      * The type for number is BigDecimal.
188      *
189      * @param key configuration key
190      * @param defaultValue default value
191      * @param <T> expected type
192      * @return configuration value
193      */
194     @NonNullByDefault
195     protected <T> T getAccessoryConfiguration(String key, T defaultValue) {
196         return accessory.getConfiguration(key, defaultValue);
197     }
198
199     /**
200      * return configuration attached to the root accessory, e.g. groupItem.
201      *
202      * @param key configuration key
203      * @param defaultValue default value
204      * @return configuration value
205      */
206     @NonNullByDefault
207     protected boolean getAccessoryConfigurationAsBoolean(String key, boolean defaultValue) {
208         return accessory.getConfigurationAsBoolean(key, defaultValue);
209     }
210
211     /**
212      * return configuration of the characteristic item, e.g. currentTemperature.
213      * Note: result will be casted to the type of the default value.
214      * The type for number is BigDecimal.
215      *
216      * @param characteristicType characteristic type
217      * @param key configuration key
218      * @param defaultValue default value
219      * @param <T> expected type
220      * @return configuration value
221      */
222     @NonNullByDefault
223     protected <T> T getAccessoryConfiguration(HomekitCharacteristicType characteristicType, String key,
224             T defaultValue) {
225         return getCharacteristic(characteristicType)
226                 .map(homekitTaggedItem -> homekitTaggedItem.getConfiguration(key, defaultValue)).orElse(defaultValue);
227     }
228
229     /**
230      * update mapping with values from item configuration.
231      * it checks for all keys from the mapping whether there is configuration at item with the same key and if yes,
232      * replace the value.
233      *
234      * @param characteristicType characteristicType to identify item
235      * @param map mapping to update
236      * @param customEnumList list to store custom state enumeration
237      */
238     @NonNullByDefault
239     protected <T> void updateMapping(HomekitCharacteristicType characteristicType, Map<T, String> map,
240             @Nullable List<T> customEnumList) {
241         getCharacteristic(characteristicType).ifPresent(c -> {
242             final Map<String, Object> configuration = c.getConfiguration();
243             if (configuration != null) {
244                 map.forEach((k, current_value) -> {
245                     final Object new_value = configuration.get(k.toString());
246                     if (new_value instanceof String) {
247                         map.put(k, (String) new_value);
248                         if (customEnumList != null) {
249                             customEnumList.add(k);
250                         }
251                     }
252                 });
253             }
254         });
255     }
256
257     @NonNullByDefault
258     protected <T> void updateMapping(HomekitCharacteristicType characteristicType, Map<T, String> map) {
259         updateMapping(characteristicType, map, null);
260     }
261
262     /**
263      * takes item state as value and retrieves the key for that value from mapping.
264      * e.g. used to map StringItem value to HomeKit Enum
265      *
266      * @param characteristicType characteristicType to identify item
267      * @param mapping mapping
268      * @param defaultValue default value if nothing found in mapping
269      * @param <T> type of the result derived from
270      * @return key for the value
271      */
272     @NonNullByDefault
273     protected <T> T getKeyFromMapping(HomekitCharacteristicType characteristicType, Map<T, String> mapping,
274             T defaultValue) {
275         final Optional<HomekitTaggedItem> c = getCharacteristic(characteristicType);
276         if (c.isPresent()) {
277             final State state = c.get().getItem().getState();
278             logger.trace("getKeyFromMapping: characteristic {}, state {}, mapping {}", characteristicType.getTag(),
279                     state, mapping);
280             if (state instanceof StringType) {
281                 return mapping.entrySet().stream().filter(entry -> state.toString().equalsIgnoreCase(entry.getValue()))
282                         .findAny().map(Entry::getKey).orElseGet(() -> {
283                             logger.warn(
284                                     "Wrong value {} for {} characteristic of the item {}. Expected one of following {}. Returning {}.",
285                                     state.toString(), characteristicType.getTag(), c.get().getName(), mapping.values(),
286                                     defaultValue);
287                             return defaultValue;
288                         });
289             }
290         }
291         return defaultValue;
292     }
293
294     @NonNullByDefault
295     protected void addCharacteristic(HomekitTaggedItem characteristic) {
296         characteristics.add(characteristic);
297     }
298
299     /**
300      * create boolean reader with ON state mapped to trueOnOffValue or trueOpenClosedValue depending of item type
301      *
302      * @param characteristicType characteristic id
303      * @param trueOnOffValue ON value for switch
304      * @param trueOpenClosedValue ON value for contact
305      * @return boolean read
306      * @throws IncompleteAccessoryException
307      */
308     @NonNullByDefault
309     protected BooleanItemReader createBooleanReader(HomekitCharacteristicType characteristicType,
310             OnOffType trueOnOffValue, OpenClosedType trueOpenClosedValue) throws IncompleteAccessoryException {
311         return new BooleanItemReader(
312                 getItem(characteristicType, GenericItem.class)
313                         .orElseThrow(() -> new IncompleteAccessoryException(characteristicType)),
314                 trueOnOffValue, trueOpenClosedValue);
315     }
316
317     /**
318      * create boolean reader with default ON/OFF mapping considering inverted flag
319      *
320      * @param characteristicType characteristic id
321      * @return boolean reader
322      * @throws IncompleteAccessoryException
323      */
324     @NonNullByDefault
325     protected BooleanItemReader createBooleanReader(HomekitCharacteristicType characteristicType)
326             throws IncompleteAccessoryException {
327         final HomekitTaggedItem taggedItem = getCharacteristic(characteristicType)
328                 .orElseThrow(() -> new IncompleteAccessoryException(characteristicType));
329         return new BooleanItemReader(taggedItem.getItem(), taggedItem.isInverted() ? OnOffType.OFF : OnOffType.ON,
330                 taggedItem.isInverted() ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
331     }
332 }