Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mielecloud] Add channels energy and water consumption #14456

Merged
merged 13 commits into from
Mar 24, 2023
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