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