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