Skip to content

Commit

Permalink
[mqtt.homeassistant] Implement WaterHeater (openhab#17859)
Browse files Browse the repository at this point in the history
* [mqtt.homeassistant] Implement WaterHeater

Signed-off-by: Cody Cutrer <[email protected]>
  • Loading branch information
ccutrer authored Dec 7, 2024
1 parent f733c85 commit 5d89c9a
Show file tree
Hide file tree
Showing 16 changed files with 455 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider;
import org.openhab.binding.mqtt.homeassistant.internal.HomeAssistantJinjaFunctionLibrary;
import org.openhab.binding.mqtt.homeassistant.internal.handler.HomeAssistantThingHandler;
import org.openhab.core.i18n.UnitProvider;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
Expand All @@ -47,17 +48,19 @@ public class MqttThingHandlerFactory extends BaseThingHandlerFactory {
private final MqttChannelStateDescriptionProvider stateDescriptionProvider;
private final ChannelTypeRegistry channelTypeRegistry;
private final Jinjava jinjava = new Jinjava();
private final UnitProvider unitProvider;

private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
.of(MqttBindingConstants.HOMEASSISTANT_MQTT_THING).collect(Collectors.toSet());

@Activate
public MqttThingHandlerFactory(final @Reference MqttChannelTypeProvider typeProvider,
final @Reference MqttChannelStateDescriptionProvider stateDescriptionProvider,
final @Reference ChannelTypeRegistry channelTypeRegistry) {
final @Reference ChannelTypeRegistry channelTypeRegistry, final @Reference UnitProvider unitProvider) {
this.typeProvider = typeProvider;
this.stateDescriptionProvider = stateDescriptionProvider;
this.channelTypeRegistry = channelTypeRegistry;
this.unitProvider = unitProvider;

HomeAssistantJinjaFunctionLibrary.register(jinjava.getGlobalContext());
}
Expand All @@ -78,7 +81,7 @@ private boolean isHomeassistantDynamicType(ThingTypeUID thingTypeUID) {

if (supportsThingType(thingTypeUID)) {
return new HomeAssistantThingHandler(thing, typeProvider, stateDescriptionProvider, channelTypeRegistry,
jinjava, 10000, 2000);
jinjava, unitProvider, 10000, 2000);
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public enum ComponentChannelType {
SWITCH("ha-switch"),
TRIGGER("ha-trigger"),
HUMIDITY("ha-humidity"),
GPS_ACCURACY("ha-gps-accuracy");
GPS_ACCURACY("ha-gps-accuracy"),
TEMPERATURE("ha-temperature");

final ChannelTypeUID channelTypeUID;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.openhab.binding.mqtt.homeassistant.internal.component.ComponentFactory;
import org.openhab.binding.mqtt.homeassistant.internal.exception.ConfigurationException;
import org.openhab.binding.mqtt.homeassistant.internal.exception.UnsupportedComponentException;
import org.openhab.core.i18n.UnitProvider;
import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
import org.openhab.core.io.transport.mqtt.MqttMessageSubscriber;
import org.openhab.core.thing.ThingUID;
Expand Down Expand Up @@ -57,6 +58,7 @@ public class DiscoverComponents implements MqttMessageSubscriber {
protected final CompletableFuture<@Nullable Void> discoverFinishedFuture = new CompletableFuture<>();
private final Gson gson;
private final Jinjava jinjava;
private final UnitProvider unitProvider;

private @Nullable ScheduledFuture<?> stopDiscoveryFuture;
private WeakReference<@Nullable MqttBrokerConnection> connectionRef = new WeakReference<>(null);
Expand All @@ -82,12 +84,13 @@ public static interface ComponentDiscovered {
*/
public DiscoverComponents(ThingUID thingUID, ScheduledExecutorService scheduler,
ChannelStateUpdateListener channelStateUpdateListener, AvailabilityTracker tracker, Gson gson,
Jinjava jinjava, boolean newStyleChannels) {
Jinjava jinjava, UnitProvider unitProvider, boolean newStyleChannels) {
this.thingUID = thingUID;
this.scheduler = scheduler;
this.updateListener = channelStateUpdateListener;
this.gson = gson;
this.jinjava = jinjava;
this.unitProvider = unitProvider;
this.tracker = tracker;
this.newStyleChannels = newStyleChannels;
}
Expand All @@ -105,7 +108,7 @@ public void processMessage(String topic, byte[] payload) {
if (config.length() > 0) {
try {
component = ComponentFactory.createComponent(thingUID, haID, config, updateListener, tracker, scheduler,
gson, jinjava, newStyleChannels);
gson, jinjava, unitProvider, newStyleChannels);
component.setConfigSeen();

logger.trace("Found HomeAssistant component {}", haID);
Expand All @@ -119,8 +122,6 @@ public void processMessage(String topic, byte[] payload) {
} catch (ConfigurationException e) {
logger.warn("HomeAssistant discover error: invalid configuration of thing {} component {}: {}",
haID.objectID, haID.component, e.getMessage());
} catch (Exception e) {
logger.warn("HomeAssistant discover error: {}", e.getMessage());
}
} else {
if (discoveredListener != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package org.openhab.binding.mqtt.homeassistant.internal.component;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
Expand All @@ -21,6 +22,9 @@
import java.util.concurrent.ScheduledExecutorService;
import java.util.stream.Stream;

import javax.measure.Unit;
import javax.measure.quantity.Temperature;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mqtt.generic.AvailabilityTracker;
Expand All @@ -40,6 +44,8 @@
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AvailabilityMode;
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.Device;
import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.binding.generic.ChannelTransformation;
Expand All @@ -53,6 +59,7 @@
import org.openhab.core.types.StateDescription;

import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import com.hubspot.jinjava.Jinjava;

/**
Expand All @@ -64,6 +71,29 @@
*/
@NonNullByDefault
public abstract class AbstractComponent<C extends AbstractChannelConfiguration> {
public enum TemperatureUnit {
@SerializedName("C")
CELSIUS(SIUnits.CELSIUS, new BigDecimal("0.1")),
@SerializedName("F")
FAHRENHEIT(ImperialUnits.FAHRENHEIT, BigDecimal.ONE);

private final Unit<Temperature> unit;
private final BigDecimal defaultPrecision;

TemperatureUnit(Unit<Temperature> unit, BigDecimal defaultPrecision) {
this.unit = unit;
this.defaultPrecision = defaultPrecision;
}

public Unit<Temperature> getUnit() {
return unit;
}

public BigDecimal getDefaultPrecision() {
return defaultPrecision;
}
}

public static final String JSON_ATTRIBUTES_CHANNEL_ID = "json-attributes";

// Component location fields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import java.util.List;
import java.util.function.Predicate;

import javax.measure.Unit;
import javax.measure.quantity.Temperature;

import org.eclipse.jdt.annotation.NonNullByDefault;
Expand All @@ -32,7 +31,6 @@
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
Expand Down Expand Up @@ -69,29 +67,6 @@ public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
public static final String TEMPERATURE_LOW_CH_ID_DEPRECATED = "temperatureLow";
public static final String POWER_CH_ID = "power";

public enum TemperatureUnit {
@SerializedName("C")
CELSIUS(SIUnits.CELSIUS, new BigDecimal("0.1")),
@SerializedName("F")
FAHRENHEIT(ImperialUnits.FAHRENHEIT, BigDecimal.ONE);

private final Unit<Temperature> unit;
private final BigDecimal defaultPrecision;

TemperatureUnit(Unit<Temperature> unit, BigDecimal defaultPrecision) {
this.unit = unit;
this.defaultPrecision = defaultPrecision;
}

public Unit<Temperature> getUnit() {
return unit;
}

public BigDecimal getDefaultPrecision() {
return defaultPrecision;
}
}

private static final String ACTION_OFF = "off";
private static final State ACTION_OFF_STATE = new StringType(ACTION_OFF);
private static final List<String> ACTION_MODES = List.of(ACTION_OFF, "heating", "cooling", "drying", "idle", "fan");
Expand Down Expand Up @@ -241,7 +216,7 @@ static class ChannelConfiguration extends AbstractChannelConfiguration {
@SerializedName("min_temp")
protected @Nullable BigDecimal minTemp;
@SerializedName("temperature_unit")
protected TemperatureUnit temperatureUnit = TemperatureUnit.CELSIUS; // System unit by default
protected @Nullable TemperatureUnit temperatureUnit;
@SerializedName("temp_step")
protected BigDecimal tempStep = BigDecimal.ONE;
protected @Nullable BigDecimal precision;
Expand All @@ -252,8 +227,16 @@ static class ChannelConfiguration extends AbstractChannelConfiguration {
public Climate(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
super(componentConfiguration, ChannelConfiguration.class, newStyleChannels);

TemperatureUnit temperatureUnit = channelConfiguration.temperatureUnit;
if (channelConfiguration.temperatureUnit == null) {
if (ImperialUnits.FAHRENHEIT.equals(componentConfiguration.getUnitProvider().getUnit(Temperature.class))) {
temperatureUnit = TemperatureUnit.FAHRENHEIT;
} else {
temperatureUnit = TemperatureUnit.CELSIUS;
}
}
BigDecimal precision = channelConfiguration.precision != null ? channelConfiguration.precision
: channelConfiguration.temperatureUnit.getDefaultPrecision();
: temperatureUnit.getDefaultPrecision();
final ChannelStateUpdateListener updateListener = componentConfiguration.getUpdateListener();

ComponentChannel actionChannel = buildOptionalChannel(ACTION_CH_ID, ComponentChannelType.STRING,
Expand All @@ -277,9 +260,8 @@ ComponentChannelType.SWITCH, new OnOffValue(), updateListener, null,
null, channelConfiguration.currentHumidityTemplate, channelConfiguration.currentHumidityTopic, null);

buildOptionalChannel(newStyleChannels ? CURRENT_TEMPERATURE_CH_ID : CURRENT_TEMPERATURE_CH_ID_DEPRECATED,
ComponentChannelType.NUMBER,
new NumberValue(null, null, precision, channelConfiguration.temperatureUnit.getUnit()), updateListener,
null, null, channelConfiguration.currentTemperatureTemplate,
ComponentChannelType.TEMPERATURE, new NumberValue(null, null, precision, temperatureUnit.getUnit()),
updateListener, null, null, channelConfiguration.currentTemperatureTemplate,
channelConfiguration.currentTemperatureTopic, commandFilter);

buildOptionalChannel(newStyleChannels ? FAN_MODE_CH_ID : FAN_MODE_CH_ID_DEPRECATED, ComponentChannelType.STRING,
Expand Down Expand Up @@ -317,25 +299,25 @@ ComponentChannelType.SWITCH, new OnOffValue(), updateListener, null,
channelConfiguration.targetHumidityCommandTopic, channelConfiguration.targetHumidityStateTemplate,
channelConfiguration.targetHumidityStateTopic, commandFilter);

buildOptionalChannel(TEMPERATURE_CH_ID, ComponentChannelType.NUMBER,
buildOptionalChannel(TEMPERATURE_CH_ID, ComponentChannelType.TEMPERATURE,
new NumberValue(channelConfiguration.minTemp, channelConfiguration.maxTemp,
channelConfiguration.tempStep, channelConfiguration.temperatureUnit.getUnit()),
channelConfiguration.tempStep, temperatureUnit.getUnit()),
updateListener, channelConfiguration.temperatureCommandTemplate,
channelConfiguration.temperatureCommandTopic, channelConfiguration.temperatureStateTemplate,
channelConfiguration.temperatureStateTopic, commandFilter);

buildOptionalChannel(newStyleChannels ? TEMPERATURE_HIGH_CH_ID : TEMPERATURE_HIGH_CH_ID_DEPRECATED,
ComponentChannelType.NUMBER,
ComponentChannelType.TEMPERATURE,
new NumberValue(channelConfiguration.minTemp, channelConfiguration.maxTemp,
channelConfiguration.tempStep, channelConfiguration.temperatureUnit.getUnit()),
channelConfiguration.tempStep, temperatureUnit.getUnit()),
updateListener, channelConfiguration.temperatureHighCommandTemplate,
channelConfiguration.temperatureHighCommandTopic, channelConfiguration.temperatureHighStateTemplate,
channelConfiguration.temperatureHighStateTopic, commandFilter);

buildOptionalChannel(newStyleChannels ? TEMPERATURE_LOW_CH_ID : TEMPERATURE_LOW_CH_ID_DEPRECATED,
ComponentChannelType.NUMBER,
ComponentChannelType.TEMPERATURE,
new NumberValue(channelConfiguration.minTemp, channelConfiguration.maxTemp,
channelConfiguration.tempStep, channelConfiguration.temperatureUnit.getUnit()),
channelConfiguration.tempStep, temperatureUnit.getUnit()),
updateListener, channelConfiguration.temperatureLowCommandTemplate,
channelConfiguration.temperatureLowCommandTopic, channelConfiguration.temperatureLowStateTemplate,
channelConfiguration.temperatureLowStateTopic, commandFilter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
import org.openhab.binding.mqtt.homeassistant.internal.exception.ConfigurationException;
import org.openhab.binding.mqtt.homeassistant.internal.exception.UnsupportedComponentException;
import org.openhab.core.i18n.UnitProvider;
import org.openhab.core.thing.ThingUID;

import com.google.gson.Gson;
Expand All @@ -47,9 +48,10 @@ public class ComponentFactory {
*/
public static AbstractComponent<?> createComponent(ThingUID thingUID, HaID haID, String channelConfigurationJSON,
ChannelStateUpdateListener updateListener, AvailabilityTracker tracker, ScheduledExecutorService scheduler,
Gson gson, Jinjava jinjava, boolean newStyleChannels) throws ConfigurationException {
Gson gson, Jinjava jinjava, UnitProvider unitProvider, boolean newStyleChannels)
throws ConfigurationException {
ComponentConfiguration componentConfiguration = new ComponentConfiguration(thingUID, haID,
channelConfigurationJSON, gson, jinjava, updateListener, tracker, scheduler);
channelConfigurationJSON, gson, jinjava, updateListener, tracker, scheduler, unitProvider);
switch (haID.component) {
case "alarm_control_panel":
return new AlarmControlPanel(componentConfiguration, newStyleChannels);
Expand Down Expand Up @@ -97,6 +99,8 @@ public static AbstractComponent<?> createComponent(ThingUID thingUID, HaID haID,
return new Vacuum(componentConfiguration, newStyleChannels);
case "valve":
return new Valve(componentConfiguration, newStyleChannels);
case "water_heater":
return new WaterHeater(componentConfiguration, newStyleChannels);
default:
throw new UnsupportedComponentException("Component '" + haID + "' is unsupported!");
}
Expand All @@ -111,6 +115,7 @@ protected static class ComponentConfiguration {
private final Gson gson;
private final Jinjava jinjava;
private final ScheduledExecutorService scheduler;
private final UnitProvider unitProvider;

/**
* Provide a thingUID and HomeAssistant topic ID to determine the channel group UID and type.
Expand All @@ -122,7 +127,7 @@ protected static class ComponentConfiguration {
*/
protected ComponentConfiguration(ThingUID thingUID, HaID haID, String configJSON, Gson gson, Jinjava jinjava,
ChannelStateUpdateListener updateListener, AvailabilityTracker tracker,
ScheduledExecutorService scheduler) {
ScheduledExecutorService scheduler, UnitProvider unitProvider) {
this.thingUID = thingUID;
this.haID = haID;
this.configJSON = configJSON;
Expand All @@ -131,6 +136,7 @@ protected ComponentConfiguration(ThingUID thingUID, HaID haID, String configJSON
this.updateListener = updateListener;
this.tracker = tracker;
this.scheduler = scheduler;
this.unitProvider = unitProvider;
}

public ThingUID getThingUID() {
Expand All @@ -157,6 +163,10 @@ public Jinjava getJinjava() {
return jinjava;
}

public UnitProvider getUnitProvider() {
return unitProvider;
}

public AvailabilityTracker getTracker() {
return tracker;
}
Expand Down
Loading

0 comments on commit 5d89c9a

Please sign in to comment.