Skip to content

Commit

Permalink
[mielecloud] Add channels energy and water consumption (#14456)
Browse files Browse the repository at this point in the history
* Add POJOs for ecoFeedback from Miele REST API
* DeviceState offers  water and energy consumption
* Convert Quantity to State for channel population
* Add eco feedback channels to devices
* Fix item types and categories
* Add update instructions for eco feedback channels

Signed-off-by: Björn Lange <[email protected]>
  • Loading branch information
BjoernLange authored Mar 24, 2023
1 parent 983efd7 commit fbcb412
Show file tree
Hide file tree
Showing 37 changed files with 1,133 additions and 41 deletions.
9 changes: 9 additions & 0 deletions bundles/org.openhab.binding.mielecloud/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ Channel ID and channel type ID match unless noted.
| plate_power_step_raw | Number | The raw power level of the heating plate. | Yes |
| door_state | Switch | Indicates if the door of the device is open. | Yes |
| door_alarm | Switch | Indicates if the door alarm of the device is active. | Yes |
| water_consumption_current | Number | The amount of water used by the current running program up to the present moment. | Yes |
| energy_consumption_current | Number | The amount of energy used by the current running program up to the present moment. | Yes |
| battery_level | Number | The battery level of the robotic vacuum cleaner. | Yes |

### Coffee System
Expand Down Expand Up @@ -215,6 +217,8 @@ Channel ID and channel type ID match unless noted.
- error_state
- info_state
- door_state
- water_consumption_current
- energy_consumption_current

### Tumble Dryer

Expand Down Expand Up @@ -242,6 +246,7 @@ Channel ID and channel type ID match unless noted.
- light_switch
- light_can_be_controlled
- door_state
- energy_consumption_current

### Freezer

Expand Down Expand Up @@ -387,6 +392,8 @@ Channel ID and channel type ID match unless noted.
- light_switch
- light_can_be_controlled
- door_state
- water_consumption_current
- energy_consumption_current

### Washing Machine

Expand Down Expand Up @@ -415,6 +422,8 @@ Channel ID and channel type ID match unless noted.
- light_switch
- light_can_be_controlled
- door_state
- water_consumption_current
- energy_consumption_current

### Wine Storage

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
* @author Björn Lange - Added locale config parameter, added i18n key collection
* @author Benjamin Bolte - Add pre-heat finished and plate step channels, door state and door alarm channels, info
* state channel and map signal flags from API
* @author Björn Lange - Add elapsed time channel, dish warmer thing, removed e-mail validation
* @author Björn Lange - Add elapsed time channel, dish warmer thing, removed e-mail validation, add eco feedback
*/
@NonNullByDefault
public final class MieleCloudBindingConstants {
Expand Down Expand Up @@ -214,6 +214,8 @@ private Channels() {
public static final String PLATE_6_POWER_STEP_RAW = "plate_6_power_step_raw";
public static final String DOOR_STATE = "door_state";
public static final String DOOR_ALARM = "door_alarm";
public static final String WATER_CONSUMPTION_CURRENT = "water_consumption_current";
public static final String ENERGY_CONSUMPTION_CURRENT = "energy_consumption_current";
public static final String BATTERY_LEVEL = "battery_level";
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
* @author Roland Edelhoff - Initial contribution
* @author Björn Lange - Add channel state wrappers
* @author Benjamin Bolte - Add info state channel and map signal flags from API
* @author Björn Lange - Add elapsed time channel
* @author Björn Lange - Add elapsed time, current water and energy consumption channels
*/
@NonNullByDefault
public class DishwasherDeviceThingHandler extends AbstractMieleThingHandler {
Expand Down Expand Up @@ -54,6 +54,8 @@ protected void updateDeviceState(DeviceChannelState device) {
updateState(channel(ERROR_STATE), device.getErrorState());
updateState(channel(INFO_STATE), device.getInfoState());
updateState(channel(DOOR_STATE), device.getDoorState());
updateState(channel(WATER_CONSUMPTION_CURRENT), device.getCurrentWaterConsumption());
updateState(channel(ENERGY_CONSUMPTION_CURRENT), device.getCurrentEnergyConsumption());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
* @author Roland Edelhoff - Initial contribution
* @author Björn Lange - Add channel state wrappers
* @author Benjamin Bolte - Add info state channel and map signal flags from API
* @author Björn Lange - Add elapsed time channel
* @author Björn Lange - Add elapsed time, current water and energy consumption channels
*/
@NonNullByDefault
public class DryerDeviceThingHandler extends AbstractMieleThingHandler {
Expand Down Expand Up @@ -57,6 +57,7 @@ protected void updateDeviceState(DeviceChannelState device) {
updateState(channel(INFO_STATE), device.getInfoState());
updateState(channel(LIGHT_SWITCH), device.getLightSwitch());
updateState(channel(DOOR_STATE), device.getDoorState());
updateState(channel(ENERGY_CONSUMPTION_CURRENT), device.getCurrentEnergyConsumption());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
* @author Roland Edelhoff - Initial contribution
* @author Björn Lange - Add channel state wrappers
* @author Benjamin Bolte - Add info state channel and map signal flags from API
* @author Björn Lange - Add elapsed time channel
* @author Björn Lange - Add elapsed time, current water and energy consumption channels
*/
@NonNullByDefault
public class WashingDeviceThingHandler extends AbstractMieleThingHandler {
Expand Down Expand Up @@ -58,6 +58,8 @@ protected void updateDeviceState(DeviceChannelState device) {
updateState(channel(INFO_STATE), device.getInfoState());
updateState(channel(LIGHT_SWITCH), device.getLightSwitch());
updateState(channel(DOOR_STATE), device.getDoorState());
updateState(channel(WATER_CONSUMPTION_CURRENT), device.getCurrentWaterConsumption());
updateState(channel(ENERGY_CONSUMPTION_CURRENT), device.getCurrentEnergyConsumption());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,21 @@
package org.openhab.binding.mielecloud.internal.handler.channel;

import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.Optional;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.mielecloud.internal.webservice.api.Quantity;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Utility class handling type conversions from Java types to channel types.
Expand All @@ -31,6 +36,8 @@
*/
@NonNullByDefault
public final class ChannelTypeUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelTypeUtil.class);

private ChannelTypeUtil() {
throw new IllegalStateException("ChannelTypeUtil cannot be instantiated.");
}
Expand Down Expand Up @@ -71,4 +78,52 @@ public static State intToTemperatureState(Optional<Integer> value) {
// The Miele 3rd Party API always provides temperatures in °C (even if the device uses another unit).
return value.map(v -> (State) new QuantityType<>(v, SIUnits.CELSIUS)).orElse(UnDefType.UNDEF);
}

/**
* Converts an {@link Optional} of {@link Quantity} to {@link State}.
*/
public static State quantityToState(Optional<Quantity> value) {
return value.flatMap(ChannelTypeUtil::formatQuantity).flatMap(ChannelTypeUtil::parseQuantityType)
.orElse(UnDefType.UNDEF);
}

/**
* Formats the quantity as "value unit" with the given locale.
*
* @param locale The locale to format with.
* @return An {@link Optional} containing the formatted quantity value or an empty {@link Optional} if formatting
* for the given locale failed.
*/
private static Optional<String> formatQuantity(Quantity quantity) {
double value = quantity.getValue();
try {
var formatted = NumberFormat.getInstance(Locale.ENGLISH).format(value);

var unit = quantity.getUnit();
if (unit.isPresent()) {
formatted = formatted + " " + unit.get();
}

return Optional.of(formatted);
} catch (ArithmeticException e) {
LOGGER.warn("Failed to format {}", value, e);
return Optional.empty();
}
}

/**
* Parses a previously formatted {@link Quantity} into a {@link State}.
*
* @param value The quantity value formatted as "value unit".
* @return An {@link Optional} containing the parsed {@link State} or an empty {@link Optional} if the quantity
* including unit could not be parsed.
*/
private static Optional<State> parseQuantityType(String value) {
try {
return Optional.of((State) new QuantityType<>(value));
} catch (IllegalArgumentException e) {
LOGGER.warn("Failed to convert {} to quantity: {}", value, e.getMessage(), e);
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
* @author Björn Lange - Initial contribution
* @author Benjamin Bolte - Add pre-heat finished, plate step, door state, door alarm and info state channel and map
* signal flags from API
* @author Björn Lange - Add elapsed time channel, dish warmer and robotic vacuum cleaner thing
* @author Björn Lange - Add elapsed time channel, dish warmer and robotic vacuum cleaner thing, eco feedback
*/
@NonNullByDefault
public final class DeviceChannelState {
Expand Down Expand Up @@ -185,6 +185,14 @@ public State getSpinningSpeedRaw() {
return ChannelTypeUtil.intToState(device.getSpinningSpeedRaw());
}

public State getCurrentWaterConsumption() {
return ChannelTypeUtil.quantityToState(device.getCurrentWaterConsumption());
}

public State getCurrentEnergyConsumption() {
return ChannelTypeUtil.quantityToState(device.getCurrentEnergyConsumption());
}

public State getBatteryLevel() {
return ChannelTypeUtil.intToState(device.getBatteryLevel());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceIdentLabel;
import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceType;
import org.openhab.binding.mielecloud.internal.webservice.api.json.DryingStep;
import org.openhab.binding.mielecloud.internal.webservice.api.json.EcoFeedback;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Ident;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Light;
import org.openhab.binding.mielecloud.internal.webservice.api.json.PlateStep;
Expand All @@ -43,7 +44,7 @@
* @author Björn Lange - Introduced null handling
* @author Benjamin Bolte - Add pre-heat finished, plate step, door state, door alarm, info state channel and map signal
* flags from API
* @author Björn Lange - Add elapsed time channel, dish warmer and robotic vacuum cleaner things
* @author Björn Lange - Add elapsed time channel, dish warmer and robotic vacuum cleaner things, eco feedback
*/
@NonNullByDefault
public class DeviceState {
Expand Down Expand Up @@ -458,6 +459,36 @@ public Optional<Boolean> getDoorAlarm() {
return Optional.of(doorState.get() && failure.get());
}

/**
* Gets the amount of water consumed since the currently running program started.
*
* @return The amount of water consumed since the currently running program started.
*/
public Optional<Quantity> getCurrentWaterConsumption() {
if (deviceIsInOffState()) {
return Optional.empty();
}

return device.flatMap(Device::getState).flatMap(State::getEcoFeedback)
.flatMap(EcoFeedback::getCurrentWaterConsumption).flatMap(consumption -> consumption.getValue()
.map(value -> new Quantity(value, consumption.getUnit().orElse(null))));
}

/**
* Gets the amount of energy consumed since the currently running program started.
*
* @return The amount of energy consumed since the currently running program started.
*/
public Optional<Quantity> getCurrentEnergyConsumption() {
if (deviceIsInOffState()) {
return Optional.empty();
}

return device.flatMap(Device::getState).flatMap(State::getEcoFeedback)
.flatMap(EcoFeedback::getCurrentEnergyConsumption).flatMap(consumption -> consumption.getValue()
.map(value -> new Quantity(value, consumption.getUnit().orElse(null))));
}

/**
* Gets the battery level.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/

package org.openhab.binding.mielecloud.internal.webservice.api;

import java.util.Objects;
import java.util.Optional;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

/**
* A physical quantity as obtained from the Miele REST API.
*
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class Quantity {
double value;
Optional<String> unit;

public Quantity(double value, @Nullable String unit) {
this.value = value;
this.unit = Optional.ofNullable(unit);
}

public double getValue() {
return value;
}

public Optional<String> getUnit() {
return unit;
}

@Override
public int hashCode() {
return Objects.hash(value, unit);
}

@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Quantity other = (Quantity) obj;
return Double.doubleToLongBits(value) == Double.doubleToLongBits(other.value)
&& Objects.equals(unit, other.unit);
}

@Override
public String toString() {
return "Quantity [value=" + value + ", unit=" + unit + "]";
}
}
Loading

0 comments on commit fbcb412

Please sign in to comment.