]> git.basschouten.com Git - openhab-addons.git/blob
ebc3adb0ccc67fc6ae678bc8bff4ba5a3c808f5c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.hueemulation.internal;
14
15 import java.util.ArrayList;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Set;
19 import java.util.TreeMap;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.core.items.Item;
24 import org.openhab.core.library.CoreItemFactory;
25 import org.openhab.core.library.types.HSBType;
26 import org.openhab.core.library.types.OnOffType;
27 import org.openhab.core.library.types.PercentType;
28 import org.openhab.core.types.Command;
29 import org.openhab.core.types.State;
30 import org.openhab.io.hueemulation.internal.dto.AbstractHueState;
31 import org.openhab.io.hueemulation.internal.dto.HueStateBulb;
32 import org.openhab.io.hueemulation.internal.dto.HueStateColorBulb;
33 import org.openhab.io.hueemulation.internal.dto.HueStateColorBulb.ColorMode;
34 import org.openhab.io.hueemulation.internal.dto.HueStatePlug;
35 import org.openhab.io.hueemulation.internal.dto.changerequest.HueStateChange;
36 import org.openhab.io.hueemulation.internal.dto.response.HueResponse;
37 import org.openhab.io.hueemulation.internal.dto.response.HueResponse.HueErrorMessage;
38 import org.openhab.io.hueemulation.internal.dto.response.HueSuccessResponseStateChanged;
39
40 /**
41  * This utility class provides all kind of functions to convert between openHAB item states to Hue states and back
42  * as well as applying a hue state change request to a hue state or openHAB item state.
43  * <p>
44  * It also provides methods to determine the hue type (plug, white bulb, coloured bulb), given an item.
45  *
46  * @author David Graeff - Initial contribution
47  */
48 @NonNullByDefault
49 public class StateUtils {
50
51     /**
52      * Compute the hue state from a given item state and a device type.
53      *
54      * @param itemState The item state
55      * @param deviceType The device type
56      * @return A hue light state
57      */
58     public static AbstractHueState colorStateFromItemState(State itemState, @Nullable DeviceType deviceType) {
59         if (deviceType == null) {
60             return new HueStatePlug(false);
61         }
62         AbstractHueState state;
63         switch (deviceType) {
64             case ColorType:
65                 if (itemState instanceof HSBType) {
66                     state = new HueStateColorBulb((HSBType) itemState);
67                 } else if (itemState instanceof PercentType) {
68                     state = new HueStateColorBulb((PercentType) itemState, ((PercentType) itemState).intValue() > 0);
69                 } else if (itemState instanceof OnOffType) {
70                     OnOffType t = (OnOffType) itemState;
71                     state = new HueStateColorBulb(t == OnOffType.ON);
72                 } else {
73                     state = new HueStateColorBulb(new HSBType());
74                 }
75                 break;
76             case WhiteType:
77             case WhiteTemperatureType:
78                 if (itemState instanceof HSBType) {
79                     PercentType brightness = ((HSBType) itemState).getBrightness();
80                     state = new HueStateBulb(brightness, brightness.intValue() > 0);
81                 } else if (itemState instanceof PercentType) {
82                     PercentType brightness = (PercentType) itemState;
83                     state = new HueStateBulb(brightness, brightness.intValue() > 0);
84                 } else if (itemState instanceof OnOffType) {
85                     OnOffType t = (OnOffType) itemState;
86                     state = new HueStateBulb(t == OnOffType.ON);
87                 } else {
88                     state = new HueStateBulb(new PercentType(0), false);
89                 }
90                 break;
91             case SwitchType:
92             default:
93                 if (itemState instanceof OnOffType) {
94                     OnOffType t = (OnOffType) itemState;
95                     state = new HueStatePlug(t == OnOffType.ON);
96                 } else {
97                     state = new HueStatePlug(false);
98                 }
99         }
100         return state;
101     }
102
103     /**
104      * Computes an openHAB item state, given a hue state.
105      *
106      * <p>
107      * This only proxies to the respective call
108      * on the concrete hue state implementation.
109      *
110      * @throws IllegalStateException Thrown if the concrete hue state is not yet handled by this method.
111      */
112     public static State itemStateByHueState(AbstractHueState state) throws IllegalStateException {
113         if (state instanceof HueStateColorBulb) {
114             return state.as(HueStateColorBulb.class).toHSBType();
115         } else if (state instanceof HueStateBulb) {
116             return state.as(HueStateBulb.class).toBrightnessType();
117         } else if (state instanceof HueStatePlug) {
118             return state.as(HueStatePlug.class).toOnOffType();
119         } else {
120             throw new IllegalStateException();
121         }
122     }
123
124     /**
125      * An openHAB state is usually also a command. Cast the state.
126      *
127      * @throws IllegalStateException Throws if the cast fails.
128      */
129     public static Command commandByItemState(State state) throws IllegalStateException {
130         if (state instanceof Command) {
131             return (Command) state;
132         } else {
133             throw new IllegalStateException();
134         }
135     }
136
137     /**
138      * Computes an openHAB command, given a hue state change request.
139      *
140      * @param changeRequest The change request
141      */
142     public static @Nullable Command computeCommandByChangeRequest(HueStateChange changeRequest) {
143         List<HueResponse> responses = new ArrayList<>();
144         return computeCommandByState(responses, "", new HueStateColorBulb(false), changeRequest);
145     }
146
147     /**
148      * Apply the new received state from the REST PUT request.
149      *
150      * @param responses Creates a response entry for each success and each error. There is one entry per non-null field
151      *            of {@link HueStateChange} created.
152      * @param prefix The response entry prefix, for example "/groups/mygroupid/state/"
153      * @param state The current item state
154      * @param newState A state change DTO
155      * @return Return a command computed via the incoming state object.
156      */
157     public static @Nullable Command computeCommandByState(List<HueResponse> responses, String prefix,
158             AbstractHueState state, HueStateChange newState) {
159         // Apply new state and collect success, error items
160         Map<String, Object> successApplied = new TreeMap<>();
161         List<String> errorApplied = new ArrayList<>();
162
163         Command command = null;
164         if (newState.on != null) {
165             try {
166                 state.as(HueStatePlug.class).on = newState.on;
167                 command = OnOffType.from(newState.on);
168                 successApplied.put("on", newState.on);
169             } catch (ClassCastException e) {
170                 errorApplied.add("on");
171             }
172         }
173
174         if (newState.bri != null) {
175             try {
176                 state.as(HueStateBulb.class).bri = newState.bri;
177                 command = new PercentType((int) (newState.bri * 100.0 / HueStateBulb.MAX_BRI + 0.5));
178                 successApplied.put("bri", newState.bri);
179             } catch (ClassCastException e) {
180                 errorApplied.add("bri");
181             }
182         }
183
184         if (newState.bri_inc != null) {
185             try {
186                 int newBri = state.as(HueStateBulb.class).bri + newState.bri_inc;
187                 if (newBri < 0 || newBri > HueStateBulb.MAX_BRI) {
188                     throw new IllegalArgumentException();
189                 }
190                 command = new PercentType((int) (newBri * 100.0 / HueStateBulb.MAX_BRI + 0.5));
191                 successApplied.put("bri", newState.bri);
192             } catch (ClassCastException e) {
193                 errorApplied.add("bri_inc");
194             } catch (IllegalArgumentException e) {
195                 errorApplied.add("bri_inc");
196             }
197         }
198
199         if (newState.sat != null) {
200             try {
201                 HueStateColorBulb c = state.as(HueStateColorBulb.class);
202                 c.sat = newState.sat;
203                 c.colormode = ColorMode.hs;
204                 command = c.toHSBType();
205                 successApplied.put("sat", newState.sat);
206             } catch (ClassCastException e) {
207                 errorApplied.add("sat");
208             }
209         }
210
211         if (newState.sat_inc != null) {
212             try {
213                 HueStateColorBulb c = state.as(HueStateColorBulb.class);
214                 int newV = c.sat + newState.sat_inc;
215                 if (newV < 0 || newV > HueStateColorBulb.MAX_SAT) {
216                     throw new IllegalArgumentException();
217                 }
218                 c.colormode = ColorMode.hs;
219                 c.sat = newV;
220                 command = c.toHSBType();
221                 successApplied.put("sat", newState.sat);
222             } catch (ClassCastException e) {
223                 errorApplied.add("sat_inc");
224             } catch (IllegalArgumentException e) {
225                 errorApplied.add("sat_inc");
226             }
227         }
228
229         if (newState.hue != null) {
230             try {
231                 HueStateColorBulb c = state.as(HueStateColorBulb.class);
232                 c.colormode = ColorMode.hs;
233                 c.hue = newState.hue;
234                 command = c.toHSBType();
235                 successApplied.put("hue", newState.hue);
236             } catch (ClassCastException e) {
237                 errorApplied.add("hue");
238             }
239         }
240
241         if (newState.hue_inc != null) {
242             try {
243                 HueStateColorBulb c = state.as(HueStateColorBulb.class);
244                 int newV = c.hue + newState.hue_inc;
245                 if (newV < 0 || newV > HueStateColorBulb.MAX_HUE) {
246                     throw new IllegalArgumentException();
247                 }
248                 c.colormode = ColorMode.hs;
249                 c.hue = newV;
250                 command = c.toHSBType();
251                 successApplied.put("hue", newState.hue);
252             } catch (ClassCastException e) {
253                 errorApplied.add("hue_inc");
254             } catch (IllegalArgumentException e) {
255                 errorApplied.add("hue_inc");
256             }
257         }
258
259         if (newState.ct != null) {
260             try {
261                 // We can't do anything here with a white color temperature.
262                 // The color type does not support setting it.
263
264                 // Adjusting the color temperature implies setting the mode to ct
265                 if (state instanceof HueStateColorBulb) {
266                     HueStateColorBulb c = state.as(HueStateColorBulb.class);
267                     c.sat = 0;
268                     c.colormode = ColorMode.ct;
269                     command = c.toHSBType();
270                 }
271                 successApplied.put("colormode", ColorMode.ct);
272                 successApplied.put("sat", 0);
273                 successApplied.put("ct", newState.ct);
274             } catch (ClassCastException e) {
275                 errorApplied.add("ct");
276             }
277         }
278
279         if (newState.ct_inc != null) {
280             try {
281                 // We can't do anything here with a white color temperature.
282                 // The color type does not support setting it.
283
284                 // Adjusting the color temperature implies setting the mode to ct
285                 if (state instanceof HueStateColorBulb) {
286                     HueStateColorBulb c = state.as(HueStateColorBulb.class);
287                     if (c.colormode != ColorMode.ct) {
288                         c.sat = 0;
289                         command = c.toHSBType();
290                         successApplied.put("colormode", c.colormode);
291                     }
292                 }
293                 successApplied.put("ct", newState.ct);
294             } catch (ClassCastException e) {
295                 errorApplied.add("ct_inc");
296             }
297         }
298
299         if (newState.transitiontime != null) {
300             successApplied.put("transitiontime", newState.transitiontime); // Pretend that worked
301         }
302         if (newState.alert != null) {
303             successApplied.put("alert", newState.alert); // Pretend that worked
304         }
305         if (newState.effect != null) {
306             successApplied.put("effect", newState.effect); // Pretend that worked
307         }
308         if (newState.xy != null) {
309             try {
310                 HueStateColorBulb c = state.as(HueStateColorBulb.class);
311                 c.colormode = ColorMode.xy;
312                 c.bri = state.as(HueStateBulb.class).bri;
313                 c.xy[0] = newState.xy.get(0);
314                 c.xy[1] = newState.xy.get(1);
315                 command = c.toHSBType();
316                 successApplied.put("xy", newState.xy);
317             } catch (ClassCastException e) {
318                 errorApplied.add("xy");
319             }
320         }
321         if (newState.xy_inc != null) {
322             try {
323                 HueStateColorBulb c = state.as(HueStateColorBulb.class);
324                 double newX = c.xy[0] + newState.xy_inc.get(0);
325                 double newY = c.xy[1] + newState.xy_inc.get(1);
326                 if (newX < 0 || newX > 1 || newY < 0 || newY > 1) {
327                     throw new IllegalArgumentException();
328                 }
329                 c.colormode = ColorMode.xy;
330                 c.bri = state.as(HueStateBulb.class).bri;
331                 c.xy[0] = newX;
332                 c.xy[1] = newY;
333                 command = c.toHSBType();
334                 successApplied.put("xy", newState.xy_inc);
335             } catch (ClassCastException e) {
336                 errorApplied.add("xy_inc");
337             } catch (IllegalArgumentException e) {
338                 errorApplied.add("xy_inc");
339             }
340         }
341
342         // Generate the response. The response consists of a list with an entry each for all
343         // submitted change requests. If for example "on" and "bri" was send, 2 entries in the response are
344         // expected.
345         successApplied.forEach((t, v) -> {
346             responses.add(new HueResponse(new HueSuccessResponseStateChanged(prefix + "/" + t, v)));
347         });
348         errorApplied.forEach(v -> {
349             responses.add(
350                     new HueResponse(new HueErrorMessage(HueResponse.NOT_AVAILABLE, prefix + "/" + v, "Could not set")));
351         });
352
353         return command;
354     }
355
356     public static @Nullable DeviceType determineTargetType(ConfigStore cs, Item element) {
357         String category = element.getCategory();
358         String type = element.getType();
359         Set<String> tags = element.getTags();
360
361         // Determine type, heuristically
362         DeviceType t = null;
363
364         // The user wants this item to be not exposed
365         if (cs.ignoreItemsFilter.stream().anyMatch(tags::contains)) {
366             return null;
367         }
368
369         // First consider the category
370         if (category != null) {
371             switch (category) {
372                 case "ColorLight":
373                     t = DeviceType.ColorType;
374                     break;
375                 case "Light":
376                     t = DeviceType.SwitchType;
377             }
378         }
379
380         // Then the tags
381         if (cs.switchFilter.stream().anyMatch(tags::contains)) {
382             t = DeviceType.SwitchType;
383         }
384         if (cs.whiteFilter.stream().anyMatch(tags::contains)) {
385             t = DeviceType.WhiteTemperatureType;
386         }
387         if (cs.colorFilter.stream().anyMatch(tags::contains)) {
388             t = DeviceType.ColorType;
389         }
390
391         // Last but not least, the item type
392         if (t == null) {
393             switch (type) {
394                 case CoreItemFactory.COLOR:
395                     if (cs.colorFilter.isEmpty()) {
396                         t = DeviceType.ColorType;
397                     }
398                     break;
399                 case CoreItemFactory.DIMMER:
400                 case CoreItemFactory.ROLLERSHUTTER:
401                     if (cs.whiteFilter.isEmpty()) {
402                         t = DeviceType.WhiteTemperatureType;
403                     }
404                     break;
405                 case CoreItemFactory.SWITCH:
406                     if (cs.switchFilter.isEmpty()) {
407                         t = DeviceType.SwitchType;
408                     }
409                     break;
410             }
411         }
412         return t;
413     }
414
415     /**
416      * Compute the hue state from a given item state and a device type.
417      * If the item state matches the last command. the hue state is adjusted
418      * to use the values from the last hue state change. This is done to prevent
419      * Alexa reporting device errors.
420      *
421      * @param itemState The item state
422      * @param deviceType The device type
423      * @param lastCommand The last command
424      * @param lastHueChange The last hue state change
425      * @return A hue light state
426      */
427     public static AbstractHueState adjustedColorStateFromItemState(State itemState, @Nullable DeviceType deviceType,
428             @Nullable Command lastCommand, @Nullable HueStateChange lastHueChange) {
429         AbstractHueState hueState = colorStateFromItemState(itemState, deviceType);
430
431         if (lastCommand != null && lastHueChange != null) {
432             if (lastCommand instanceof HSBType) {
433                 if (hueState instanceof HueStateColorBulb && itemState.as(HSBType.class).equals(lastCommand)) {
434                     HueStateColorBulb c = (HueStateColorBulb) hueState;
435
436                     if (lastHueChange.bri != null) {
437                         c.bri = lastHueChange.bri;
438                     }
439                     if (lastHueChange.hue != null) {
440                         c.hue = lastHueChange.hue;
441                     }
442                     if (lastHueChange.sat != null) {
443                         c.sat = lastHueChange.sat;
444                     }
445                     // Although we can't set a colour temperature in OH
446                     // this keeps Alexa happy when asking to turn a light
447                     // to white.
448                     if (lastHueChange.ct != null) {
449                         c.ct = lastHueChange.ct;
450                     }
451                 }
452             } else if (lastCommand instanceof PercentType) {
453                 if (hueState instanceof HueStateBulb && itemState != null
454                         && lastCommand.equals(itemState.as(PercentType.class))) {
455                     if (lastHueChange.bri != null) {
456                         ((HueStateBulb) hueState).bri = lastHueChange.bri;
457                     }
458                 }
459             }
460         }
461
462         return hueState;
463     }
464 }