]> git.basschouten.com Git - openhab-addons.git/blob
e59b78482e9f9b37662970a14f4115b36f9b8c51
[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.binding.hue.internal.api.dto.clip2.helper;
14
15 import java.math.BigDecimal;
16 import java.time.Duration;
17 import java.util.Collection;
18 import java.util.HashMap;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Objects;
23 import java.util.Set;
24
25 import javax.measure.Unit;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.hue.internal.api.dto.clip2.Alerts;
30 import org.openhab.binding.hue.internal.api.dto.clip2.ColorTemperature;
31 import org.openhab.binding.hue.internal.api.dto.clip2.ColorXy;
32 import org.openhab.binding.hue.internal.api.dto.clip2.Dimming;
33 import org.openhab.binding.hue.internal.api.dto.clip2.Effects;
34 import org.openhab.binding.hue.internal.api.dto.clip2.MetaData;
35 import org.openhab.binding.hue.internal.api.dto.clip2.MirekSchema;
36 import org.openhab.binding.hue.internal.api.dto.clip2.OnState;
37 import org.openhab.binding.hue.internal.api.dto.clip2.Resource;
38 import org.openhab.binding.hue.internal.api.dto.clip2.TimedEffects;
39 import org.openhab.binding.hue.internal.api.dto.clip2.enums.ActionType;
40 import org.openhab.binding.hue.internal.api.dto.clip2.enums.EffectType;
41 import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType;
42 import org.openhab.core.library.types.DecimalType;
43 import org.openhab.core.library.types.HSBType;
44 import org.openhab.core.library.types.PercentType;
45 import org.openhab.core.library.types.QuantityType;
46 import org.openhab.core.library.types.StringType;
47 import org.openhab.core.library.unit.Units;
48 import org.openhab.core.types.Command;
49 import org.openhab.core.util.ColorUtil;
50 import org.openhab.core.util.ColorUtil.Gamut;
51
52 /**
53  * Advanced setter methods for fields in the Resource class for special cases where setting the new value in the target
54  * resource depends on logic using the values of other fields in a another source Resource.
55  *
56  * @author Andrew Fiddian-Green - Initial contribution
57  */
58 @NonNullByDefault
59 public class Setters {
60
61     private static final Set<ResourceType> LIGHT_TYPES = Set.of(ResourceType.LIGHT, ResourceType.GROUPED_LIGHT);
62
63     /**
64      * Setter for Alert field:
65      * Use the given command value to set the target resource DTO value based on the attributes of the source resource
66      * (if any).
67      *
68      * @param target the target resource.
69      * @param command the new state command should be a StringType.
70      * @param source another resource containing the allowed alert action values.
71      *
72      * @return the target resource.
73      */
74     public static Resource setAlert(Resource target, Command command, @Nullable Resource source) {
75         if ((command instanceof StringType) && Objects.nonNull(source)) {
76             Alerts otherAlert = source.getAlerts();
77             if (Objects.nonNull(otherAlert)) {
78                 ActionType actionType = ActionType.of(((StringType) command).toString());
79                 if (otherAlert.getActionValues().contains(actionType)) {
80                     target.setAlerts(new Alerts().setAction(actionType));
81                 }
82             }
83         }
84         return target;
85     }
86
87     /**
88      * Setter for Color Temperature field:
89      * Use the given command value to set the target resource DTO value based on the attributes of the source resource
90      * (if any).
91      *
92      * @param target the target resource.
93      * @param command the new state command should be a {@code QuantityType<Temperature>} (but it can also handle
94      *            {@code DecimalType}).
95      * @param source another resource containing the MirekSchema.
96      *
97      * @return the target resource.
98      */
99     public static Resource setColorTemperatureAbsolute(Resource target, Command command, @Nullable Resource source) {
100         QuantityType<?> mirek;
101         if (command instanceof QuantityType<?>) {
102             QuantityType<?> quantity = (QuantityType<?>) command;
103             Unit<?> unit = quantity.getUnit();
104             if (Units.KELVIN.equals(unit)) {
105                 mirek = quantity.toInvertibleUnit(Units.MIRED);
106             } else if (Units.MIRED.equals(unit)) {
107                 mirek = quantity;
108             } else {
109                 QuantityType<?> kelvin = quantity.toInvertibleUnit(Units.KELVIN);
110                 mirek = Objects.nonNull(kelvin) ? kelvin.toInvertibleUnit(Units.MIRED) : null;
111             }
112         } else if (command instanceof DecimalType) {
113             mirek = QuantityType.valueOf(((DecimalType) command).doubleValue(), Units.KELVIN)
114                     .toInvertibleUnit(Units.MIRED);
115         } else {
116             mirek = null;
117         }
118         if (Objects.nonNull(mirek)) {
119             MirekSchema schema = target.getMirekSchema();
120             schema = Objects.nonNull(schema) ? schema : Objects.nonNull(source) ? source.getMirekSchema() : null;
121             schema = Objects.nonNull(schema) ? schema : MirekSchema.DEFAULT_SCHEMA;
122             ColorTemperature colorTemperature = target.getColorTemperature();
123             colorTemperature = Objects.nonNull(colorTemperature) ? colorTemperature : new ColorTemperature();
124             double min = schema.getMirekMinimum();
125             double max = schema.getMirekMaximum();
126             double val = Math.max(min, Math.min(max, mirek.doubleValue()));
127             target.setColorTemperature(colorTemperature.setMirek(val));
128         }
129         return target;
130     }
131
132     /**
133      * Setter for Color Temperature field:
134      * Use the given command value to set the target resource DTO value based on the attributes of the source resource
135      * (if any).
136      *
137      * @param target the target resource.
138      * @param command the new state command should be a PercentType.
139      * @param source another resource containing the MirekSchema.
140      *
141      * @return the target resource.
142      */
143     public static Resource setColorTemperaturePercent(Resource target, Command command, @Nullable Resource source) {
144         if (command instanceof PercentType) {
145             MirekSchema schema = target.getMirekSchema();
146             schema = Objects.nonNull(schema) ? schema : Objects.nonNull(source) ? source.getMirekSchema() : null;
147             schema = Objects.nonNull(schema) ? schema : MirekSchema.DEFAULT_SCHEMA;
148             ColorTemperature colorTemperature = target.getColorTemperature();
149             colorTemperature = Objects.nonNull(colorTemperature) ? colorTemperature : new ColorTemperature();
150             double min = schema.getMirekMinimum();
151             double max = schema.getMirekMaximum();
152             double val = min + ((max - min) * ((PercentType) command).doubleValue() / 100f);
153             target.setColorTemperature(colorTemperature.setMirek(val));
154         }
155         return target;
156     }
157
158     /**
159      * Setter for Color Xy field:
160      * Use the given command value to set the target resource DTO value based on the attributes of the source resource
161      * (if any). Use the HS parts of the HSB value to set the value of the 'ColorXy' JSON element, and ignore the 'B'
162      * part.
163      *
164      * @param target the target resource.
165      * @param command the new state command should be an HSBType with the new color XY value.
166      * @param source another resource containing the color Gamut.
167      *
168      * @return the target resource.
169      */
170     public static Resource setColorXy(Resource target, Command command, @Nullable Resource source) {
171         if (command instanceof HSBType) {
172             Gamut gamut = target.getGamut();
173             gamut = Objects.nonNull(gamut) ? gamut : Objects.nonNull(source) ? source.getGamut() : null;
174             gamut = Objects.nonNull(gamut) ? gamut : ColorUtil.DEFAULT_GAMUT;
175             HSBType hsb = (HSBType) command;
176             ColorXy color = target.getColorXy();
177             target.setColorXy((Objects.nonNull(color) ? color : new ColorXy()).setXY(ColorUtil.hsbToXY(hsb, gamut)));
178         }
179         return target;
180     }
181
182     /**
183      * Setter for Dimming field:
184      * Use the given command value to set the target resource DTO value based on the attributes of the source resource
185      * (if any).
186      *
187      * @param target the target resource.
188      * @param command the new state command should be a PercentType with the new dimming parameter.
189      * @param source another resource containing the minimum dimming level.
190      *
191      * @return the target resource.
192      */
193     public static Resource setDimming(Resource target, Command command, @Nullable Resource source) {
194         if (command instanceof PercentType) {
195             Double min = target.getMinimumDimmingLevel();
196             min = Objects.nonNull(min) ? min : Objects.nonNull(source) ? source.getMinimumDimmingLevel() : null;
197             min = Objects.nonNull(min) ? min : Dimming.DEFAULT_MINIMUM_DIMMIMG_LEVEL;
198             PercentType brightness = (PercentType) command;
199             if (brightness.doubleValue() < min.doubleValue()) {
200                 brightness = new PercentType(new BigDecimal(min, Resource.PERCENT_MATH_CONTEXT));
201             }
202             Dimming dimming = target.getDimming();
203             dimming = Objects.nonNull(dimming) ? dimming : new Dimming();
204             dimming.setBrightness(brightness.doubleValue());
205             target.setDimming(dimming);
206         }
207         return target;
208     }
209
210     /**
211      * Setter for fixed or timed effect field:
212      * Use the given command value to set the target fixed or timed effects resource DTO value based on the attributes
213      * of the source resource (if any).
214      *
215      * @param target the target resource.
216      * @param command the new state command should be a StringType.
217      * @param source another resource containing the allowed effect action values.
218      *
219      * @return the target resource.
220      */
221     public static Resource setEffect(Resource target, Command command, @Nullable Resource source) {
222         if ((command instanceof StringType) && Objects.nonNull(source)) {
223             EffectType commandEffectType = EffectType.of(((StringType) command).toString());
224             Effects sourceFixedEffects = source.getFixedEffects();
225             if (Objects.nonNull(sourceFixedEffects) && sourceFixedEffects.allows(commandEffectType)) {
226                 target.setFixedEffects(new Effects().setEffect(commandEffectType));
227             }
228             TimedEffects sourceTimedEffects = source.getTimedEffects();
229             if (Objects.nonNull(sourceTimedEffects) && sourceTimedEffects.allows(commandEffectType)) {
230                 Duration duration = sourceTimedEffects.getDuration();
231                 target.setTimedEffects(((TimedEffects) new TimedEffects().setEffect(commandEffectType))
232                         .setDuration(Objects.nonNull(duration) ? duration : TimedEffects.DEFAULT_DURATION));
233             }
234         }
235         return target;
236     }
237
238     /**
239      * Setter to copy persisted fields from the source Resource into the target Resource. If the field in the target is
240      * null and the same field in the source is not null, then the value from the source is copied to the target. This
241      * method allows 'hasSparseData' resources to expand themselves to include necessary fields taken over from a
242      * previously cached full data resource.
243      *
244      * @param target the target resource.
245      * @param source another resource containing the values to be taken over.
246      *
247      * @return the target resource.
248      */
249     public static Resource setResource(Resource target, Resource source) {
250         // on
251         OnState targetOnOff = target.getOnState();
252         OnState sourceOnOff = source.getOnState();
253         if (Objects.isNull(targetOnOff) && Objects.nonNull(sourceOnOff)) {
254             target.setOnState(sourceOnOff);
255         }
256
257         // dimming
258         Dimming targetDimming = target.getDimming();
259         Dimming sourceDimming = source.getDimming();
260         if (Objects.isNull(targetDimming) && Objects.nonNull(sourceDimming)) {
261             target.setDimming(sourceDimming);
262             targetDimming = target.getDimming();
263         }
264
265         // minimum dimming level
266         if (Objects.nonNull(targetDimming)) {
267             Double sourceMinDimLevel = Objects.isNull(sourceDimming) ? null : sourceDimming.getMinimumDimmingLevel();
268             if (Objects.nonNull(sourceMinDimLevel)) {
269                 targetDimming.setMinimumDimmingLevel(sourceMinDimLevel);
270             }
271         }
272
273         // color
274         ColorXy targetColor = target.getColorXy();
275         ColorXy sourceColor = source.getColorXy();
276         if (Objects.isNull(targetColor) && Objects.nonNull(sourceColor)) {
277             target.setColorXy(sourceColor);
278             targetColor = target.getColorXy();
279         }
280
281         // color gamut
282         Gamut sourceGamut = Objects.isNull(sourceColor) ? null : sourceColor.getGamut();
283         if (Objects.nonNull(targetColor) && Objects.nonNull(sourceGamut)) {
284             targetColor.setGamut(sourceGamut);
285         }
286
287         // color temperature
288         ColorTemperature targetColorTemp = target.getColorTemperature();
289         ColorTemperature sourceColorTemp = source.getColorTemperature();
290         if (Objects.isNull(targetColorTemp) && Objects.nonNull(sourceColorTemp)) {
291             target.setColorTemperature(sourceColorTemp);
292             targetColorTemp = target.getColorTemperature();
293         }
294
295         // mirek schema
296         if (Objects.nonNull(targetColorTemp)) {
297             MirekSchema sourceMirekSchema = Objects.isNull(sourceColorTemp) ? null : sourceColorTemp.getMirekSchema();
298             if (Objects.nonNull(sourceMirekSchema)) {
299                 targetColorTemp.setMirekSchema(sourceMirekSchema);
300             }
301         }
302
303         // metadata
304         MetaData targetMetaData = target.getMetaData();
305         MetaData sourceMetaData = source.getMetaData();
306         if (Objects.isNull(targetMetaData) && Objects.nonNull(sourceMetaData)) {
307             target.setMetadata(sourceMetaData);
308         }
309
310         // alerts
311         Alerts targetAlerts = target.getAlerts();
312         Alerts sourceAlerts = source.getAlerts();
313         if (Objects.isNull(targetAlerts) && Objects.nonNull(sourceAlerts)) {
314             target.setAlerts(sourceAlerts);
315         }
316
317         // fixed effects
318         Effects targetFixedEffects = target.getFixedEffects();
319         Effects sourceFixedEffects = source.getFixedEffects();
320         if (Objects.isNull(targetFixedEffects) && Objects.nonNull(sourceFixedEffects)) {
321             target.setFixedEffects(sourceFixedEffects);
322             targetFixedEffects = target.getFixedEffects();
323         }
324
325         // fixed effects allowed values
326         if (Objects.nonNull(targetFixedEffects)) {
327             List<String> values = Objects.isNull(sourceFixedEffects) ? List.of() : sourceFixedEffects.getStatusValues();
328             if (!values.isEmpty()) {
329                 targetFixedEffects.setStatusValues(values);
330             }
331         }
332
333         // timed effects
334         TimedEffects targetTimedEffects = target.getTimedEffects();
335         TimedEffects sourceTimedEffects = source.getTimedEffects();
336         if (Objects.isNull(targetTimedEffects) && Objects.nonNull(sourceTimedEffects)) {
337             target.setTimedEffects(sourceTimedEffects);
338             targetTimedEffects = target.getTimedEffects();
339         }
340
341         // timed effects allowed values and duration
342         if (Objects.nonNull(targetTimedEffects)) {
343             List<String> values = Objects.isNull(sourceTimedEffects) ? List.of() : sourceTimedEffects.getStatusValues();
344             if (!values.isEmpty()) {
345                 targetTimedEffects.setStatusValues(values);
346             }
347             Duration duration = Objects.isNull(sourceTimedEffects) ? null : sourceTimedEffects.getDuration();
348             if (Objects.nonNull(duration)) {
349                 targetTimedEffects.setDuration(duration);
350             }
351         }
352         return target;
353     }
354
355     /**
356      * Merge on/dimming/color fields from light and grouped light resources.
357      * Subsequent resources will be merged into the first one.
358      * Full state resources are not supported by this method.
359      */
360     public static Collection<Resource> mergeLightResources(Collection<Resource> resources) {
361         Map<String, Resource> resourceIndex = new HashMap<>();
362         Iterator<Resource> iterator = resources.iterator();
363         while (iterator.hasNext()) {
364             Resource resource = iterator.next();
365             String id = resource.getId();
366
367             if (resource.hasFullState()) {
368                 throw new IllegalStateException("Resource " + id + " has full state, this is not expected");
369             }
370
371             Resource indexedResource = resourceIndex.get(id);
372             if (indexedResource == null) {
373                 resourceIndex.put(id, resource);
374                 continue;
375             }
376
377             if (!LIGHT_TYPES.contains(resource.getType()) || !resource.hasHSBField()) {
378                 continue;
379             }
380
381             OnState onState = resource.getOnState();
382             if (onState != null) {
383                 indexedResource.setOnState(onState);
384                 resource.setOnState(null);
385             }
386             Dimming dimming = resource.getDimming();
387             if (dimming != null) {
388                 indexedResource.setDimming(dimming);
389                 resource.setDimming(null);
390             }
391             ColorXy colorXy = resource.getColorXy();
392             if (colorXy != null) {
393                 indexedResource.setColorXy(colorXy);
394                 resource.setColorXy(null);
395             }
396
397             if (!resource.hasAnyRelevantField()) {
398                 iterator.remove();
399             }
400         }
401
402         return resources;
403     }
404 }