2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.hue.internal.api.dto.clip2.helper;
15 import java.math.BigDecimal;
16 import java.time.Duration;
17 import java.util.List;
18 import java.util.Objects;
20 import javax.measure.Unit;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.hue.internal.api.dto.clip2.Alerts;
25 import org.openhab.binding.hue.internal.api.dto.clip2.ColorTemperature;
26 import org.openhab.binding.hue.internal.api.dto.clip2.ColorXy;
27 import org.openhab.binding.hue.internal.api.dto.clip2.Dimming;
28 import org.openhab.binding.hue.internal.api.dto.clip2.Effects;
29 import org.openhab.binding.hue.internal.api.dto.clip2.MetaData;
30 import org.openhab.binding.hue.internal.api.dto.clip2.MirekSchema;
31 import org.openhab.binding.hue.internal.api.dto.clip2.OnState;
32 import org.openhab.binding.hue.internal.api.dto.clip2.Resource;
33 import org.openhab.binding.hue.internal.api.dto.clip2.TimedEffects;
34 import org.openhab.binding.hue.internal.api.dto.clip2.enums.ActionType;
35 import org.openhab.binding.hue.internal.api.dto.clip2.enums.EffectType;
36 import org.openhab.core.library.types.DecimalType;
37 import org.openhab.core.library.types.HSBType;
38 import org.openhab.core.library.types.PercentType;
39 import org.openhab.core.library.types.QuantityType;
40 import org.openhab.core.library.types.StringType;
41 import org.openhab.core.library.unit.Units;
42 import org.openhab.core.types.Command;
43 import org.openhab.core.util.ColorUtil;
44 import org.openhab.core.util.ColorUtil.Gamut;
47 * Advanced setter methods for fields in the Resource class for special cases where setting the new value in the target
48 * resource depends on logic using the values of other fields in a another source Resource.
50 * @author Andrew Fiddian-Green - Initial contribution
53 public class Setters {
56 * Setter for Alert field:
57 * Use the given command value to set the target resource DTO value based on the attributes of the source resource
60 * @param target the target resource.
61 * @param command the new state command should be a StringType.
62 * @param source another resource containing the allowed alert action values.
64 * @return the target resource.
66 public static Resource setAlert(Resource target, Command command, @Nullable Resource source) {
67 if ((command instanceof StringType) && Objects.nonNull(source)) {
68 Alerts otherAlert = source.getAlerts();
69 if (Objects.nonNull(otherAlert)) {
70 ActionType actionType = ActionType.of(((StringType) command).toString());
71 if (otherAlert.getActionValues().contains(actionType)) {
72 target.setAlerts(new Alerts().setAction(actionType));
80 * Setter for Color Temperature field:
81 * Use the given command value to set the target resource DTO value based on the attributes of the source resource
84 * @param target the target resource.
85 * @param command the new state command should be a {@code QuantityType<Temperature>} (but it can also handle
86 * {@code DecimalType}).
87 * @param source another resource containing the MirekSchema.
89 * @return the target resource.
91 public static Resource setColorTemperatureAbsolute(Resource target, Command command, @Nullable Resource source) {
92 QuantityType<?> mirek;
93 if (command instanceof QuantityType<?>) {
94 QuantityType<?> quantity = (QuantityType<?>) command;
95 Unit<?> unit = quantity.getUnit();
96 if (Units.KELVIN.equals(unit)) {
97 mirek = quantity.toInvertibleUnit(Units.MIRED);
98 } else if (Units.MIRED.equals(unit)) {
101 QuantityType<?> kelvin = quantity.toInvertibleUnit(Units.KELVIN);
102 mirek = Objects.nonNull(kelvin) ? kelvin.toInvertibleUnit(Units.MIRED) : null;
104 } else if (command instanceof DecimalType) {
105 mirek = QuantityType.valueOf(((DecimalType) command).doubleValue(), Units.KELVIN)
106 .toInvertibleUnit(Units.MIRED);
110 if (Objects.nonNull(mirek)) {
111 MirekSchema schema = target.getMirekSchema();
112 schema = Objects.nonNull(schema) ? schema : Objects.nonNull(source) ? source.getMirekSchema() : null;
113 schema = Objects.nonNull(schema) ? schema : MirekSchema.DEFAULT_SCHEMA;
114 ColorTemperature colorTemperature = target.getColorTemperature();
115 colorTemperature = Objects.nonNull(colorTemperature) ? colorTemperature : new ColorTemperature();
116 double min = schema.getMirekMinimum();
117 double max = schema.getMirekMaximum();
118 double val = Math.max(min, Math.min(max, mirek.doubleValue()));
119 target.setColorTemperature(colorTemperature.setMirek(val));
125 * Setter for Color Temperature field:
126 * Use the given command value to set the target resource DTO value based on the attributes of the source resource
129 * @param target the target resource.
130 * @param command the new state command should be a PercentType.
131 * @param source another resource containing the MirekSchema.
133 * @return the target resource.
135 public static Resource setColorTemperaturePercent(Resource target, Command command, @Nullable Resource source) {
136 if (command instanceof PercentType) {
137 MirekSchema schema = target.getMirekSchema();
138 schema = Objects.nonNull(schema) ? schema : Objects.nonNull(source) ? source.getMirekSchema() : null;
139 schema = Objects.nonNull(schema) ? schema : MirekSchema.DEFAULT_SCHEMA;
140 ColorTemperature colorTemperature = target.getColorTemperature();
141 colorTemperature = Objects.nonNull(colorTemperature) ? colorTemperature : new ColorTemperature();
142 double min = schema.getMirekMinimum();
143 double max = schema.getMirekMaximum();
144 double val = min + ((max - min) * ((PercentType) command).doubleValue() / 100f);
145 target.setColorTemperature(colorTemperature.setMirek(val));
151 * Setter for Color Xy field:
152 * Use the given command value to set the target resource DTO value based on the attributes of the source resource
153 * (if any). Use the HS parts of the HSB value to set the value of the 'ColorXy' JSON element, and ignore the 'B'
156 * @param target the target resource.
157 * @param command the new state command should be an HSBType with the new color XY value.
158 * @param source another resource containing the color Gamut.
160 * @return the target resource.
162 public static Resource setColorXy(Resource target, Command command, @Nullable Resource source) {
163 if (command instanceof HSBType) {
164 Gamut gamut = target.getGamut();
165 gamut = Objects.nonNull(gamut) ? gamut : Objects.nonNull(source) ? source.getGamut() : null;
166 gamut = Objects.nonNull(gamut) ? gamut : ColorUtil.DEFAULT_GAMUT;
167 HSBType hsb = (HSBType) command;
168 ColorXy color = target.getColorXy();
169 target.setColorXy((Objects.nonNull(color) ? color : new ColorXy()).setXY(ColorUtil.hsbToXY(hsb, gamut)));
175 * Setter for Dimming field:
176 * Use the given command value to set the target resource DTO value based on the attributes of the source resource
179 * @param target the target resource.
180 * @param command the new state command should be a PercentType with the new dimming parameter.
181 * @param source another resource containing the minimum dimming level.
183 * @return the target resource.
185 public static Resource setDimming(Resource target, Command command, @Nullable Resource source) {
186 if (command instanceof PercentType) {
187 Double min = target.getMinimumDimmingLevel();
188 min = Objects.nonNull(min) ? min : Objects.nonNull(source) ? source.getMinimumDimmingLevel() : null;
189 min = Objects.nonNull(min) ? min : Dimming.DEFAULT_MINIMUM_DIMMIMG_LEVEL;
190 PercentType brightness = (PercentType) command;
191 if (brightness.doubleValue() < min.doubleValue()) {
192 brightness = new PercentType(new BigDecimal(min, Resource.PERCENT_MATH_CONTEXT));
194 Dimming dimming = target.getDimming();
195 dimming = Objects.nonNull(dimming) ? dimming : new Dimming();
196 dimming.setBrightness(brightness.doubleValue());
197 target.setDimming(dimming);
203 * Setter for fixed or timed effect field:
204 * Use the given command value to set the target fixed or timed effects resource DTO value based on the attributes
205 * of the source resource (if any).
207 * @param target the target resource.
208 * @param command the new state command should be a StringType.
209 * @param source another resource containing the allowed effect action values.
211 * @return the target resource.
213 public static Resource setEffect(Resource target, Command command, @Nullable Resource source) {
214 if ((command instanceof StringType) && Objects.nonNull(source)) {
215 EffectType commandEffectType = EffectType.of(((StringType) command).toString());
216 Effects sourceFixedEffects = source.getFixedEffects();
217 if (Objects.nonNull(sourceFixedEffects) && sourceFixedEffects.allows(commandEffectType)) {
218 target.setFixedEffects(new Effects().setEffect(commandEffectType));
220 TimedEffects sourceTimedEffects = source.getTimedEffects();
221 if (Objects.nonNull(sourceTimedEffects) && sourceTimedEffects.allows(commandEffectType)) {
222 Duration duration = sourceTimedEffects.getDuration();
223 target.setTimedEffects(((TimedEffects) new TimedEffects().setEffect(commandEffectType))
224 .setDuration(Objects.nonNull(duration) ? duration : TimedEffects.DEFAULT_DURATION));
231 * Setter to copy persisted fields from the source Resource into the target Resource. If the field in the target is
232 * null and the same field in the source is not null, then the value from the source is copied to the target. This
233 * method allows 'hasSparseData' resources to expand themselves to include necessary fields taken over from a
234 * previously cached full data resource.
236 * @param target the target resource.
237 * @param source another resource containing the values to be taken over.
239 * @return the target resource.
241 public static Resource setResource(Resource target, Resource source) {
243 OnState targetOnOff = target.getOnState();
244 OnState sourceOnOff = source.getOnState();
245 if (Objects.isNull(targetOnOff) && Objects.nonNull(sourceOnOff)) {
246 target.setOnState(sourceOnOff);
250 Dimming targetDimming = target.getDimming();
251 Dimming sourceDimming = source.getDimming();
252 if (Objects.isNull(targetDimming) && Objects.nonNull(sourceDimming)) {
253 target.setDimming(sourceDimming);
254 targetDimming = target.getDimming();
257 // minimum dimming level
258 if (Objects.nonNull(targetDimming)) {
259 Double sourceMinDimLevel = Objects.isNull(sourceDimming) ? null : sourceDimming.getMinimumDimmingLevel();
260 if (Objects.nonNull(sourceMinDimLevel)) {
261 targetDimming.setMinimumDimmingLevel(sourceMinDimLevel);
266 ColorXy targetColor = target.getColorXy();
267 ColorXy sourceColor = source.getColorXy();
268 if (Objects.isNull(targetColor) && Objects.nonNull(sourceColor)) {
269 target.setColorXy(sourceColor);
270 targetColor = target.getColorXy();
274 Gamut sourceGamut = Objects.isNull(sourceColor) ? null : sourceColor.getGamut();
275 if (Objects.nonNull(targetColor) && Objects.nonNull(sourceGamut)) {
276 targetColor.setGamut(sourceGamut);
280 ColorTemperature targetColorTemp = target.getColorTemperature();
281 ColorTemperature sourceColorTemp = source.getColorTemperature();
282 if (Objects.isNull(targetColorTemp) && Objects.nonNull(sourceColorTemp)) {
283 target.setColorTemperature(sourceColorTemp);
284 targetColorTemp = target.getColorTemperature();
288 if (Objects.nonNull(targetColorTemp)) {
289 MirekSchema sourceMirekSchema = Objects.isNull(sourceColorTemp) ? null : sourceColorTemp.getMirekSchema();
290 if (Objects.nonNull(sourceMirekSchema)) {
291 targetColorTemp.setMirekSchema(sourceMirekSchema);
296 MetaData targetMetaData = target.getMetaData();
297 MetaData sourceMetaData = source.getMetaData();
298 if (Objects.isNull(targetMetaData) && Objects.nonNull(sourceMetaData)) {
299 target.setMetadata(sourceMetaData);
303 Alerts targetAlerts = target.getAlerts();
304 Alerts sourceAlerts = source.getAlerts();
305 if (Objects.isNull(targetAlerts) && Objects.nonNull(sourceAlerts)) {
306 target.setAlerts(sourceAlerts);
310 Effects targetFixedEffects = target.getFixedEffects();
311 Effects sourceFixedEffects = source.getFixedEffects();
312 if (Objects.isNull(targetFixedEffects) && Objects.nonNull(sourceFixedEffects)) {
313 target.setFixedEffects(sourceFixedEffects);
314 targetFixedEffects = target.getFixedEffects();
317 // fixed effects allowed values
318 if (Objects.nonNull(targetFixedEffects)) {
319 List<String> values = Objects.isNull(sourceFixedEffects) ? List.of() : sourceFixedEffects.getStatusValues();
320 if (!values.isEmpty()) {
321 targetFixedEffects.setStatusValues(values);
326 TimedEffects targetTimedEffects = target.getTimedEffects();
327 TimedEffects sourceTimedEffects = source.getTimedEffects();
328 if (Objects.isNull(targetTimedEffects) && Objects.nonNull(sourceTimedEffects)) {
329 target.setTimedEffects(sourceTimedEffects);
330 targetTimedEffects = target.getTimedEffects();
333 // timed effects allowed values and duration
334 if (Objects.nonNull(targetTimedEffects)) {
335 List<String> values = Objects.isNull(sourceTimedEffects) ? List.of() : sourceTimedEffects.getStatusValues();
336 if (!values.isEmpty()) {
337 targetTimedEffects.setStatusValues(values);
339 Duration duration = Objects.isNull(sourceTimedEffects) ? null : sourceTimedEffects.getDuration();
340 if (Objects.nonNull(duration)) {
341 targetTimedEffects.setDuration(duration);