]> git.basschouten.com Git - openhab-addons.git/blob
deff78be92a7ee7a0170cc7795bdbf248519fa38
[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.mielecloud.internal.webservice.api;
14
15 import java.util.List;
16 import java.util.Objects;
17 import java.util.Optional;
18
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.binding.mielecloud.internal.webservice.api.json.Device;
22 import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceIdentLabel;
23 import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceType;
24 import org.openhab.binding.mielecloud.internal.webservice.api.json.DryingStep;
25 import org.openhab.binding.mielecloud.internal.webservice.api.json.EcoFeedback;
26 import org.openhab.binding.mielecloud.internal.webservice.api.json.Ident;
27 import org.openhab.binding.mielecloud.internal.webservice.api.json.Light;
28 import org.openhab.binding.mielecloud.internal.webservice.api.json.PlateStep;
29 import org.openhab.binding.mielecloud.internal.webservice.api.json.ProgramId;
30 import org.openhab.binding.mielecloud.internal.webservice.api.json.ProgramPhase;
31 import org.openhab.binding.mielecloud.internal.webservice.api.json.RemoteEnable;
32 import org.openhab.binding.mielecloud.internal.webservice.api.json.SpinningSpeed;
33 import org.openhab.binding.mielecloud.internal.webservice.api.json.State;
34 import org.openhab.binding.mielecloud.internal.webservice.api.json.StateType;
35 import org.openhab.binding.mielecloud.internal.webservice.api.json.Status;
36 import org.openhab.binding.mielecloud.internal.webservice.api.json.Temperature;
37 import org.openhab.binding.mielecloud.internal.webservice.api.json.Type;
38 import org.openhab.binding.mielecloud.internal.webservice.api.json.VentilationStep;
39
40 /**
41  * This immutable class provides methods to extract the device state information in a comfortable way.
42  *
43  * @author Roland Edelhoff - Initial contribution
44  * @author Björn Lange - Introduced null handling
45  * @author Benjamin Bolte - Add pre-heat finished, plate step, door state, door alarm, info state channel and map signal
46  *         flags from API
47  * @author Björn Lange - Add elapsed time channel, dish warmer and robotic vacuum cleaner things, eco feedback
48  */
49 @NonNullByDefault
50 public class DeviceState {
51
52     private final String deviceIdentifier;
53
54     private final Optional<Device> device;
55
56     public DeviceState(String deviceIdentifier, @Nullable Device device) {
57         this.deviceIdentifier = deviceIdentifier;
58         this.device = Optional.ofNullable(device);
59     }
60
61     /**
62      * Gets the unique identifier for this device.
63      *
64      * @return The unique identifier for this device.
65      */
66     public String getDeviceIdentifier() {
67         return deviceIdentifier;
68     }
69
70     /**
71      * Gets the main operation status of the device.
72      *
73      * @return The main operation status of the device.
74      */
75     public Optional<String> getStatus() {
76         return device.flatMap(Device::getState).flatMap(State::getStatus).flatMap(Status::getValueLocalized);
77     }
78
79     /**
80      * Gets the raw main operation status of the device.
81      *
82      * @return The raw main operation status of the device.
83      */
84     public Optional<Integer> getStatusRaw() {
85         return device.flatMap(Device::getState).flatMap(State::getStatus).flatMap(Status::getValueRaw);
86     }
87
88     /**
89      * Gets the raw operation status of the device parsed to a {@link StateType}.
90      *
91      * @return The raw operation status of the device parsed to a {@link StateType}.
92      */
93     public Optional<StateType> getStateType() {
94         return device.flatMap(Device::getState).flatMap(State::getStatus).flatMap(Status::getValueRaw)
95                 .flatMap(StateType::fromCode);
96     }
97
98     /**
99      * Gets the currently selected program type of the device.
100      *
101      * @return The currently selected program type of the device.
102      */
103     public Optional<String> getSelectedProgram() {
104         if (deviceIsInOffState()) {
105             return Optional.empty();
106         }
107         return device.flatMap(Device::getState).flatMap(State::getProgramId).flatMap(ProgramId::getValueLocalized);
108     }
109
110     /**
111      * Gets the selected program ID.
112      *
113      * @return The selected program ID.
114      */
115     public Optional<Long> getSelectedProgramId() {
116         if (deviceIsInOffState()) {
117             return Optional.empty();
118         }
119         return device.flatMap(Device::getState).flatMap(State::getProgramId).flatMap(ProgramId::getValueRaw);
120     }
121
122     /**
123      * Gets the currently active phase of the active program.
124      *
125      * @return The currently active phase of the active program.
126      */
127     public Optional<String> getProgramPhase() {
128         if (deviceIsInOffState()) {
129             return Optional.empty();
130         }
131         return device.flatMap(Device::getState).flatMap(State::getProgramPhase)
132                 .flatMap(ProgramPhase::getValueLocalized);
133     }
134
135     /**
136      * Gets the currently active raw phase of the active program.
137      *
138      * @return The currently active raw phase of the active program.
139      */
140     public Optional<Integer> getProgramPhaseRaw() {
141         if (deviceIsInOffState()) {
142             return Optional.empty();
143         }
144         return device.flatMap(Device::getState).flatMap(State::getProgramPhase).flatMap(ProgramPhase::getValueRaw);
145     }
146
147     /**
148      * Gets the currently selected drying step.
149      *
150      * @return The currently selected drying step.
151      */
152     public Optional<String> getDryingTarget() {
153         if (deviceIsInOffState()) {
154             return Optional.empty();
155         }
156         return device.flatMap(Device::getState).flatMap(State::getDryingStep).flatMap(DryingStep::getValueLocalized);
157     }
158
159     /**
160      * Gets the currently selected raw drying step.
161      *
162      * @return The currently selected raw drying step.
163      */
164     public Optional<Integer> getDryingTargetRaw() {
165         if (deviceIsInOffState()) {
166             return Optional.empty();
167         }
168         return device.flatMap(Device::getState).flatMap(State::getDryingStep).flatMap(DryingStep::getValueRaw);
169     }
170
171     /**
172      * Calculates if pre-heating the oven has finished.
173      *
174      * @return Whether pre-heating the oven has finished.
175      */
176     public Optional<Boolean> hasPreHeatFinished() {
177         if (deviceIsInOffState()) {
178             return Optional.empty();
179         }
180
181         Optional<Integer> targetTemperature = getTargetTemperature(0);
182         Optional<Integer> currentTemperature = getTemperature(0);
183
184         if (targetTemperature.isEmpty() || currentTemperature.isEmpty()) {
185             return Optional.empty();
186         }
187
188         return Optional.of(isInState(StateType.RUNNING) && currentTemperature.get() >= targetTemperature.get());
189     }
190
191     /**
192      * Gets the target temperature with the given index.
193      *
194      * @return The target temperature with the given index.
195      */
196     public Optional<Integer> getTargetTemperature(int index) {
197         if (deviceIsInOffState()) {
198             return Optional.empty();
199         }
200         return device.flatMap(Device::getState).map(State::getTargetTemperature).flatMap(l -> getOrNull(l, index))
201                 .flatMap(Temperature::getValueLocalized);
202     }
203
204     /**
205      * Gets the current temperature of the device for the given index.
206      *
207      * @param index The index of the device zone for which the temperature shall be obtained.
208      * @return The target temperature if available.
209      */
210     public Optional<Integer> getTemperature(int index) {
211         if (deviceIsInOffState()) {
212             return Optional.empty();
213         }
214
215         return device.flatMap(Device::getState).map(State::getTemperature).flatMap(l -> getOrNull(l, index))
216                 .flatMap(Temperature::getValueLocalized);
217     }
218
219     /**
220      * Gets the remaining time of the active program.
221      *
222      * @return The remaining time in seconds.
223      */
224     public Optional<Integer> getRemainingTime() {
225         if (deviceIsInOffState()) {
226             return Optional.empty();
227         }
228         return device.flatMap(Device::getState).flatMap(State::getRemainingTime).flatMap(this::toSeconds);
229     }
230
231     /**
232      * Gets the elapsed time of the active program.
233      *
234      * @return The elapsed time in seconds.
235      */
236     public Optional<Integer> getElapsedTime() {
237         if (deviceIsInOffState()) {
238             return Optional.empty();
239         }
240         return device.flatMap(Device::getState).flatMap(State::getElapsedTime).flatMap(this::toSeconds);
241     }
242
243     /**
244      * Gets the relative start time of the active program.
245      *
246      * @return The delayed start time in seconds.
247      */
248     public Optional<Integer> getStartTime() {
249         if (deviceIsInOffState()) {
250             return Optional.empty();
251         }
252         return device.flatMap(Device::getState).flatMap(State::getStartTime).flatMap(this::toSeconds);
253     }
254
255     /**
256      * Gets the "fullRemoteControl" state information of the device. If this flag is true ALL remote control actions
257      * of the device can be triggered.
258      *
259      * @return Whether the device can be remote controlled.
260      */
261     public Optional<Boolean> isRemoteControlEnabled() {
262         return device.flatMap(Device::getState).flatMap(State::getRemoteEnable)
263                 .flatMap(RemoteEnable::getFullRemoteControl);
264     }
265
266     /**
267      * Calculates the program process.
268      *
269      * @return The progress of the active program in percent.
270      */
271     public Optional<Integer> getProgress() {
272         if (deviceIsInOffState()) {
273             return Optional.empty();
274         }
275
276         Optional<Double> elapsedTime = device.flatMap(Device::getState).flatMap(State::getElapsedTime)
277                 .flatMap(this::toSeconds).map(Integer::doubleValue);
278         Optional<Double> remainingTime = device.flatMap(Device::getState).flatMap(State::getRemainingTime)
279                 .flatMap(this::toSeconds).map(Integer::doubleValue);
280
281         if (elapsedTime.isPresent() && remainingTime.isPresent()
282                 && (elapsedTime.get() != 0 || remainingTime.get() != 0)) {
283             return Optional.of((int) ((elapsedTime.get() / (elapsedTime.get() + remainingTime.get())) * 100.0));
284         } else {
285             return Optional.empty();
286         }
287     }
288
289     private Optional<Integer> toSeconds(List<Integer> time) {
290         if (time.size() != 2) {
291             return Optional.empty();
292         }
293         return Optional.of((time.get(0) * 60 + time.get(1)) * 60);
294     }
295
296     /**
297      * Gets the spinning speed.
298      *
299      * @return The spinning speed.
300      */
301     public Optional<String> getSpinningSpeed() {
302         if (deviceIsInOffState()) {
303             return Optional.empty();
304         }
305         return device.flatMap(Device::getState).flatMap(State::getSpinningSpeed).flatMap(SpinningSpeed::getValueRaw)
306                 .map(String::valueOf);
307     }
308
309     /**
310      * Gets the raw spinning speed.
311      *
312      * @return The raw spinning speed.
313      */
314     public Optional<Integer> getSpinningSpeedRaw() {
315         if (deviceIsInOffState()) {
316             return Optional.empty();
317         }
318         return device.flatMap(Device::getState).flatMap(State::getSpinningSpeed).flatMap(SpinningSpeed::getValueRaw);
319     }
320
321     /**
322      * Gets the ventilation step.
323      *
324      * @return The ventilation step.
325      */
326     public Optional<String> getVentilationStep() {
327         if (deviceIsInOffState()) {
328             return Optional.empty();
329         }
330         return device.flatMap(Device::getState).flatMap(State::getVentilationStep)
331                 .flatMap(VentilationStep::getValueLocalized).map(Object::toString);
332     }
333
334     /**
335      * Gets the raw ventilation step.
336      *
337      * @return The raw ventilation step.
338      */
339     public Optional<Integer> getVentilationStepRaw() {
340         if (deviceIsInOffState()) {
341             return Optional.empty();
342         }
343         return device.flatMap(Device::getState).flatMap(State::getVentilationStep)
344                 .flatMap(VentilationStep::getValueRaw);
345     }
346
347     /**
348      * Gets the plate power step of the device for the given index.
349      *
350      * @param index The index of the device plate for which the power step shall be obtained.
351      * @return The plate power step if available.
352      */
353     public Optional<String> getPlateStep(int index) {
354         if (deviceIsInOffState()) {
355             return Optional.empty();
356         }
357         return device.flatMap(Device::getState).map(State::getPlateStep).flatMap(l -> getOrNull(l, index))
358                 .flatMap(PlateStep::getValueLocalized);
359     }
360
361     /**
362      * Gets the raw plate power step of the device for the given index.
363      *
364      * @param index The index of the device plate for which the power step shall be obtained.
365      * @return The raw plate power step if available.
366      */
367     public Optional<Integer> getPlateStepRaw(int index) {
368         if (deviceIsInOffState()) {
369             return Optional.empty();
370         }
371         return device.flatMap(Device::getState).map(State::getPlateStep).flatMap(l -> getOrNull(l, index))
372                 .flatMap(PlateStep::getValueRaw);
373     }
374
375     /**
376      * Gets the number of available plate steps.
377      *
378      * @return The number of available plate steps.
379      */
380     public Optional<Integer> getPlateStepCount() {
381         return device.flatMap(Device::getState).map(State::getPlateStep).map(List::size);
382     }
383
384     /**
385      * Indicates if the device has an error that requires a user action.
386      *
387      * @return Whether the device has an error that requires a user action.
388      */
389     public boolean hasError() {
390         return isInState(StateType.FAILURE)
391                 || device.flatMap(Device::getState).flatMap(State::getSignalFailure).orElse(false);
392     }
393
394     /**
395      * Indicates if the device has a user information.
396      *
397      * @return Whether the device has a user information.
398      */
399     public boolean hasInfo() {
400         if (deviceIsInOffState()) {
401             return false;
402         }
403         return device.flatMap(Device::getState).flatMap(State::getSignalInfo).orElse(false);
404     }
405
406     /**
407      * Gets the state of the light attached to the device.
408      *
409      * @return An {@link Optional} with value {@code true} if the light is turned on, {@code false} if the light is
410      *         turned off or an empty {@link Optional} if light is not supported or no state is available.
411      */
412     public Optional<Boolean> getLightState() {
413         if (deviceIsInOffState()) {
414             return Optional.empty();
415         }
416
417         Optional<Light> light = device.flatMap(Device::getState).map(State::getLight);
418         if (light.isPresent()) {
419             if (light.get().equals(Light.ENABLE)) {
420                 return Optional.of(true);
421             } else if (light.get().equals(Light.DISABLE)) {
422                 return Optional.of(false);
423             }
424         }
425
426         return Optional.empty();
427     }
428
429     /**
430      * Gets the state of the door attached to the device.
431      *
432      * @return Whether the device door is open.
433      */
434     public Optional<Boolean> getDoorState() {
435         if (deviceIsInOffState()) {
436             return Optional.empty();
437         }
438
439         return device.flatMap(Device::getState).flatMap(State::getSignalDoor);
440     }
441
442     /**
443      * Gets the state of the device's door alarm.
444      *
445      * @return Whether the device door alarm was triggered.
446      */
447     public Optional<Boolean> getDoorAlarm() {
448         if (deviceIsInOffState()) {
449             return Optional.empty();
450         }
451
452         Optional<Boolean> doorState = getDoorState();
453         Optional<Boolean> failure = device.flatMap(Device::getState).flatMap(State::getSignalFailure);
454
455         if (doorState.isEmpty() || failure.isEmpty()) {
456             return Optional.empty();
457         }
458
459         return Optional.of(doorState.get() && failure.get());
460     }
461
462     /**
463      * Gets the amount of water consumed since the currently running program started.
464      *
465      * @return The amount of water consumed since the currently running program started.
466      */
467     public Optional<Quantity> getCurrentWaterConsumption() {
468         if (deviceIsInOffState()) {
469             return Optional.empty();
470         }
471
472         return device.flatMap(Device::getState).flatMap(State::getEcoFeedback)
473                 .flatMap(EcoFeedback::getCurrentWaterConsumption).flatMap(consumption -> consumption.getValue()
474                         .map(value -> new Quantity(value, consumption.getUnit().orElse(null))));
475     }
476
477     /**
478      * Gets the amount of energy consumed since the currently running program started.
479      *
480      * @return The amount of energy consumed since the currently running program started.
481      */
482     public Optional<Quantity> getCurrentEnergyConsumption() {
483         if (deviceIsInOffState()) {
484             return Optional.empty();
485         }
486
487         return device.flatMap(Device::getState).flatMap(State::getEcoFeedback)
488                 .flatMap(EcoFeedback::getCurrentEnergyConsumption).flatMap(consumption -> consumption.getValue()
489                         .map(value -> new Quantity(value, consumption.getUnit().orElse(null))));
490     }
491
492     /**
493      * Gets the battery level.
494      *
495      * @return The battery level.
496      */
497     public Optional<Integer> getBatteryLevel() {
498         if (deviceIsInOffState()) {
499             return Optional.empty();
500         }
501
502         return device.flatMap(Device::getState).flatMap(State::getBatteryLevel);
503     }
504
505     /**
506      * Gets the device type.
507      *
508      * @return The device type as human readable value.
509      */
510     public Optional<String> getType() {
511         return device.flatMap(Device::getIdent).flatMap(Ident::getType).flatMap(Type::getValueLocalized)
512                 .filter(type -> !type.isEmpty());
513     }
514
515     /**
516      * Gets the raw device type.
517      *
518      * @return The raw device type.
519      */
520     public DeviceType getRawType() {
521         return device.flatMap(Device::getIdent).flatMap(Ident::getType).map(Type::getValueRaw)
522                 .orElse(DeviceType.UNKNOWN);
523     }
524
525     /**
526      * Gets the user-defined name of the device.
527      *
528      * @return The user-defined name of the device.
529      */
530     public Optional<String> getDeviceName() {
531         return device.flatMap(Device::getIdent).flatMap(Ident::getDeviceName).filter(name -> !name.isEmpty());
532     }
533
534     /**
535      * Gets the fabrication (=serial) number of the device.
536      *
537      * @return The serial number of the device.
538      */
539     public Optional<String> getFabNumber() {
540         return device.flatMap(Device::getIdent).flatMap(Ident::getDeviceIdentLabel)
541                 .flatMap(DeviceIdentLabel::getFabNumber).filter(fabNumber -> !fabNumber.isEmpty());
542     }
543
544     /**
545      * Gets the tech type of the device.
546      *
547      * @return The tech type of the device.
548      */
549     public Optional<String> getTechType() {
550         return device.flatMap(Device::getIdent).flatMap(Ident::getDeviceIdentLabel)
551                 .flatMap(DeviceIdentLabel::getTechType).filter(techType -> !techType.isEmpty());
552     }
553
554     private <T> Optional<T> getOrNull(List<T> list, int index) {
555         if (index < 0 || index >= list.size()) {
556             return Optional.empty();
557         }
558
559         return Optional.ofNullable(list.get(index));
560     }
561
562     private boolean deviceIsInOffState() {
563         return getStateType().map(StateType.OFF::equals).orElse(true);
564     }
565
566     public boolean isInState(StateType stateType) {
567         return getStateType().map(stateType::equals).orElse(false);
568     }
569
570     @Override
571     public int hashCode() {
572         return Objects.hash(device, deviceIdentifier);
573     }
574
575     @Override
576     public boolean equals(@Nullable Object obj) {
577         if (this == obj) {
578             return true;
579         }
580         if (obj == null) {
581             return false;
582         }
583         if (getClass() != obj.getClass()) {
584             return false;
585         }
586         DeviceState other = (DeviceState) obj;
587         return Objects.equals(device, other.device) && Objects.equals(deviceIdentifier, other.deviceIdentifier);
588     }
589 }