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.mielecloud.internal.webservice.api;
15 import java.util.List;
16 import java.util.Objects;
17 import java.util.Optional;
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;
40 * This immutable class provides methods to extract the device state information in a comfortable way.
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
46 * @author Björn Lange - Add elapsed time channel, dish warmer and robotic vacuum cleaner things
49 public class DeviceState {
51 private final String deviceIdentifier;
53 private final Optional<Device> device;
55 public DeviceState(String deviceIdentifier, @Nullable Device device) {
56 this.deviceIdentifier = deviceIdentifier;
57 this.device = Optional.ofNullable(device);
61 * Gets the unique identifier for this device.
63 * @return The unique identifier for this device.
65 public String getDeviceIdentifier() {
66 return deviceIdentifier;
70 * Gets the main operation status of the device.
72 * @return The main operation status of the device.
74 public Optional<String> getStatus() {
75 return device.flatMap(Device::getState).flatMap(State::getStatus).flatMap(Status::getValueLocalized);
79 * Gets the raw main operation status of the device.
81 * @return The raw main operation status of the device.
83 public Optional<Integer> getStatusRaw() {
84 return device.flatMap(Device::getState).flatMap(State::getStatus).flatMap(Status::getValueRaw);
88 * Gets the raw operation status of the device parsed to a {@link StateType}.
90 * @return The raw operation status of the device parsed to a {@link StateType}.
92 public Optional<StateType> getStateType() {
93 return device.flatMap(Device::getState).flatMap(State::getStatus).flatMap(Status::getValueRaw)
94 .flatMap(StateType::fromCode);
98 * Gets the currently selected program type of the device.
100 * @return The currently selected program type of the device.
102 public Optional<String> getSelectedProgram() {
103 if (deviceIsInOffState()) {
104 return Optional.empty();
106 return device.flatMap(Device::getState).flatMap(State::getProgramId).flatMap(ProgramId::getValueLocalized);
110 * Gets the selected program ID.
112 * @return The selected program ID.
114 public Optional<Long> getSelectedProgramId() {
115 if (deviceIsInOffState()) {
116 return Optional.empty();
118 return device.flatMap(Device::getState).flatMap(State::getProgramId).flatMap(ProgramId::getValueRaw);
122 * Gets the currently active phase of the active program.
124 * @return The currently active phase of the active program.
126 public Optional<String> getProgramPhase() {
127 if (deviceIsInOffState()) {
128 return Optional.empty();
130 return device.flatMap(Device::getState).flatMap(State::getProgramPhase)
131 .flatMap(ProgramPhase::getValueLocalized);
135 * Gets the currently active raw phase of the active program.
137 * @return The currently active raw phase of the active program.
139 public Optional<Integer> getProgramPhaseRaw() {
140 if (deviceIsInOffState()) {
141 return Optional.empty();
143 return device.flatMap(Device::getState).flatMap(State::getProgramPhase).flatMap(ProgramPhase::getValueRaw);
147 * Gets the currently selected drying step.
149 * @return The currently selected drying step.
151 public Optional<String> getDryingTarget() {
152 if (deviceIsInOffState()) {
153 return Optional.empty();
155 return device.flatMap(Device::getState).flatMap(State::getDryingStep).flatMap(DryingStep::getValueLocalized);
159 * Gets the currently selected raw drying step.
161 * @return The currently selected raw drying step.
163 public Optional<Integer> getDryingTargetRaw() {
164 if (deviceIsInOffState()) {
165 return Optional.empty();
167 return device.flatMap(Device::getState).flatMap(State::getDryingStep).flatMap(DryingStep::getValueRaw);
171 * Calculates if pre-heating the oven has finished.
173 * @return Whether pre-heating the oven has finished.
175 public Optional<Boolean> hasPreHeatFinished() {
176 if (deviceIsInOffState()) {
177 return Optional.empty();
180 Optional<Integer> targetTemperature = getTargetTemperature(0);
181 Optional<Integer> currentTemperature = getTemperature(0);
183 if (!targetTemperature.isPresent() || !currentTemperature.isPresent()) {
184 return Optional.empty();
187 return Optional.of(isInState(StateType.RUNNING) && currentTemperature.get() >= targetTemperature.get());
191 * Gets the target temperature with the given index.
193 * @return The target temperature with the given index.
195 public Optional<Integer> getTargetTemperature(int index) {
196 if (deviceIsInOffState()) {
197 return Optional.empty();
199 return device.flatMap(Device::getState).map(State::getTargetTemperature).flatMap(l -> getOrNull(l, index))
200 .flatMap(Temperature::getValueLocalized);
204 * Gets the current temperature of the device for the given index.
206 * @param index The index of the device zone for which the temperature shall be obtained.
207 * @return The target temperature if available.
209 public Optional<Integer> getTemperature(int index) {
210 if (deviceIsInOffState()) {
211 return Optional.empty();
214 return device.flatMap(Device::getState).map(State::getTemperature).flatMap(l -> getOrNull(l, index))
215 .flatMap(Temperature::getValueLocalized);
219 * Gets the remaining time of the active program.
221 * @return The remaining time in seconds.
223 public Optional<Integer> getRemainingTime() {
224 if (deviceIsInOffState()) {
225 return Optional.empty();
227 return device.flatMap(Device::getState).flatMap(State::getRemainingTime).flatMap(this::toSeconds);
231 * Gets the elapsed time of the active program.
233 * @return The elapsed time in seconds.
235 public Optional<Integer> getElapsedTime() {
236 if (deviceIsInOffState()) {
237 return Optional.empty();
239 return device.flatMap(Device::getState).flatMap(State::getElapsedTime).flatMap(this::toSeconds);
243 * Gets the relative start time of the active program.
245 * @return The delayed start time in seconds.
247 public Optional<Integer> getStartTime() {
248 if (deviceIsInOffState()) {
249 return Optional.empty();
251 return device.flatMap(Device::getState).flatMap(State::getStartTime).flatMap(this::toSeconds);
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.
258 * @return Whether the device can be remote controlled.
260 public Optional<Boolean> isRemoteControlEnabled() {
261 return device.flatMap(Device::getState).flatMap(State::getRemoteEnable)
262 .flatMap(RemoteEnable::getFullRemoteControl);
266 * Calculates the program process.
268 * @return The progress of the active program in percent.
270 public Optional<Integer> getProgress() {
271 if (deviceIsInOffState()) {
272 return Optional.empty();
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);
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));
284 return Optional.empty();
288 private Optional<Integer> toSeconds(List<Integer> time) {
289 if (time.size() != 2) {
290 return Optional.empty();
292 return Optional.of((time.get(0) * 60 + time.get(1)) * 60);
296 * Gets the spinning speed.
298 * @return The spinning speed.
300 public Optional<String> getSpinningSpeed() {
301 if (deviceIsInOffState()) {
302 return Optional.empty();
304 return device.flatMap(Device::getState).flatMap(State::getSpinningSpeed).flatMap(SpinningSpeed::getValueRaw)
305 .map(String::valueOf);
309 * Gets the raw spinning speed.
311 * @return The raw spinning speed.
313 public Optional<Integer> getSpinningSpeedRaw() {
314 if (deviceIsInOffState()) {
315 return Optional.empty();
317 return device.flatMap(Device::getState).flatMap(State::getSpinningSpeed).flatMap(SpinningSpeed::getValueRaw);
321 * Gets the ventilation step.
323 * @return The ventilation step.
325 public Optional<String> getVentilationStep() {
326 if (deviceIsInOffState()) {
327 return Optional.empty();
329 return device.flatMap(Device::getState).flatMap(State::getVentilationStep)
330 .flatMap(VentilationStep::getValueLocalized).map(Object::toString);
334 * Gets the raw ventilation step.
336 * @return The raw ventilation step.
338 public Optional<Integer> getVentilationStepRaw() {
339 if (deviceIsInOffState()) {
340 return Optional.empty();
342 return device.flatMap(Device::getState).flatMap(State::getVentilationStep)
343 .flatMap(VentilationStep::getValueRaw);
347 * Gets the plate power step of the device for the given index.
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.
352 public Optional<String> getPlateStep(int index) {
353 if (deviceIsInOffState()) {
354 return Optional.empty();
356 return device.flatMap(Device::getState).map(State::getPlateStep).flatMap(l -> getOrNull(l, index))
357 .flatMap(PlateStep::getValueLocalized);
361 * Gets the raw plate power step of the device for the given index.
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.
366 public Optional<Integer> getPlateStepRaw(int index) {
367 if (deviceIsInOffState()) {
368 return Optional.empty();
370 return device.flatMap(Device::getState).map(State::getPlateStep).flatMap(l -> getOrNull(l, index))
371 .flatMap(PlateStep::getValueRaw);
375 * Gets the number of available plate steps.
377 * @return The number of available plate steps.
379 public Optional<Integer> getPlateStepCount() {
380 return device.flatMap(Device::getState).map(State::getPlateStep).map(List::size);
384 * Indicates if the device has an error that requires a user action.
386 * @return Whether the device has an error that requires a user action.
388 public boolean hasError() {
389 return isInState(StateType.FAILURE)
390 || device.flatMap(Device::getState).flatMap(State::getSignalFailure).orElse(false);
394 * Indicates if the device has a user information.
396 * @return Whether the device has a user information.
398 public boolean hasInfo() {
399 if (deviceIsInOffState()) {
402 return device.flatMap(Device::getState).flatMap(State::getSignalInfo).orElse(false);
406 * Gets the state of the light attached to the device.
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.
411 public Optional<Boolean> getLightState() {
412 if (deviceIsInOffState()) {
413 return Optional.empty();
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);
425 return Optional.empty();
429 * Gets the state of the door attached to the device.
431 * @return Whether the device door is open.
433 public Optional<Boolean> getDoorState() {
434 if (deviceIsInOffState()) {
435 return Optional.empty();
438 return device.flatMap(Device::getState).flatMap(State::getSignalDoor);
442 * Gets the state of the device's door alarm.
444 * @return Whether the device door alarm was triggered.
446 public Optional<Boolean> getDoorAlarm() {
447 if (deviceIsInOffState()) {
448 return Optional.empty();
451 Optional<Boolean> doorState = getDoorState();
452 Optional<Boolean> failure = device.flatMap(Device::getState).flatMap(State::getSignalFailure);
454 if (!doorState.isPresent() || !failure.isPresent()) {
455 return Optional.empty();
458 return Optional.of(doorState.get() && failure.get());
462 * Gets the battery level.
464 * @return The battery level.
466 public Optional<Integer> getBatteryLevel() {
467 if (deviceIsInOffState()) {
468 return Optional.empty();
471 return device.flatMap(Device::getState).flatMap(State::getBatteryLevel);
475 * Gets the device type.
477 * @return The device type as human readable value.
479 public Optional<String> getType() {
480 return device.flatMap(Device::getIdent).flatMap(Ident::getType).flatMap(Type::getValueLocalized)
481 .filter(type -> !type.isEmpty());
485 * Gets the raw device type.
487 * @return The raw device type.
489 public DeviceType getRawType() {
490 return device.flatMap(Device::getIdent).flatMap(Ident::getType).map(Type::getValueRaw)
491 .orElse(DeviceType.UNKNOWN);
495 * Gets the user-defined name of the device.
497 * @return The user-defined name of the device.
499 public Optional<String> getDeviceName() {
500 return device.flatMap(Device::getIdent).flatMap(Ident::getDeviceName).filter(name -> !name.isEmpty());
504 * Gets the fabrication (=serial) number of the device.
506 * @return The serial number of the device.
508 public Optional<String> getFabNumber() {
509 return device.flatMap(Device::getIdent).flatMap(Ident::getDeviceIdentLabel)
510 .flatMap(DeviceIdentLabel::getFabNumber).filter(fabNumber -> !fabNumber.isEmpty());
514 * Gets the tech type of the device.
516 * @return The tech type of the device.
518 public Optional<String> getTechType() {
519 return device.flatMap(Device::getIdent).flatMap(Ident::getDeviceIdentLabel)
520 .flatMap(DeviceIdentLabel::getTechType).filter(techType -> !techType.isEmpty());
523 private <T> Optional<T> getOrNull(List<T> list, int index) {
524 if (index < 0 || index >= list.size()) {
525 return Optional.empty();
528 return Optional.ofNullable(list.get(index));
531 private boolean deviceIsInOffState() {
532 return getStateType().map(StateType.OFF::equals).orElse(true);
535 public boolean isInState(StateType stateType) {
536 return getStateType().map(stateType::equals).orElse(false);
540 public int hashCode() {
541 return Objects.hash(device, deviceIdentifier);
545 public boolean equals(@Nullable Object obj) {
552 if (getClass() != obj.getClass()) {
555 DeviceState other = (DeviceState) obj;
556 return Objects.equals(device, other.device) && Objects.equals(deviceIdentifier, other.deviceIdentifier);