From d434a9c227d30c7d5eab9db7652a0cb1ba2d90f2 Mon Sep 17 00:00:00 2001 From: antroids Date: Mon, 19 Apr 2021 12:37:09 +0300 Subject: [PATCH 01/23] MQTT.Homeassistant Climate support Signed-off-by: Anton Kharuzhy --- .../mqtt/homeassistant/internal/CChannel.java | 14 ++ .../internal/ComponentClimate.java | 223 +++++++++++++++++- .../internal/HAConfigurationTests.java | 40 ++++ .../configTS0601ClimateThermostat.json | 52 ++++ 4 files changed, 326 insertions(+), 3 deletions(-) create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configTS0601ClimateThermostat.json diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/CChannel.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/CChannel.java index cffc7027d25b0..9a2321f565736 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/CChannel.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/CChannel.java @@ -130,6 +130,7 @@ public static class Builder { private ChannelStateUpdateListener channelStateUpdateListener; private @Nullable String templateIn; + private @Nullable String templateOut; public Builder(AbstractComponent component, ComponentConfiguration componentConfiguration, String channelID, Value valueState, String label, ChannelStateUpdateListener channelStateUpdateListener) { @@ -173,9 +174,17 @@ public Builder commandTopic(@Nullable String command_topic, boolean retain) { } public Builder commandTopic(@Nullable String command_topic, boolean retain, int qos) { + return commandTopic(command_topic, retain, qos, null); + } + + public Builder commandTopic(@Nullable String command_topic, boolean retain, int qos, + @Nullable String template) { this.command_topic = command_topic; this.retain = retain; this.qos = qos; + if (command_topic != null && !command_topic.isBlank()) { + this.templateOut = template; + } return this; } @@ -233,6 +242,11 @@ public CChannel build(boolean addToComponent) { channelState .addTransformation(new ChannelStateTransformation(JINJA, templateIn, transformationProvider)); } + final String templateOut = this.templateOut; + if (templateOut != null && transformationProvider != null) { + channelState.addTransformationOut( + new ChannelStateTransformation(JINJA, templateOut, transformationProvider)); + } if (addToComponent) { component.channels.put(channelID, result); } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentClimate.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentClimate.java index 9e5b5d32cf0fd..197347af28603 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentClimate.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentClimate.java @@ -12,17 +12,52 @@ */ package org.openhab.binding.mqtt.homeassistant.internal; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener; +import org.openhab.binding.mqtt.generic.values.NumberValue; +import org.openhab.binding.mqtt.generic.values.OnOffValue; +import org.openhab.binding.mqtt.generic.values.TextValue; +import org.openhab.binding.mqtt.generic.values.Value; +import org.openhab.binding.mqtt.homeassistant.internal.listener.ChannelStateUpdateListenerProxy; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; /** * A MQTT climate component, following the https://www.home-assistant.io/components/climate.mqtt/ specification. * - * At the moment this only notifies the user that this feature is not yet supported. - * * @author David Graeff - Initial contribution */ @NonNullByDefault public class ComponentClimate extends AbstractComponent { + private static final String ACTION_CH_ID = "action"; + private static final String AUX_CH_ID = "aux"; + private static final String AWAY_MODE_CH_ID = "awayMode"; + private static final String CURRENT_TEMPERATURE_CH_ID = "currentTemperature"; + private static final String FAN_MODE_CH_ID = "fanMode"; + private static final String HOLD_CH_ID = "hold"; + private static final String MODE_CH_ID = "mode"; + private static final String SWING_CH_ID = "swing"; + private static final String TEMPERATURE_CH_ID = "temperature"; + private static final String TEMPERATURE_HIGH_CH_ID = "temperatureHigh"; + private static final String TEMPERATURE_LOW_CH_ID = "temperatureLow"; + private static final String POWER_CH_ID = "power"; + + private static final String CELSIUM = "C"; + private static final String FAHRENHEIT = "F"; + private static final float DEFAULT_CELSIUM_PRECISION = 0.1f; + private static final float DEFAULT_FAHRENHEIT_PRECISION = 1f; + + private static final String ACTION_OFF = "off"; + private static final State ACTION_OFF_STATE = new StringType(ACTION_OFF); + private static final List ACTION_MODES = List.of(ACTION_OFF, "heating", "cooling", "drying", "idle", "fan"); /** * Configuration class for MQTT component @@ -31,10 +66,192 @@ static class ChannelConfiguration extends BaseChannelConfiguration { ChannelConfiguration() { super("MQTT HVAC"); } + + protected @Nullable String action_template; + protected @Nullable String action_topic; + + protected @Nullable String aux_command_topic; + protected @Nullable String aux_state_template; + protected @Nullable String aux_state_topic; + + protected @Nullable String away_mode_command_topic; + protected @Nullable String away_mode_state_template; + protected @Nullable String away_mode_state_topic; + + protected @Nullable String current_temperature_template; + protected @Nullable String current_temperature_topic; + + protected @Nullable String fan_mode_command_template; + protected @Nullable String fan_mode_command_topic; + protected @Nullable String fan_mode_state_template; + protected @Nullable String fan_mode_state_topic; + protected List fan_modes = Arrays.asList("auto", "low", "medium", "high"); + + protected @Nullable String hold_command_template; + protected @Nullable String hold_command_topic; + protected @Nullable String hold_state_template; + protected @Nullable String hold_state_topic; + protected @Nullable List hold_modes; // Are there default modes? Now the channel will be ignored without + // hold modes. + + protected @Nullable String json_attributes_template; // Attributes are not supported yet + protected @Nullable String json_attributes_topic; + + protected @Nullable String mode_command_template; + protected @Nullable String mode_command_topic; + protected @Nullable String mode_state_template; + protected @Nullable String mode_state_topic; + protected List modes = Arrays.asList("auto", "off", "cool", "heat", "dry", "fan_only"); + + protected @Nullable String swing_command_template; + protected @Nullable String swing_command_topic; + protected @Nullable String swing_state_template; + protected @Nullable String swing_state_topic; + protected List swing_modes = Arrays.asList("on", "off"); + + protected @Nullable String temperature_command_template; + protected @Nullable String temperature_command_topic; + protected @Nullable String temperature_state_template; + protected @Nullable String temperature_state_topic; + + protected @Nullable String temperature_high_command_template; + protected @Nullable String temperature_high_command_topic; + protected @Nullable String temperature_high_state_template; + protected @Nullable String temperature_high_state_topic; + + protected @Nullable String temperature_low_command_template; + protected @Nullable String temperature_low_command_topic; + protected @Nullable String temperature_low_state_template; + protected @Nullable String temperature_low_state_topic; + + protected @Nullable String power_command_topic; + + protected Integer initial = 21; + protected @Nullable Float max_temp; + protected @Nullable Float min_temp; + protected String temperature_unit = CELSIUM; // System unit by default + protected Float temp_step = 1f; + protected @Nullable Float precision; + protected Boolean send_if_off = true; } public ComponentClimate(CFactory.ComponentConfiguration componentConfiguration) { super(componentConfiguration, ChannelConfiguration.class); - throw new UnsupportedOperationException("Component:Climate not supported yet"); + + @Nullable + BigDecimal minTemp = channelConfiguration.min_temp != null ? BigDecimal.valueOf(channelConfiguration.min_temp) + : null; + @Nullable + BigDecimal maxTemp = channelConfiguration.max_temp != null ? BigDecimal.valueOf(channelConfiguration.max_temp) + : null; + float precision = channelConfiguration.precision != null ? channelConfiguration.precision + : (FAHRENHEIT.equals(channelConfiguration.temperature_unit) ? DEFAULT_FAHRENHEIT_PRECISION + : DEFAULT_CELSIUM_PRECISION); + + @Nullable + CChannel actionChannel = buildOptionalChannel(ACTION_CH_ID, new TextValue(ACTION_MODES.toArray(new String[0])), + componentConfiguration.getUpdateListener(), null, null, channelConfiguration.action_template, + channelConfiguration.action_topic); + + ChannelStateUpdateListener updateListener = getListener(channelConfiguration.send_if_off, actionChannel, + componentConfiguration.getUpdateListener()); + + buildOptionalChannel(AUX_CH_ID, new OnOffValue(), updateListener, null, channelConfiguration.aux_command_topic, + channelConfiguration.aux_state_template, channelConfiguration.aux_state_topic); + + buildOptionalChannel(AWAY_MODE_CH_ID, new OnOffValue(), updateListener, null, + channelConfiguration.away_mode_command_topic, channelConfiguration.away_mode_state_template, + channelConfiguration.away_mode_state_topic); + + buildOptionalChannel(CURRENT_TEMPERATURE_CH_ID, + new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(precision), channelConfiguration.temperature_unit), + updateListener, null, null, channelConfiguration.current_temperature_template, + channelConfiguration.current_temperature_topic); + + buildOptionalChannel(FAN_MODE_CH_ID, new TextValue(channelConfiguration.fan_modes.toArray(new String[0])), + updateListener, channelConfiguration.fan_mode_command_template, + channelConfiguration.fan_mode_command_topic, channelConfiguration.fan_mode_state_template, + channelConfiguration.fan_mode_state_topic); + + if (channelConfiguration.hold_modes != null && !channelConfiguration.hold_modes.isEmpty()) { + buildOptionalChannel(HOLD_CH_ID, new TextValue(channelConfiguration.hold_modes.toArray(new String[0])), + updateListener, channelConfiguration.hold_command_template, channelConfiguration.hold_command_topic, + channelConfiguration.hold_state_template, channelConfiguration.hold_state_topic); + } + + buildOptionalChannel(MODE_CH_ID, new TextValue(channelConfiguration.modes.toArray(new String[0])), + updateListener, channelConfiguration.mode_command_template, channelConfiguration.mode_command_topic, + channelConfiguration.mode_state_template, channelConfiguration.mode_state_topic); + + buildOptionalChannel(SWING_CH_ID, new TextValue(channelConfiguration.swing_modes.toArray(new String[0])), + updateListener, channelConfiguration.swing_command_template, channelConfiguration.swing_command_topic, + channelConfiguration.swing_state_template, channelConfiguration.swing_state_topic); + + buildOptionalChannel(TEMPERATURE_CH_ID, + new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.temp_step), + channelConfiguration.temperature_unit), + updateListener, channelConfiguration.temperature_command_template, + channelConfiguration.temperature_command_topic, channelConfiguration.temperature_state_template, + channelConfiguration.temperature_state_topic); + + buildOptionalChannel(TEMPERATURE_HIGH_CH_ID, + new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.temp_step), + channelConfiguration.temperature_unit), + updateListener, channelConfiguration.temperature_high_command_template, + channelConfiguration.temperature_high_command_topic, + channelConfiguration.temperature_high_state_template, + channelConfiguration.temperature_high_state_topic); + + buildOptionalChannel(TEMPERATURE_LOW_CH_ID, + new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.temp_step), + channelConfiguration.temperature_unit), + updateListener, channelConfiguration.temperature_low_command_template, + channelConfiguration.temperature_low_command_topic, channelConfiguration.temperature_low_state_template, + channelConfiguration.temperature_low_state_topic); + + buildOptionalChannel(POWER_CH_ID, new OnOffValue(), updateListener, null, + channelConfiguration.power_command_topic, null, null); + } + + @Nullable + private CChannel buildOptionalChannel(String channelId, Value valueState, + ChannelStateUpdateListener channelStateUpdateListener, @Nullable String commandTemplate, + @Nullable String commandTopic, @Nullable String stateTemplate, @Nullable String stateTopic) { + if ((commandTopic != null && !commandTopic.isBlank()) || (stateTopic != null && !stateTopic.isBlank())) { + return buildChannel(channelId, valueState, channelConfiguration.name, channelStateUpdateListener) + .stateTopic(stateTopic, stateTemplate, channelConfiguration.value_template) + .commandTopic(commandTopic, channelConfiguration.retain, channelConfiguration.qos, commandTemplate) + .build(); + } + return null; + } + + private ChannelStateUpdateListener getListener(boolean sendIfOff, @Nullable CChannel actionChannel, + ChannelStateUpdateListener original) { + if (!sendIfOff && actionChannel != null) { + return new ChannelStateUpdateListenerProxy(original) { + @Override + public void postChannelCommand(@NonNull ChannelUID channelUID, @NonNull Command value) { + if (isOff()) { + return; // Do not send command if action is off + } + super.postChannelCommand(channelUID, value); + } + + @Override + public void triggerChannel(@NonNull ChannelUID channelUID, @NonNull String eventPayload) { + if (isOff()) { + return; // Do not trigger if action is off + } + super.triggerChannel(channelUID, eventPayload); + } + + private boolean isOff() { + Value val = actionChannel.getState().getCache(); + return ACTION_OFF_STATE.equals(val.getChannelState()); + } + }; + } + return original; } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/HAConfigurationTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/HAConfigurationTests.java index e25ccf92a49ef..b5b6168dbf605 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/HAConfigurationTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/HAConfigurationTests.java @@ -141,4 +141,44 @@ public void testDeviceSingleStringConfig() { assertThat(device.identifiers, is(Arrays.asList("A"))); } } + + @Test + public void testTS0601ClimateConfig() { + String json = readTestJson("configTS0601ClimateThermostat.json"); + ComponentClimate.ChannelConfiguration config = BaseChannelConfiguration.fromString(json, gson, + ComponentClimate.ChannelConfiguration.class); + assertThat(config.device, is(notNullValue())); + assertThat(config.device.identifiers, is(notNullValue())); + assertThat(config.device.identifiers.get(0), is("zigbee2mqtt_0x847127fffe11dd6a")); + assertThat(config.device.manufacturer, is("TuYa")); + assertThat(config.device.model, is("Radiator valve with thermostat (TS0601_thermostat)")); + assertThat(config.device.name, is("th1")); + assertThat(config.device.sw_version, is("Zigbee2MQTT 1.18.2")); + + assertThat(config.action_template, is("{% set values = {'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'} %}{{ values[value_json.running_state] }}")); + assertThat(config.action_topic, is("zigbee2mqtt/th1")); + assertThat(config.away_mode_command_topic, is("zigbee2mqtt/th1/set/away_mode")); + assertThat(config.away_mode_state_template, is("{{ value_json.away_mode }}")); + assertThat(config.away_mode_state_topic, is("zigbee2mqtt/th1")); + assertThat(config.current_temperature_template, is("{{ value_json.local_temperature }}")); + assertThat(config.current_temperature_topic, is("zigbee2mqtt/th1")); + assertThat(config.hold_command_topic, is("zigbee2mqtt/th1/set/preset")); + assertThat(config.hold_modes, is(List.of("schedule", "manual", "boost", "complex", "comfort", "eco"))); + assertThat(config.hold_state_template, is("{{ value_json.preset }}")); + assertThat(config.hold_state_topic, is("zigbee2mqtt/th1")); + assertThat(config.json_attributes_topic, is("zigbee2mqtt/th1")); + assertThat(config.max_temp, is(35f)); + assertThat(config.min_temp, is(5f)); + assertThat(config.mode_command_topic, is("zigbee2mqtt/th1/set/system_mode")); + assertThat(config.mode_state_template, is("{{ value_json.system_mode }}")); + assertThat(config.mode_state_topic, is("zigbee2mqtt/th1")); + assertThat(config.modes, is(List.of("heat", "auto", "off"))); + assertThat(config.name, is("th1")); + assertThat(config.temp_step, is(0.5f)); + assertThat(config.temperature_command_topic, is("zigbee2mqtt/th1/set/current_heating_setpoint")); + assertThat(config.temperature_state_template, is("{{ value_json.current_heating_setpoint }}")); + assertThat(config.temperature_state_topic, is("zigbee2mqtt/th1")); + assertThat(config.temperature_unit, is("C")); + assertThat(config.unique_id, is("0x847127fffe11dd6a_climate_zigbee2mqtt")); + } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configTS0601ClimateThermostat.json b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configTS0601ClimateThermostat.json new file mode 100644 index 0000000000000..4ab5d2e391739 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configTS0601ClimateThermostat.json @@ -0,0 +1,52 @@ +{ + "action_template": "{% set values = {'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'} %}{{ values[value_json.running_state] }}", + "action_topic": "zigbee2mqtt/th1", + "availability": [ + { + "topic": "zigbee2mqtt/bridge/state" + } + ], + "away_mode_command_topic": "zigbee2mqtt/th1/set/away_mode", + "away_mode_state_template": "{{ value_json.away_mode }}", + "away_mode_state_topic": "zigbee2mqtt/th1", + "current_temperature_template": "{{ value_json.local_temperature }}", + "current_temperature_topic": "zigbee2mqtt/th1", + "device": { + "identifiers": [ + "zigbee2mqtt_0x847127fffe11dd6a" + ], + "manufacturer": "TuYa", + "model": "Radiator valve with thermostat (TS0601_thermostat)", + "name": "th1", + "sw_version": "Zigbee2MQTT 1.18.2" + }, + "hold_command_topic": "zigbee2mqtt/th1/set/preset", + "hold_modes": [ + "schedule", + "manual", + "boost", + "complex", + "comfort", + "eco" + ], + "hold_state_template": "{{ value_json.preset }}", + "hold_state_topic": "zigbee2mqtt/th1", + "json_attributes_topic": "zigbee2mqtt/th1", + "max_temp": "35", + "min_temp": "5", + "mode_command_topic": "zigbee2mqtt/th1/set/system_mode", + "mode_state_template": "{{ value_json.system_mode }}", + "mode_state_topic": "zigbee2mqtt/th1", + "modes": [ + "heat", + "auto", + "off" + ], + "name": "th1", + "temp_step": 0.5, + "temperature_command_topic": "zigbee2mqtt/th1/set/current_heating_setpoint", + "temperature_state_template": "{{ value_json.current_heating_setpoint }}", + "temperature_state_topic": "zigbee2mqtt/th1", + "temperature_unit": "C", + "unique_id": "0x847127fffe11dd6a_climate_zigbee2mqtt" +} \ No newline at end of file From ce624483b775f7b4565884cb61c1f5246486636f Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Mon, 26 Apr 2021 12:40:25 +0300 Subject: [PATCH 02/23] MQTT.Homeassistant synthetic config test added Signed-off-by: Anton Kharuzhy --- .../internal/HAConfigurationTests.java | 65 +++++++++++++++++- .../homeassistant/internal/configClimate.json | 66 +++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configClimate.json diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/HAConfigurationTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/HAConfigurationTests.java index b5b6168dbf605..071c4ea1ac4c8 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/HAConfigurationTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/HAConfigurationTests.java @@ -155,7 +155,8 @@ public void testTS0601ClimateConfig() { assertThat(config.device.name, is("th1")); assertThat(config.device.sw_version, is("Zigbee2MQTT 1.18.2")); - assertThat(config.action_template, is("{% set values = {'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'} %}{{ values[value_json.running_state] }}")); + assertThat(config.action_template, is( + "{% set values = {'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'} %}{{ values[value_json.running_state] }}")); assertThat(config.action_topic, is("zigbee2mqtt/th1")); assertThat(config.away_mode_command_topic, is("zigbee2mqtt/th1/set/away_mode")); assertThat(config.away_mode_state_template, is("{{ value_json.away_mode }}")); @@ -180,5 +181,67 @@ public void testTS0601ClimateConfig() { assertThat(config.temperature_state_topic, is("zigbee2mqtt/th1")); assertThat(config.temperature_unit, is("C")); assertThat(config.unique_id, is("0x847127fffe11dd6a_climate_zigbee2mqtt")); + + assertThat(config.initial, is(21)); + assertThat(config.send_if_off, is(true)); + } + + @Test + public void testClimateConfig() { + String json = readTestJson("configClimate.json"); + ComponentClimate.ChannelConfiguration config = BaseChannelConfiguration.fromString(json, gson, + ComponentClimate.ChannelConfiguration.class); + assertThat(config.action_template, is("a")); + assertThat(config.action_topic, is("b")); + assertThat(config.aux_command_topic, is("c")); + assertThat(config.aux_state_template, is("d")); + assertThat(config.aux_state_topic, is("e")); + assertThat(config.away_mode_command_topic, is("f")); + assertThat(config.away_mode_state_template, is("g")); + assertThat(config.away_mode_state_topic, is("h")); + assertThat(config.current_temperature_template, is("i")); + assertThat(config.current_temperature_topic, is("j")); + assertThat(config.fan_mode_command_template, is("k")); + assertThat(config.fan_mode_command_topic, is("l")); + assertThat(config.fan_mode_state_template, is("m")); + assertThat(config.fan_mode_state_topic, is("n")); + assertThat(config.fan_modes, is(List.of("p1", "p2"))); + assertThat(config.hold_command_template, is("q")); + assertThat(config.hold_command_topic, is("r")); + assertThat(config.hold_state_template, is("s")); + assertThat(config.hold_state_topic, is("t")); + assertThat(config.hold_modes, is(List.of("u1", "u2", "u3"))); + assertThat(config.json_attributes_template, is("v")); + assertThat(config.json_attributes_topic, is("w")); + assertThat(config.mode_command_template, is("x")); + assertThat(config.mode_command_topic, is("y")); + assertThat(config.mode_state_template, is("z")); + assertThat(config.mode_state_topic, is("A")); + assertThat(config.modes, is(List.of("B1", "B2"))); + assertThat(config.swing_command_template, is("C")); + assertThat(config.swing_command_topic, is("D")); + assertThat(config.swing_state_template, is("E")); + assertThat(config.swing_state_topic, is("F")); + assertThat(config.swing_modes, is(List.of("G1"))); + assertThat(config.temperature_command_template, is("H")); + assertThat(config.temperature_command_topic, is("I")); + assertThat(config.temperature_state_template, is("J")); + assertThat(config.temperature_state_topic, is("K")); + assertThat(config.temperature_high_command_template, is("L")); + assertThat(config.temperature_high_command_topic, is("N")); + assertThat(config.temperature_high_state_template, is("O")); + assertThat(config.temperature_high_state_topic, is("P")); + assertThat(config.temperature_low_command_template, is("Q")); + assertThat(config.temperature_low_command_topic, is("R")); + assertThat(config.temperature_low_state_template, is("S")); + assertThat(config.temperature_low_state_topic, is("T")); + assertThat(config.power_command_topic, is("U")); + assertThat(config.initial, is(10)); + assertThat(config.max_temp, is(40f)); + assertThat(config.min_temp, is(0f)); + assertThat(config.temperature_unit, is("F")); + assertThat(config.temp_step, is(1f)); + assertThat(config.precision, is(0.5f)); + assertThat(config.send_if_off, is(false)); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configClimate.json b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configClimate.json new file mode 100644 index 0000000000000..671b0b65e01cf --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configClimate.json @@ -0,0 +1,66 @@ +{ + "action_template": "a", + "action_topic": "b", + "aux_command_topic": "c", + "aux_state_template": "d", + "aux_state_topic": "e", + "away_mode_command_topic": "f", + "away_mode_state_template": "g", + "away_mode_state_topic": "h", + "current_temperature_template": "i", + "current_temperature_topic": "j", + "fan_mode_command_template": "k", + "fan_mode_command_topic": "l", + "fan_mode_state_template": "m", + "fan_mode_state_topic": "n", + "fan_modes": [ + "p1", + "p2" + ], + "hold_command_template": "q", + "hold_command_topic": "r", + "hold_state_template": "s", + "hold_state_topic": "t", + "hold_modes": [ + "u1", + "u2", + "u3" + ], + "json_attributes_template": "v", + "json_attributes_topic": "w", + "mode_command_template": "x", + "mode_command_topic": "y", + "mode_state_template": "z", + "mode_state_topic": "A", + "modes": [ + "B1", + "B2" + ], + "swing_command_template": "C", + "swing_command_topic": "D", + "swing_state_template": "E", + "swing_state_topic": "F", + "swing_modes": [ + "G1" + ], + "temperature_command_template": "H", + "temperature_command_topic": "I", + "temperature_state_template": "J", + "temperature_state_topic": "K", + "temperature_high_command_template": "L", + "temperature_high_command_topic": "N", + "temperature_high_state_template": "O", + "temperature_high_state_topic": "P", + "temperature_low_command_template": "Q", + "temperature_low_command_topic": "R", + "temperature_low_state_template": "S", + "temperature_low_state_topic": "T", + "power_command_topic": "U", + "initial": "10", + "max_temp": "40", + "min_temp": "0", + "temperature_unit": "F", + "temp_step": "1", + "precision": "0.5", + "send_if_off": "false" +} \ No newline at end of file From 8b622dfcf530b71bfb37bc81dd8152add636def1 Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Thu, 29 Apr 2021 15:31:29 +0300 Subject: [PATCH 03/23] MQTT.Homeassistant refactoring Signed-off-by: Anton Kharuzhy --- .../{CChannel.java => ComponentChannel.java} | 52 +++--- .../internal/DiscoverComponents.java | 18 +- .../mqtt/homeassistant/internal/HaID.java | 11 +- .../internal/HandlerConfiguration.java | 2 +- .../{ => component}/AbstractComponent.java | 89 +++++---- .../AlarmControlPanel.java} | 29 +-- .../BinarySensor.java} | 13 +- .../Camera.java} | 11 +- .../Climate.java} | 27 +-- .../ComponentFactory.java} | 42 +++-- .../Cover.java} | 17 +- .../{ComponentFan.java => component/Fan.java} | 17 +- .../Light.java} | 40 +++-- .../Lock.java} | 17 +- .../Sensor.java} | 18 +- .../Switch.java} | 14 +- .../AbstractChannelConfiguration.java} | 169 +++++++++++------- .../internal/config/Availability.java | 37 ++++ .../internal/config/AvailabilityMode.java | 42 +++++ ...hannelConfigurationTypeAdapterFactory.java | 32 ++-- .../internal/config/Connection.java | 46 +++++ .../{ => config}/ConnectionDeserializer.java | 13 +- .../homeassistant/internal/config/Device.java | 70 ++++++++ .../ListOrStringDeserializer.java | 6 +- .../discovery/HomeAssistantDiscovery.java | 6 +- .../handler/HomeAssistantThingHandler.java | 28 +-- .../listener/ExpireUpdateStateListener.java | 2 +- .../listener/OffDelayUpdateStateListener.java | 3 +- .../{ => component}/HAConfigurationTests.java | 113 ++++++------ .../internal/{ => component}/configA.json | 0 .../internal/{ => component}/configB.json | 0 .../{ => component}/configClimate.json | 0 .../{ => component}/configDeviceList.json | 0 .../configDeviceSingleString.json | 0 .../internal/{ => component}/configFan.json | 0 .../configTS0601ClimateThermostat.json | 0 36 files changed, 646 insertions(+), 338 deletions(-) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/{CChannel.java => ComponentChannel.java} (86%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/{ => component}/AbstractComponent.java (66%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/{ComponentAlarmControlPanel.java => component/AlarmControlPanel.java} (74%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/{ComponentBinarySensor.java => component/BinarySensor.java} (83%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/{ComponentCamera.java => component/Camera.java} (73%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/{ComponentClimate.java => component/Climate.java} (92%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/{CFactory.java => component/ComponentFactory.java} (76%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/{ComponentCover.java => component/Cover.java} (73%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/{ComponentFan.java => component/Fan.java} (72%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/{ComponentLight.java => component/Light.java} (84%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/{ComponentLock.java => component/Lock.java} (72%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/{ComponentSensor.java => component/Sensor.java} (80%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/{ComponentSwitch.java => component/Switch.java} (82%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/{BaseChannelConfiguration.java => config/AbstractChannelConfiguration.java} (60%) create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Availability.java create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/AvailabilityMode.java rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/{ => config}/ChannelConfigurationTypeAdapterFactory.java (82%) create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Connection.java rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/{ => config}/ConnectionDeserializer.java (66%) create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Device.java rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/{ => config}/ListOrStringDeserializer.java (93%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/{ => component}/HAConfigurationTests.java (66%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/{ => component}/configA.json (100%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/{ => component}/configB.json (100%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/{ => component}/configClimate.json (100%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/{ => component}/configDeviceList.json (100%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/{ => component}/configDeviceSingleString.json (100%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/{ => component}/configFan.json (100%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/{ => component}/configTS0601ClimateThermostat.json (100%) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/CChannel.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java similarity index 86% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/CChannel.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java index 9a2321f565736..0ab22e71b2d25 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/CChannel.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java @@ -26,7 +26,7 @@ import org.openhab.binding.mqtt.generic.TransformationServiceProvider; import org.openhab.binding.mqtt.generic.values.Value; import org.openhab.binding.mqtt.homeassistant.generic.internal.MqttBindingConstants; -import org.openhab.binding.mqtt.homeassistant.internal.CFactory.ComponentConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.component.AbstractComponent; import org.openhab.core.config.core.Configuration; import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; import org.openhab.core.thing.Channel; @@ -55,7 +55,7 @@ * @author David Graeff - Initial contribution */ @NonNullByDefault -public class CChannel { +public class ComponentChannel { private static final String JINJA = "JINJA"; private final ChannelUID channelUID; @@ -65,7 +65,7 @@ public class CChannel { private final ChannelTypeUID channelTypeUID; private final ChannelStateUpdateListener channelStateUpdateListener; - private CChannel(ChannelUID channelUID, ChannelState channelState, Channel channel, ChannelType type, + private ComponentChannel(ChannelUID channelUID, ChannelState channelState, Channel channel, ChannelType type, ChannelTypeUID channelTypeUID, ChannelStateUpdateListener channelStateUpdateListener) { super(); this.channelUID = channelUID; @@ -117,25 +117,24 @@ public void resetState() { } public static class Builder { - private AbstractComponent component; - private ComponentConfiguration componentConfiguration; - private String channelID; - private Value valueState; - private String label; + private final AbstractComponent component; + private final String channelID; + private final Value valueState; + private final String label; + private final ChannelStateUpdateListener channelStateUpdateListener; + private @Nullable String state_topic; private @Nullable String command_topic; private boolean retain; private boolean trigger; private @Nullable Integer qos; - private ChannelStateUpdateListener channelStateUpdateListener; private @Nullable String templateIn; private @Nullable String templateOut; - public Builder(AbstractComponent component, ComponentConfiguration componentConfiguration, String channelID, - Value valueState, String label, ChannelStateUpdateListener channelStateUpdateListener) { + public Builder(AbstractComponent component, String channelID, Value valueState, String label, + ChannelStateUpdateListener channelStateUpdateListener) { this.component = component; - this.componentConfiguration = componentConfiguration; this.channelID = channelID; this.valueState = valueState; this.label = label; @@ -150,7 +149,8 @@ public Builder stateTopic(@Nullable String state_topic) { public Builder stateTopic(@Nullable String state_topic, @Nullable String... templates) { this.state_topic = state_topic; if (state_topic != null && !state_topic.isBlank()) { - for (String template : templates) { + for (@Nullable + String template : templates) { if (template != null && !template.isBlank()) { this.templateIn = template; break; @@ -162,9 +162,9 @@ public Builder stateTopic(@Nullable String state_topic, @Nullable String... temp /** * @deprecated use commandTopic(String, boolean, int) - * @param command_topic - * @param retain - * @return + * @param command_topic topic + * @param retain retain + * @return this */ @Deprecated public Builder commandTopic(@Nullable String command_topic, boolean retain) { @@ -193,18 +193,18 @@ public Builder trigger(boolean trigger) { return this; } - public CChannel build() { + public ComponentChannel build() { return build(true); } - public CChannel build(boolean addToComponent) { + public ComponentChannel build(boolean addToComponent) { ChannelUID channelUID; ChannelState channelState; Channel channel; ChannelType type; ChannelTypeUID channelTypeUID; - channelUID = new ChannelUID(component.channelGroupUID, channelID); + channelUID = new ChannelUID(component.getGroupUID(), channelID); channelTypeUID = new ChannelTypeUID(MqttBindingConstants.BINDING_ID, channelUID.getGroupId() + "_" + channelID); channelState = new ChannelState( @@ -212,6 +212,7 @@ public CChannel build(boolean addToComponent) { .withCommandTopic(command_topic).makeTrigger(trigger).build(), channelUID, valueState, channelStateUpdateListener); + @Nullable String localStateTopic = state_topic; if (localStateTopic == null || localStateTopic.isBlank() || this.trigger) { type = ChannelTypeBuilder.trigger(channelTypeUID, label) @@ -224,31 +225,32 @@ public CChannel build(boolean addToComponent) { } Configuration configuration = new Configuration(); - configuration.put("config", component.channelConfigurationJson); - component.haID.toConfig(configuration); + configuration.put("config", component.getChannelConfigurationJson()); + component.getHaID().toConfig(configuration); channel = ChannelBuilder.create(channelUID, channelState.getItemType()).withType(channelTypeUID) .withKind(type.getKind()).withLabel(label).withConfiguration(configuration).build(); - CChannel result = new CChannel(channelUID, channelState, channel, type, channelTypeUID, + ComponentChannel result = new ComponentChannel(channelUID, channelState, channel, type, channelTypeUID, channelStateUpdateListener); @Nullable - TransformationServiceProvider transformationProvider = componentConfiguration - .getTransformationServiceProvider(); + TransformationServiceProvider transformationProvider = component.getTransformationServiceProvider(); + @Nullable final String templateIn = this.templateIn; if (templateIn != null && transformationProvider != null) { channelState .addTransformation(new ChannelStateTransformation(JINJA, templateIn, transformationProvider)); } + @Nullable final String templateOut = this.templateOut; if (templateOut != null && transformationProvider != null) { channelState.addTransformationOut( new ChannelStateTransformation(JINJA, templateOut, transformationProvider)); } if (addToComponent) { - component.channels.put(channelID, result); + component.getChannelMap().put(channelID, result); } return result; } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java index 3e6794b58fed8..3523c3c32e9af 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java @@ -27,6 +27,8 @@ import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener; import org.openhab.binding.mqtt.generic.TransformationServiceProvider; import org.openhab.binding.mqtt.generic.utils.FutureCollector; +import org.openhab.binding.mqtt.homeassistant.internal.component.AbstractComponent; +import org.openhab.binding.mqtt.homeassistant.internal.component.ComponentFactory; import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; import org.openhab.core.io.transport.mqtt.MqttMessageSubscriber; import org.openhab.core.thing.ThingUID; @@ -55,7 +57,7 @@ public class DiscoverComponents implements MqttMessageSubscriber { private @Nullable ScheduledFuture stopDiscoveryFuture; private WeakReference<@Nullable MqttBrokerConnection> connectionRef = new WeakReference<>(null); - protected @NonNullByDefault({}) ComponentDiscovered discoveredListener; + protected @Nullable @NonNullByDefault({}) ComponentDiscovered discoveredListener; private int discoverTime; private Set topics = new HashSet<>(); @@ -93,11 +95,12 @@ public void processMessage(String topic, byte[] payload) { HaID haID = new HaID(topic); String config = new String(payload); + @Nullable AbstractComponent component = null; if (config.length() > 0) { - component = CFactory.createComponent(thingUID, haID, config, updateListener, tracker, scheduler, gson, - transformationServiceProvider); + component = ComponentFactory.createComponent(thingUID, haID, config, updateListener, tracker, scheduler, + gson, transformationServiceProvider); } if (component != null) { component.setConfigSeen(); @@ -122,9 +125,9 @@ public void processMessage(String topic, byte[] payload) { * @param connection A MQTT broker connection * @param discoverTime The time in milliseconds for the discovery to run. Can be 0 to disable the * timeout. - * You need to call {@link #stopDiscovery(MqttBrokerConnection)} at some + * You need to call {@link #stopDiscovery()} at some * point in that case. - * @param topicDescription Contains the object-id (=device id) and potentially a node-id as well. + * @param topicDescriptions Contains the object-id (=device id) and potentially a node-id as well. * @param componentsDiscoveredListener Listener for results * @return A future that completes normally after the given time in milliseconds or exceptionally on any error. * Completes immediately if the timeout is disabled. @@ -144,6 +147,7 @@ public void processMessage(String topic, byte[] payload) { } private void subscribeSuccess() { + @Nullable final MqttBrokerConnection connection = connectionRef.get(); // Set up a scheduled future that will stop the discovery after the given time if (connection != null && discoverTime > 0) { @@ -160,12 +164,14 @@ private void subscribeSuccess() { } private @Nullable Void subscribeFail(Throwable e) { + @Nullable final ScheduledFuture scheduledFuture = this.stopDiscoveryFuture; if (scheduledFuture != null) { // Cancel timeout scheduledFuture.cancel(false); this.stopDiscoveryFuture = null; } this.discoveredListener = null; + @Nullable final MqttBrokerConnection connection = connectionRef.get(); if (connection != null) { this.topics.parallelStream().forEach(t -> connection.unsubscribe(t, this)); @@ -177,8 +183,6 @@ private void subscribeSuccess() { /** * Stops an ongoing discovery or do nothing if no discovery is running. - * - * @param connection A MQTT broker connection */ public void stopDiscovery() { subscribeFail(new Throwable("Stopped")); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HaID.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HaID.java index c7f895cd4557e..2552bd3d860ee 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HaID.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HaID.java @@ -89,7 +89,7 @@ private HaID(String baseTopic, String objectID, String nodeID, String component) this.topic = createTopic(this); } - private static final String createTopic(HaID id) { + private static String createTopic(HaID id) { StringBuilder str = new StringBuilder(); str.append(id.baseTopic).append('/').append(id.component).append('/'); if (!id.nodeID.isBlank()) { @@ -104,8 +104,8 @@ private static final String createTopic(HaID id) { *

* objectid, nodeid, and component values are fetched from the configuration. * - * @param baseTopic - * @param config + * @param baseTopic base topic + * @param config config * @return newly created HaID */ public static HaID fromConfig(String baseTopic, Configuration config) { @@ -120,7 +120,7 @@ public static HaID fromConfig(String baseTopic, Configuration config) { *

* objectid, nodeid, and component values are added to the configuration. * - * @param config + * @param config config * @return the modified configuration */ public Configuration toConfig(Configuration config) { @@ -139,7 +139,7 @@ public Configuration toConfig(Configuration config) { * The component component in the resulting HaID will be set to +. * This enables the HaID to be used as an mqtt subscription topic. * - * @param config + * @param config config * @return newly created HaID */ public static Collection fromConfig(HandlerConfiguration config) { @@ -188,6 +188,7 @@ public String toShortTopic() { * @return group id */ public String getGroupId(@Nullable final String uniqueId) { + @Nullable String result = uniqueId; // the null test is only here so the compile knows, result is not null afterwards diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HandlerConfiguration.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HandlerConfiguration.java index 8c589592e4538..11b23ad71772a 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HandlerConfiguration.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HandlerConfiguration.java @@ -76,7 +76,7 @@ public HandlerConfiguration(String basetopic, List topics) { /** * Add the basetopic and objectid to the properties. * - * @param properties + * @param properties properties * @return the modified properties */ public > T appendToProperties(T properties) { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractComponent.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java similarity index 66% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractComponent.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java index 074549d7f8413..9e0573d22658a 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractComponent.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal; +package org.openhab.binding.mqtt.homeassistant.internal.component; import java.util.List; import java.util.Map; @@ -23,10 +23,14 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener; import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider; +import org.openhab.binding.mqtt.generic.TransformationServiceProvider; import org.openhab.binding.mqtt.generic.utils.FutureCollector; import org.openhab.binding.mqtt.generic.values.Value; import org.openhab.binding.mqtt.homeassistant.generic.internal.MqttBindingConstants; -import org.openhab.binding.mqtt.homeassistant.internal.CFactory.ComponentConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel; +import org.openhab.binding.mqtt.homeassistant.internal.HaID; +import org.openhab.binding.mqtt.homeassistant.internal.component.ComponentFactory.ComponentConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; import org.openhab.core.thing.ChannelGroupUID; import org.openhab.core.thing.type.ChannelDefinition; @@ -40,10 +44,10 @@ * It has a name and consists of multiple channels. * * @author David Graeff - Initial contribution - * @param Config class derived from {@link BaseChannelConfiguration} + * @param Config class derived from {@link AbstractChannelConfiguration} */ @NonNullByDefault -public abstract class AbstractComponent { +public abstract class AbstractComponent { // Component location fields private final ComponentConfiguration componentConfiguration; protected final ChannelGroupTypeUID channelGroupTypeUID; @@ -51,7 +55,7 @@ public abstract class AbstractComponent { protected final HaID haID; // Channels and configuration - protected final Map channels = new TreeMap<>(); + protected final Map channels = new TreeMap<>(); // The hash code ({@link String#hashCode()}) of the configuration string // Used to determine if a component has changed. protected final int configHash; @@ -61,14 +65,12 @@ public abstract class AbstractComponent { protected boolean configSeen; /** - * Provide a thingUID and HomeAssistant topic ID to determine the channel group UID and type. + * Creates component based on generic configuration and component configuration type. * - * @param thing A ThingUID - * @param haID A HomeAssistant topic ID - * @param configJson The configuration string - * @param gson A Gson instance + * @param componentConfiguration generic componentConfiguration with not parsed JSON config + * @param clazz target configuration type */ - public AbstractComponent(CFactory.ComponentConfiguration componentConfiguration, Class clazz) { + public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class clazz) { this.componentConfiguration = componentConfiguration; this.channelConfigurationJson = componentConfiguration.getConfigJSON(); @@ -77,24 +79,25 @@ public AbstractComponent(CFactory.ComponentConfiguration componentConfiguration, this.haID = componentConfiguration.getHaID(); - String groupId = this.haID.getGroupId(channelConfiguration.unique_id); + String groupId = this.haID.getGroupId(channelConfiguration.getUniqueId()); this.channelGroupTypeUID = new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, groupId); this.channelGroupUID = new ChannelGroupUID(componentConfiguration.getThingUID(), groupId); this.configSeen = false; - String availability_topic = this.channelConfiguration.availability_topic; + @Nullable + String availability_topic = this.channelConfiguration.getAvailabilityTopic(); if (availability_topic != null) { componentConfiguration.getTracker().addAvailabilityTopic(availability_topic, - this.channelConfiguration.payload_available, this.channelConfiguration.payload_not_available); + this.channelConfiguration.getPayloadAvailable(), + this.channelConfiguration.getPayloadNotAvailable()); } } - protected CChannel.Builder buildChannel(String channelID, Value valueState, String label, + protected ComponentChannel.Builder buildChannel(String channelID, Value valueState, String label, ChannelStateUpdateListener channelStateUpdateListener) { - return new CChannel.Builder(this, componentConfiguration, channelID, valueState, label, - channelStateUpdateListener); + return new ComponentChannel.Builder(this, channelID, valueState, label, channelStateUpdateListener); } public void setConfigSeen() { @@ -104,14 +107,15 @@ public void setConfigSeen() { /** * Subscribes to all state channels of the component and adds all channels to the provided channel type provider. * - * @param connection The connection - * @param channelStateUpdateListener A listener + * @param connection connection to the MQTT broker + * @param scheduler thing scheduler + * @param timeout channel subscription timeout * @return A future that completes as soon as all subscriptions have been performed. Completes exceptionally on * errors. */ public CompletableFuture<@Nullable Void> start(MqttBrokerConnection connection, ScheduledExecutorService scheduler, int timeout) { - return channels.values().parallelStream().map(v -> v.start(connection, scheduler, timeout)) + return channels.values().parallelStream().map(cChannel -> cChannel.start(connection, scheduler, timeout)) .collect(FutureCollector.allOf()); } @@ -122,7 +126,7 @@ public void setConfigSeen() { * exceptionally on errors. */ public CompletableFuture<@Nullable Void> stop() { - return channels.values().parallelStream().map(CChannel::stop).collect(FutureCollector.allOf()); + return channels.values().parallelStream().map(ComponentChannel::stop).collect(FutureCollector.allOf()); } /** @@ -131,7 +135,7 @@ public void setConfigSeen() { * @param channelTypeProvider The channel type provider */ public void addChannelTypes(MqttChannelTypeProvider channelTypeProvider) { - channelTypeProvider.setChannelGroupType(groupTypeUID(), type()); + channelTypeProvider.setChannelGroupType(getGroupTypeUID(), getType()); channels.values().forEach(v -> v.addChannelTypes(channelTypeProvider)); } @@ -143,46 +147,46 @@ public void addChannelTypes(MqttChannelTypeProvider channelTypeProvider) { */ public void removeChannelTypes(MqttChannelTypeProvider channelTypeProvider) { channels.values().forEach(v -> v.removeChannelTypes(channelTypeProvider)); - channelTypeProvider.removeChannelGroupType(groupTypeUID()); + channelTypeProvider.removeChannelGroupType(getGroupTypeUID()); } /** * Each HomeAssistant component corresponds to a Channel Group Type. */ - public ChannelGroupTypeUID groupTypeUID() { + public ChannelGroupTypeUID getGroupTypeUID() { return channelGroupTypeUID; } /** * The unique id of this component. */ - public ChannelGroupUID uid() { + public ChannelGroupUID getGroupUID() { return channelGroupUID; } /** * Component (Channel Group) name. */ - public String name() { - return channelConfiguration.name; + public String getName() { + return channelConfiguration.getName(); } /** * Each component consists of multiple Channels. */ - public Map channelTypes() { + public Map getChannelMap() { return channels; } /** * Return a components channel. A HomeAssistant MQTT component consists of multiple functions * and those are mapped to one or more channels. The channel IDs are constants within the - * derived Component, like the {@link ComponentSwitch#switchChannelID}. + * derived Component, like the {@link Switch#switchChannelID}. * * @param channelID The channel ID * @return A components channel */ - public @Nullable CChannel channel(String channelID) { + public @Nullable ComponentChannel getChannel(String channelID) { return channels.get(channelID); } @@ -196,11 +200,11 @@ public int getConfigHash() { /** * Return the channel group type. */ - public ChannelGroupType type() { - final List channelDefinitions = channels.values().stream().map(CChannel::type) + public ChannelGroupType getType() { + final List channelDefinitions = channels.values().stream().map(ComponentChannel::type) .collect(Collectors.toList()); - return ChannelGroupTypeBuilder.instance(channelGroupTypeUID, name()).withChannelDefinitions(channelDefinitions) - .build(); + return ChannelGroupTypeBuilder.instance(channelGroupTypeUID, getName()) + .withChannelDefinitions(channelDefinitions).build(); } /** @@ -208,13 +212,26 @@ public ChannelGroupType type() { * to the MQTT broker got lost. */ public void resetState() { - channels.values().forEach(CChannel::resetState); + channels.values().forEach(ComponentChannel::resetState); } /** * Return the channel group definition for this component. */ public ChannelGroupDefinition getGroupDefinition() { - return new ChannelGroupDefinition(channelGroupUID.getId(), groupTypeUID(), name(), null); + return new ChannelGroupDefinition(channelGroupUID.getId(), getGroupTypeUID(), getName(), null); + } + + public HaID getHaID() { + return haID; + } + + public String getChannelConfigurationJson() { + return channelConfigurationJson; + } + + @Nullable + public TransformationServiceProvider getTransformationServiceProvider() { + return componentConfiguration.getTransformationServiceProvider(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentAlarmControlPanel.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java similarity index 74% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentAlarmControlPanel.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java index 6ae7d4e0faa79..8065910cab585 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentAlarmControlPanel.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java @@ -10,11 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal; +package org.openhab.binding.mqtt.homeassistant.internal.component; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.values.TextValue; +import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; /** * A MQTT alarm control panel, following the https://www.home-assistant.io/components/alarm_control_panel.mqtt/ @@ -26,7 +27,7 @@ * @author David Graeff - Initial contribution */ @NonNullByDefault -public class ComponentAlarmControlPanel extends AbstractComponent { +public class AlarmControlPanel extends AbstractComponent { public static final String stateChannelID = "alarm"; // Randomly chosen channel "ID" public static final String switchDisarmChannelID = "disarm"; // Randomly chosen channel "ID" public static final String switchArmHomeChannelID = "armhome"; // Randomly chosen channel "ID" @@ -35,7 +36,7 @@ public class ComponentAlarmControlPanel extends AbstractComponent { +public class BinarySensor extends AbstractComponent { public static final String sensorChannelID = "sensor"; // Randomly chosen channel "ID" /** * Configuration class for MQTT component */ - static class ChannelConfiguration extends BaseChannelConfiguration { + static class ChannelConfiguration extends AbstractChannelConfiguration { ChannelConfiguration() { super("MQTT Binary Sensor"); } @@ -53,16 +54,16 @@ static class ChannelConfiguration extends BaseChannelConfiguration { protected @Nullable List json_attributes; } - public ComponentBinarySensor(CFactory.ComponentConfiguration componentConfiguration) { + public BinarySensor(ComponentFactory.ComponentConfiguration componentConfiguration) { super(componentConfiguration, ChannelConfiguration.class); OnOffValue value = new OnOffValue(channelConfiguration.payload_on, channelConfiguration.payload_off); buildChannel(sensorChannelID, value, "value", getListener(componentConfiguration, value)) - .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template).build(); + .stateTopic(channelConfiguration.state_topic, channelConfiguration.getValueTemplate()).build(); } - private ChannelStateUpdateListener getListener(CFactory.ComponentConfiguration componentConfiguration, + private ChannelStateUpdateListener getListener(ComponentFactory.ComponentConfiguration componentConfiguration, Value value) { ChannelStateUpdateListener updateListener = componentConfiguration.getUpdateListener(); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentCamera.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java similarity index 73% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentCamera.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java index f61cc3bc1ac3c..58567f8ced710 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentCamera.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java @@ -10,10 +10,11 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal; +package org.openhab.binding.mqtt.homeassistant.internal.component; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.mqtt.generic.values.ImageValue; +import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; /** * A MQTT camera, following the https://www.home-assistant.io/components/camera.mqtt/ specification. @@ -23,13 +24,13 @@ * @author David Graeff - Initial contribution */ @NonNullByDefault -public class ComponentCamera extends AbstractComponent { +public class Camera extends AbstractComponent { public static final String cameraChannelID = "camera"; // Randomly chosen channel "ID" /** * Configuration class for MQTT component */ - static class ChannelConfiguration extends BaseChannelConfiguration { + static class ChannelConfiguration extends AbstractChannelConfiguration { ChannelConfiguration() { super("MQTT Camera"); } @@ -37,12 +38,12 @@ static class ChannelConfiguration extends BaseChannelConfiguration { protected String topic = ""; } - public ComponentCamera(CFactory.ComponentConfiguration componentConfiguration) { + public Camera(ComponentFactory.ComponentConfiguration componentConfiguration) { super(componentConfiguration, ChannelConfiguration.class); ImageValue value = new ImageValue(); - buildChannel(cameraChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener()) + buildChannel(cameraChannelID, value, channelConfiguration.getName(), componentConfiguration.getUpdateListener()) .stateTopic(channelConfiguration.topic).build(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentClimate.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java similarity index 92% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentClimate.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java index 197347af28603..ad3b434b12165 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentClimate.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal; +package org.openhab.binding.mqtt.homeassistant.internal.component; import java.math.BigDecimal; import java.util.Arrays; @@ -24,6 +24,8 @@ import org.openhab.binding.mqtt.generic.values.OnOffValue; import org.openhab.binding.mqtt.generic.values.TextValue; import org.openhab.binding.mqtt.generic.values.Value; +import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel; +import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; import org.openhab.binding.mqtt.homeassistant.internal.listener.ChannelStateUpdateListenerProxy; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.ChannelUID; @@ -36,7 +38,7 @@ * @author David Graeff - Initial contribution */ @NonNullByDefault -public class ComponentClimate extends AbstractComponent { +public class Climate extends AbstractComponent { private static final String ACTION_CH_ID = "action"; private static final String AUX_CH_ID = "aux"; private static final String AWAY_MODE_CH_ID = "awayMode"; @@ -62,7 +64,7 @@ public class ComponentClimate extends AbstractComponent C getConfig(Class clazz) { - return BaseChannelConfiguration.fromString(configJSON, gson, clazz); + public C getConfig(Class clazz) { + return AbstractChannelConfiguration.fromString(configJSON, gson, clazz); } } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentCover.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java similarity index 73% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentCover.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java index e2c89b01e732d..1d706a1d99963 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentCover.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java @@ -10,11 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal; +package org.openhab.binding.mqtt.homeassistant.internal.component; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.values.RollershutterValue; +import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; /** * A MQTT Cover component, following the https://www.home-assistant.io/components/cover.mqtt/ specification. @@ -24,13 +25,13 @@ * @author David Graeff - Initial contribution */ @NonNullByDefault -public class ComponentCover extends AbstractComponent { +public class Cover extends AbstractComponent { public static final String switchChannelID = "cover"; // Randomly chosen channel "ID" /** * Configuration class for MQTT component */ - static class ChannelConfiguration extends BaseChannelConfiguration { + static class ChannelConfiguration extends AbstractChannelConfiguration { ChannelConfiguration() { super("MQTT Cover"); } @@ -42,14 +43,16 @@ static class ChannelConfiguration extends BaseChannelConfiguration { protected String payload_stop = "STOP"; } - public ComponentCover(CFactory.ComponentConfiguration componentConfiguration) { + public Cover(ComponentFactory.ComponentConfiguration componentConfiguration) { super(componentConfiguration, ChannelConfiguration.class); RollershutterValue value = new RollershutterValue(channelConfiguration.payload_open, channelConfiguration.payload_close, channelConfiguration.payload_stop); - buildChannel(switchChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener()) - .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template) - .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain).build(); + buildChannel(switchChannelID, value, channelConfiguration.getName(), componentConfiguration.getUpdateListener()) + .stateTopic(channelConfiguration.state_topic, channelConfiguration.getValueTemplate()) + .commandTopic(channelConfiguration.command_topic, channelConfiguration.isRetain(), + channelConfiguration.getQos()) + .build(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentFan.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java similarity index 72% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentFan.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java index 069585b747ab2..74ae40c43bdfa 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentFan.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java @@ -10,11 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal; +package org.openhab.binding.mqtt.homeassistant.internal.component; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.values.OnOffValue; +import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; /** * A MQTT Fan component, following the https://www.home-assistant.io/components/fan.mqtt/ specification. @@ -24,13 +25,13 @@ * @author David Graeff - Initial contribution */ @NonNullByDefault -public class ComponentFan extends AbstractComponent { +public class Fan extends AbstractComponent { public static final String switchChannelID = "fan"; // Randomly chosen channel "ID" /** * Configuration class for MQTT component */ - static class ChannelConfiguration extends BaseChannelConfiguration { + static class ChannelConfiguration extends AbstractChannelConfiguration { ChannelConfiguration() { super("MQTT Fan"); } @@ -41,12 +42,14 @@ static class ChannelConfiguration extends BaseChannelConfiguration { protected String payload_off = "OFF"; } - public ComponentFan(CFactory.ComponentConfiguration componentConfiguration) { + public Fan(ComponentFactory.ComponentConfiguration componentConfiguration) { super(componentConfiguration, ChannelConfiguration.class); OnOffValue value = new OnOffValue(channelConfiguration.payload_on, channelConfiguration.payload_off); - buildChannel(switchChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener()) - .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template) - .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain).build(); + buildChannel(switchChannelID, value, channelConfiguration.getName(), componentConfiguration.getUpdateListener()) + .stateTopic(channelConfiguration.state_topic, channelConfiguration.getValueTemplate()) + .commandTopic(channelConfiguration.command_topic, channelConfiguration.isRetain(), + channelConfiguration.getQos()) + .build(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentLight.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java similarity index 84% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentLight.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java index e42ed29bbce47..c5860e00c204f 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentLight.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal; +package org.openhab.binding.mqtt.homeassistant.internal.component; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -22,6 +22,8 @@ import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener; import org.openhab.binding.mqtt.generic.mapping.ColorMode; import org.openhab.binding.mqtt.generic.values.ColorValue; +import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel; +import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; import org.openhab.core.thing.ChannelUID; import org.openhab.core.types.Command; @@ -36,8 +38,7 @@ * @author David Graeff - Initial contribution */ @NonNullByDefault -public class ComponentLight extends AbstractComponent - implements ChannelStateUpdateListener { +public class Light extends AbstractComponent implements ChannelStateUpdateListener { public static final String switchChannelID = "light"; // Randomly chosen channel "ID" public static final String brightnessChannelID = "brightness"; // Randomly chosen channel "ID" public static final String colorChannelID = "color"; // Randomly chosen channel "ID" @@ -45,7 +46,7 @@ public class ComponentLight extends AbstractComponent { +public class Lock extends AbstractComponent { public static final String switchChannelID = "lock"; // Randomly chosen channel "ID" /** * Configuration class for MQTT component */ - static class ChannelConfiguration extends BaseChannelConfiguration { + static class ChannelConfiguration extends AbstractChannelConfiguration { ChannelConfiguration() { super("MQTT Lock"); } @@ -41,7 +42,7 @@ static class ChannelConfiguration extends BaseChannelConfiguration { protected @Nullable String command_topic; } - public ComponentLock(CFactory.ComponentConfiguration componentConfiguration) { + public Lock(ComponentFactory.ComponentConfiguration componentConfiguration) { super(componentConfiguration, ChannelConfiguration.class); // We do not support all HomeAssistant quirks @@ -51,8 +52,10 @@ public ComponentLock(CFactory.ComponentConfiguration componentConfiguration) { buildChannel(switchChannelID, new OnOffValue(channelConfiguration.payload_lock, channelConfiguration.payload_unlock), - channelConfiguration.name, componentConfiguration.getUpdateListener()) - .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template) - .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain).build(); + channelConfiguration.getName(), componentConfiguration.getUpdateListener()) + .stateTopic(channelConfiguration.state_topic, channelConfiguration.getValueTemplate()) + .commandTopic(channelConfiguration.command_topic, channelConfiguration.isRetain(), + channelConfiguration.getQos()) + .build(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentSensor.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java similarity index 80% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentSensor.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java index 2e18a3a2dd6f1..fd71eced7c8b8 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentSensor.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal; +package org.openhab.binding.mqtt.homeassistant.internal.component; import java.util.List; import java.util.regex.Pattern; @@ -21,6 +21,7 @@ import org.openhab.binding.mqtt.generic.values.NumberValue; import org.openhab.binding.mqtt.generic.values.TextValue; import org.openhab.binding.mqtt.generic.values.Value; +import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; import org.openhab.binding.mqtt.homeassistant.internal.listener.ExpireUpdateStateListener; /** @@ -29,14 +30,14 @@ * @author David Graeff - Initial contribution */ @NonNullByDefault -public class ComponentSensor extends AbstractComponent { +public class Sensor extends AbstractComponent { public static final String sensorChannelID = "sensor"; // Randomly chosen channel "ID" private static final Pattern triggerIcons = Pattern.compile("^mdi:(toggle|gesture).*$"); /** * Configuration class for MQTT component */ - static class ChannelConfiguration extends BaseChannelConfiguration { + static class ChannelConfiguration extends AbstractChannelConfiguration { ChannelConfiguration() { super("MQTT Sensor"); } @@ -53,11 +54,12 @@ static class ChannelConfiguration extends BaseChannelConfiguration { protected @Nullable List json_attributes; } - public ComponentSensor(CFactory.ComponentConfiguration componentConfiguration) { + public Sensor(ComponentFactory.ComponentConfiguration componentConfiguration) { super(componentConfiguration, ChannelConfiguration.class); Value value; + @Nullable String uom = channelConfiguration.unit_of_measurement; if (uom != null && !uom.isBlank()) { @@ -66,16 +68,16 @@ public ComponentSensor(CFactory.ComponentConfiguration componentConfiguration) { value = new TextValue(); } - String icon = channelConfiguration.icon; + String icon = channelConfiguration.getIcon(); boolean trigger = triggerIcons.matcher(icon).matches(); - buildChannel(sensorChannelID, value, channelConfiguration.name, getListener(componentConfiguration, value)) - .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)// + buildChannel(sensorChannelID, value, channelConfiguration.getName(), getListener(componentConfiguration, value)) + .stateTopic(channelConfiguration.state_topic, channelConfiguration.getValueTemplate())// .trigger(trigger).build(); } - private ChannelStateUpdateListener getListener(CFactory.ComponentConfiguration componentConfiguration, + private ChannelStateUpdateListener getListener(ComponentFactory.ComponentConfiguration componentConfiguration, Value value) { ChannelStateUpdateListener updateListener = componentConfiguration.getUpdateListener(); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentSwitch.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Switch.java similarity index 82% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentSwitch.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Switch.java index 87f179d132b27..2dcf56655df6f 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentSwitch.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Switch.java @@ -10,11 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal; +package org.openhab.binding.mqtt.homeassistant.internal.component; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.values.OnOffValue; +import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; /** * A MQTT switch, following the https://www.home-assistant.io/components/switch.mqtt/ specification. @@ -22,13 +23,13 @@ * @author David Graeff - Initial contribution */ @NonNullByDefault -public class ComponentSwitch extends AbstractComponent { +public class Switch extends AbstractComponent { public static final String switchChannelID = "switch"; // Randomly chosen channel "ID" /** * Configuration class for MQTT component */ - static class ChannelConfiguration extends BaseChannelConfiguration { + static class ChannelConfiguration extends AbstractChannelConfiguration { ChannelConfiguration() { super("MQTT Switch"); } @@ -47,7 +48,7 @@ static class ChannelConfiguration extends BaseChannelConfiguration { protected @Nullable String json_attributes_template; } - public ComponentSwitch(CFactory.ComponentConfiguration componentConfiguration) { + public Switch(ComponentFactory.ComponentConfiguration componentConfiguration) { super(componentConfiguration, ChannelConfiguration.class); boolean optimistic = channelConfiguration.optimistic != null ? channelConfiguration.optimistic @@ -66,8 +67,9 @@ public ComponentSwitch(CFactory.ComponentConfiguration componentConfiguration) { channelConfiguration.payload_off); buildChannel(switchChannelID, value, "state", componentConfiguration.getUpdateListener()) - .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template) - .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain, channelConfiguration.qos) + .stateTopic(channelConfiguration.state_topic, channelConfiguration.getValueTemplate()) + .commandTopic(channelConfiguration.command_topic, channelConfiguration.isRetain(), + channelConfiguration.getQos()) .build(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/BaseChannelConfiguration.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/AbstractChannelConfiguration.java similarity index 60% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/BaseChannelConfiguration.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/AbstractChannelConfiguration.java index bb17b03530cf1..a1e54f767f9a0 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/BaseChannelConfiguration.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/AbstractChannelConfiguration.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal; +package org.openhab.binding.mqtt.homeassistant.internal.config; import java.util.List; import java.util.Map; @@ -22,7 +22,6 @@ import org.openhab.core.util.UIDUtils; import com.google.gson.Gson; -import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; /** @@ -31,44 +30,8 @@ * @author Jochen Klein - Initial contribution */ @NonNullByDefault -public abstract class BaseChannelConfiguration { - - /** - * This class is needed, to be able to parse only the common base attributes. - * Without this, {@link BaseChannelConfiguration} cannot be instantiated, as it is abstract. - * This is needed during the discovery. - */ - private static class Config extends BaseChannelConfiguration { - public Config() { - super("private"); - } - } - - /** - * Parse the configJSON into a subclass of {@link BaseChannelConfiguration} - * - * @param configJSON - * @param gson - * @param clazz - * @return configuration object - */ - public static C fromString(final String configJSON, final Gson gson, - final Class clazz) { - return Objects.requireNonNull(gson.fromJson(configJSON, clazz)); - } - - /** - * Parse the base properties of the configJSON into a {@link BaseChannelConfiguration} - * - * @param configJSON - * @param gson - * @return configuration object - */ - public static BaseChannelConfiguration fromString(final String configJSON, final Gson gson) { - return fromString(configJSON, gson, Config.class); - } - - public String name; +public abstract class AbstractChannelConfiguration { + protected String name; protected String icon = ""; protected int qos; // defaults to 0 according to HA specification @@ -76,42 +39,39 @@ public static BaseChannelConfiguration fromString(final String configJSON, final protected @Nullable String value_template; protected @Nullable String unique_id; + protected AvailabilityMode availability_mode = AvailabilityMode.LATEST; protected @Nullable String availability_topic; protected String payload_available = "online"; protected String payload_not_available = "offline"; + /** + * A list of MQTT topics subscribed to receive availability (online/offline) updates. Must not be used together with + * availability_topic + */ + protected @Nullable List availability; + @SerializedName(value = "~") protected String tilde = ""; - protected BaseChannelConfiguration(String defaultName) { - this.name = defaultName; - } + protected @Nullable Device device; - public @Nullable String expand(@Nullable String value) { - return value == null ? null : value.replaceAll("~", tilde); + /** + * Parse the base properties of the configJSON into a {@link AbstractChannelConfiguration} + * + * @param configJSON channels configuration in JSON + * @param gson parser + * @return configuration object + */ + public static AbstractChannelConfiguration fromString(final String configJSON, final Gson gson) { + return fromString(configJSON, gson, Config.class); } - protected @Nullable Device device; - - static class Device { - @JsonAdapter(ListOrStringDeserializer.class) - protected @Nullable List identifiers; - protected @Nullable List connections; - protected @Nullable String manufacturer; - protected @Nullable String model; - protected @Nullable String name; - protected @Nullable String sw_version; - - public @Nullable String getId() { - List identifiers = this.identifiers; - return identifiers == null ? null : String.join("_", identifiers); - } + protected AbstractChannelConfiguration(String defaultName) { + this.name = defaultName; } - @JsonAdapter(ConnectionDeserializer.class) - static class Connection { - protected @Nullable String type; - protected @Nullable String identifier; + public @Nullable String expand(@Nullable String value) { + return value == null ? null : value.replaceAll("~", tilde); } public String getThingName() { @@ -140,22 +100,103 @@ public String getThingId(String defaultId) { } public Map appendToProperties(Map properties) { + @Nullable final Device device_ = device; if (device_ == null) { return properties; } + @Nullable final String manufacturer = device_.manufacturer; if (manufacturer != null) { properties.put(Thing.PROPERTY_VENDOR, manufacturer); } + @Nullable final String model = device_.model; if (model != null) { properties.put(Thing.PROPERTY_MODEL_ID, model); } + @Nullable final String sw_version = device_.sw_version; if (sw_version != null) { properties.put(Thing.PROPERTY_FIRMWARE_VERSION, sw_version); } return properties; } + + public String getName() { + return name; + } + + public String getIcon() { + return icon; + } + + public int getQos() { + return qos; + } + + public boolean isRetain() { + return retain; + } + + @Nullable + public String getValueTemplate() { + return value_template; + } + + @Nullable + public String getUniqueId() { + return unique_id; + } + + @Nullable + public String getAvailabilityTopic() { + return availability_topic; + } + + public String getPayloadAvailable() { + return payload_available; + } + + public String getPayloadNotAvailable() { + return payload_not_available; + } + + @Nullable + public Device getDevice() { + return device; + } + + @Nullable + public List getAvailability() { + return availability; + } + + public AvailabilityMode getAvailabilityMode() { + return availability_mode; + } + + /** + * This class is needed, to be able to parse only the common base attributes. + * Without this, {@link AbstractChannelConfiguration} cannot be instantiated, as it is abstract. + * This is needed during the discovery. + */ + private static class Config extends AbstractChannelConfiguration { + public Config() { + super("private"); + } + } + + /** + * Parse the configJSON into a subclass of {@link AbstractChannelConfiguration} + * + * @param configJSON channels configuration in JSON + * @param gson parser + * @param clazz target configuration class + * @return configuration object + */ + public static C fromString(final String configJSON, final Gson gson, + final Class clazz) { + return Objects.requireNonNull(gson.fromJson(configJSON, clazz)); + } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Availability.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Availability.java new file mode 100644 index 0000000000000..fd9510a88af2b --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Availability.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.config; + +/** + * MQTT topic subscribed to receive availability (online/offline) updates. Must not be used together with + * availability_topic + * + * @author Anton Kharuzhy - Initial contribution + */ +public class Availability { + protected String payload_available = "online"; + protected String payload_not_available = "offline"; + protected String topic; + + public String getPayload_available() { + return payload_available; + } + + public String getPayload_not_available() { + return payload_not_available; + } + + public String getTopic() { + return topic; + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/AvailabilityMode.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/AvailabilityMode.java new file mode 100644 index 0000000000000..22be1b0bf968b --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/AvailabilityMode.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.config; + +import com.google.gson.annotations.SerializedName; + +/** + * controls the conditions needed to set the entity to available + * + * @author Anton Kharuzhy - Initial contribution + */ +public enum AvailabilityMode { + /** + * payload_available must be received on all configured availability topics before the entity is marked as online + */ + @SerializedName("all") + ALL, + + /** + * payload_available must be received on at least one configured availability topic before the entity is marked as + * online + */ + @SerializedName("any") + ANY, + + /** + * the last payload_available or payload_not_available received on any configured availability topic controls the + * availability + */ + @SerializedName("latest") + LATEST +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ChannelConfigurationTypeAdapterFactory.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/ChannelConfigurationTypeAdapterFactory.java similarity index 82% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ChannelConfigurationTypeAdapterFactory.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/ChannelConfigurationTypeAdapterFactory.java index 5d61e981aeb87..e94bd60e4d462 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ChannelConfigurationTypeAdapterFactory.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/ChannelConfigurationTypeAdapterFactory.java @@ -10,13 +10,14 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal; +package org.openhab.binding.mqtt.homeassistant.internal.config; import java.io.IOException; import java.lang.reflect.Field; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mqtt.homeassistant.internal.MappingJsonReader; import com.google.gson.Gson; import com.google.gson.TypeAdapter; @@ -28,12 +29,16 @@ /** * This a Gson type adapter factory. * - * It will create a type adapter for every class derived from {@link BaseChannelConfiguration} and ensures, + *

+ * It will create a type adapter for every class derived from {@link + * org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration} and ensures, * that abbreviated names are replaces with their long versions during the read. * + *

* In elements, whose name end in'_topic' '~' replacement is performed. * - * The adapters also handle {@link BaseChannelConfiguration.Device} + *

+ * The adapters also handle {@link org.openhab.binding.mqtt.homeassistant.internal.config.Device} * * @author Jochen Klein - Initial contribution */ @@ -46,21 +51,22 @@ public TypeAdapter create(@Nullable Gson gson, @Nullable TypeToken typ if (gson == null || type == null) { return null; } - if (BaseChannelConfiguration.class.isAssignableFrom(type.getRawType())) { + if (AbstractChannelConfiguration.class.isAssignableFrom(type.getRawType())) { return createHAConfig(gson, type); } - if (BaseChannelConfiguration.Device.class.isAssignableFrom(type.getRawType())) { + if (Device.class.isAssignableFrom(type.getRawType())) { return createHADevice(gson, type); } return null; } /** - * Handle {@link BaseChannelConfiguration} + * Handle {@link + * org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration} * - * @param gson - * @param type - * @return + * @param gson parser + * @param type type + * @return adapter */ private TypeAdapter createHAConfig(Gson gson, TypeToken type) { /* The delegate is the 'default' adapter */ @@ -72,7 +78,7 @@ private TypeAdapter createHAConfig(Gson gson, TypeToken type) { /* read the object using the default adapter, but translate the names in the reader */ T result = delegate.read(MappingJsonReader.getConfigMapper(in)); /* do the '~' expansion afterwards */ - expandTidleInTopics(BaseChannelConfiguration.class.cast(result)); + expandTidleInTopics(AbstractChannelConfiguration.class.cast(result)); return result; } @@ -102,7 +108,7 @@ public void write(JsonWriter out, @Nullable T value) throws IOException { }; } - private void expandTidleInTopics(BaseChannelConfiguration config) { + private void expandTidleInTopics(AbstractChannelConfiguration config) { Class type = config.getClass(); String tilde = config.tilde; @@ -127,9 +133,7 @@ private void expandTidleInTopics(BaseChannelConfiguration config) { } field.set(config, newValue); - } catch (IllegalArgumentException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { + } catch (IllegalArgumentException | IllegalAccessException e) { throw new RuntimeException(e); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Connection.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Connection.java new file mode 100644 index 0000000000000..18d076daf60c0 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Connection.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.config; + +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.annotations.JsonAdapter; + +/** + * Connection configuration + * + * @author Jochen Klein - Initial contribution + */ +@JsonAdapter(ConnectionDeserializer.class) +public class Connection { + protected @Nullable String type; + protected @Nullable String identifier; + + public Connection() { + } + + public Connection(@Nullable String type, @Nullable String identifier) { + this.type = type; + this.identifier = identifier; + } + + @Nullable + public String getType() { + return type; + } + + @Nullable + public String getIdentifier() { + return identifier; + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ConnectionDeserializer.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/ConnectionDeserializer.java similarity index 66% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ConnectionDeserializer.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/ConnectionDeserializer.java index 7383b4d7c4277..99a3985c256e4 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ConnectionDeserializer.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/ConnectionDeserializer.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal; +package org.openhab.binding.mqtt.homeassistant.internal.config; import java.lang.reflect.Type; @@ -27,14 +27,11 @@ * * @author Jan N. Klug - Initial contribution */ -public class ConnectionDeserializer implements JsonDeserializer { +public class ConnectionDeserializer implements JsonDeserializer { @Override - public BaseChannelConfiguration.Connection deserialize(JsonElement json, Type typeOfT, - JsonDeserializationContext context) throws JsonParseException { + public Connection deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { JsonArray list = json.getAsJsonArray(); - BaseChannelConfiguration.Connection conn = new BaseChannelConfiguration.Connection(); - conn.type = list.get(0).getAsString(); - conn.identifier = list.get(1).getAsString(); - return conn; + return new Connection(list.get(0).getAsString(), list.get(1).getAsString()); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Device.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Device.java new file mode 100644 index 0000000000000..924cbe68fffea --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Device.java @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.config; + +import java.util.List; + +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.annotations.JsonAdapter; + +/** + * Device configuration + * + * @author Jochen Klein - Initial contribution + */ +public class Device { + @JsonAdapter(ListOrStringDeserializer.class) + protected @Nullable List identifiers; + protected @Nullable List connections; + protected @Nullable String manufacturer; + protected @Nullable String model; + protected @Nullable String name; + protected @Nullable String sw_version; + + public @Nullable String getId() { + @Nullable + List identifiers = this.identifiers; + return identifiers == null ? null : String.join("_", identifiers); + } + + @Nullable + public List getConnections() { + return connections; + } + + @Nullable + public String getManufacturer() { + return manufacturer; + } + + @Nullable + public String getModel() { + return model; + } + + @Nullable + public String getName() { + return name; + } + + @Nullable + public String getSw_version() { + return sw_version; + } + + @Nullable + public List getIdentifiers() { + return identifiers; + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ListOrStringDeserializer.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/ListOrStringDeserializer.java similarity index 93% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ListOrStringDeserializer.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/ListOrStringDeserializer.java index d848b1d33d302..94320c1d49acb 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ListOrStringDeserializer.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/ListOrStringDeserializer.java @@ -10,11 +10,11 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal; +package org.openhab.binding.mqtt.homeassistant.internal.config; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -62,7 +62,7 @@ public void write(@Nullable JsonWriter out, @Nullable List value) throws in.nextNull(); return null; case STRING: - return Arrays.asList(in.nextString()); + return Collections.singletonList(in.nextString()); case BEGIN_ARRAY: return readList(in); default: diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscovery.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscovery.java index eb8ff8b2dab4b..e7accbbd6ded4 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscovery.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscovery.java @@ -33,10 +33,10 @@ import org.openhab.binding.mqtt.discovery.MQTTTopicDiscoveryService; import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider; import org.openhab.binding.mqtt.homeassistant.generic.internal.MqttBindingConstants; -import org.openhab.binding.mqtt.homeassistant.internal.BaseChannelConfiguration; -import org.openhab.binding.mqtt.homeassistant.internal.ChannelConfigurationTypeAdapterFactory; import org.openhab.binding.mqtt.homeassistant.internal.HaID; import org.openhab.binding.mqtt.homeassistant.internal.HandlerConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.ChannelConfigurationTypeAdapterFactory; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryService; @@ -147,7 +147,7 @@ public void receivedMessage(ThingUID connectionBridge, MqttBrokerConnection conn } this.future = scheduler.schedule(this::publishResults, 2, TimeUnit.SECONDS); - BaseChannelConfiguration config = BaseChannelConfiguration + AbstractChannelConfiguration config = AbstractChannelConfiguration .fromString(new String(payload, StandardCharsets.UTF_8), gson); // We will of course find multiple of the same unique Thing IDs, for each different component another one. diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java index a6d9261162670..bc66b1ca9789f 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java @@ -32,14 +32,14 @@ import org.openhab.binding.mqtt.generic.tools.DelayedBatchProcessing; import org.openhab.binding.mqtt.generic.utils.FutureCollector; import org.openhab.binding.mqtt.homeassistant.generic.internal.MqttBindingConstants; -import org.openhab.binding.mqtt.homeassistant.internal.AbstractComponent; -import org.openhab.binding.mqtt.homeassistant.internal.CChannel; -import org.openhab.binding.mqtt.homeassistant.internal.CFactory; -import org.openhab.binding.mqtt.homeassistant.internal.ChannelConfigurationTypeAdapterFactory; +import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel; import org.openhab.binding.mqtt.homeassistant.internal.DiscoverComponents; import org.openhab.binding.mqtt.homeassistant.internal.DiscoverComponents.ComponentDiscovered; import org.openhab.binding.mqtt.homeassistant.internal.HaID; import org.openhab.binding.mqtt.homeassistant.internal.HandlerConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.component.AbstractComponent; +import org.openhab.binding.mqtt.homeassistant.internal.component.ComponentFactory; +import org.openhab.binding.mqtt.homeassistant.internal.config.ChannelConfigurationTypeAdapterFactory; import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; @@ -153,12 +153,12 @@ public void initialize() { if (channelConfigurationJSON == null) { logger.warn("Provided channel does not have a 'config' configuration key!"); } else { - component = CFactory.createComponent(thingUID, haID, channelConfigurationJSON, this, this, scheduler, - gson, transformationServiceProvider); + component = ComponentFactory.createComponent(thingUID, haID, channelConfigurationJSON, this, this, + scheduler, gson, transformationServiceProvider); } if (component != null) { - haComponents.put(component.uid().getId(), component); + haComponents.put(component.getGroupUID().getId(), component); component.addChannelTypes(channelTypeProvider); } else { logger.warn("Could not restore component {}", thing); @@ -235,7 +235,7 @@ protected void stop() { if (component == null) { return null; } - CChannel componentChannel = component.channel(channelUID.getIdWithoutGroup()); + ComponentChannel componentChannel = component.getChannel(channelUID.getIdWithoutGroup()); if (componentChannel == null) { return null; } @@ -264,7 +264,7 @@ public void accept(List> discoveredComponentsList) { synchronized (haComponents) { // sync whenever discoverComponents is started for (AbstractComponent discovered : discoveredComponentsList) { - AbstractComponent known = haComponents.get(discovered.uid().getId()); + AbstractComponent known = haComponents.get(discovered.getGroupUID().getId()); // Is component already known? if (known != null) { if (discovered.getConfigHash() != known.getConfigHash()) { @@ -280,15 +280,15 @@ public void accept(List> discoveredComponentsList) { // Add channel and group types to the types registry discovered.addChannelTypes(channelTypeProvider); // Add component to the component map - haComponents.put(discovered.uid().getId(), discovered); + haComponents.put(discovered.getGroupUID().getId(), discovered); // Start component / Subscribe to channel topics discovered.start(connection, scheduler, 0).exceptionally(e -> { - logger.warn("Failed to start component {}", discovered.uid(), e); + logger.warn("Failed to start component {}", discovered.getGroupUID(), e); return null; }); - Collection channels = discovered.channelTypes().values().stream().map(CChannel::getChannel) - .collect(Collectors.toList()); + Collection channels = discovered.getChannelMap().values().stream() + .map(ComponentChannel::getChannel).collect(Collectors.toList()); ThingHelper.addChannelsToThing(thing, channels); } } @@ -314,7 +314,7 @@ private void updateThingType() { synchronized (haComponents) { // sync whenever discoverComponents is started groupDefs = haComponents.values().stream().map(AbstractComponent::getGroupDefinition) .collect(Collectors.toList()); - channelDefs = haComponents.values().stream().map(AbstractComponent::type) + channelDefs = haComponents.values().stream().map(AbstractComponent::getType) .map(ChannelGroupType::getChannelDefinitions).flatMap(List::stream) .collect(Collectors.toList()); } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/ExpireUpdateStateListener.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/ExpireUpdateStateListener.java index 9427ffb0e3f7b..15a9ef8f20a7d 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/ExpireUpdateStateListener.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/ExpireUpdateStateListener.java @@ -38,7 +38,7 @@ public class ExpireUpdateStateListener extends ChannelStateUpdateListenerProxy { private final AvailabilityTracker tracker; private final ScheduledExecutorService scheduler; - private AtomicReference<@Nullable ScheduledFuture> expire = new AtomicReference<>(); + private final AtomicReference<@Nullable ScheduledFuture> expire = new AtomicReference<>(); public ExpireUpdateStateListener(ChannelStateUpdateListener original, int expireAfter, Value value, AvailabilityTracker tracker, ScheduledExecutorService scheduler) { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/OffDelayUpdateStateListener.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/OffDelayUpdateStateListener.java index 16b83ae026df7..c531975a204a3 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/OffDelayUpdateStateListener.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/OffDelayUpdateStateListener.java @@ -37,7 +37,7 @@ public class OffDelayUpdateStateListener extends ChannelStateUpdateListenerProxy private final Value value; private final ScheduledExecutorService scheduler; - private AtomicReference<@Nullable ScheduledFuture> delay = new AtomicReference<>(); + private final AtomicReference<@Nullable ScheduledFuture> delay = new AtomicReference<>(); public OffDelayUpdateStateListener(ChannelStateUpdateListener original, int offDelay, Value value, ScheduledExecutorService scheduler) { @@ -51,6 +51,7 @@ public OffDelayUpdateStateListener(ChannelStateUpdateListener original, int offD public void updateChannelState(final ChannelUID channelUID, State state) { super.updateChannelState(channelUID, state); + @Nullable ScheduledFuture newDelay = null; if (OnOffType.ON == state) { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/HAConfigurationTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/HAConfigurationTests.java similarity index 66% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/HAConfigurationTests.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/HAConfigurationTests.java index 071c4ea1ac4c8..141c568a9d3d3 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/HAConfigurationTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/HAConfigurationTests.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal; +package org.openhab.binding.mqtt.homeassistant.internal.component; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; @@ -24,7 +24,10 @@ import org.eclipse.jdt.annotation.NonNull; import org.junit.jupiter.api.Test; -import org.openhab.binding.mqtt.homeassistant.internal.BaseChannelConfiguration.Connection; +import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.ChannelConfigurationTypeAdapterFactory; +import org.openhab.binding.mqtt.homeassistant.internal.config.Connection; +import org.openhab.binding.mqtt.homeassistant.internal.config.Device; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -57,33 +60,33 @@ private static String readTestJson(final String name) { public void testAbbreviations() { String json = readTestJson("configA.json"); - BaseChannelConfiguration config = BaseChannelConfiguration.fromString(json, gson); + AbstractChannelConfiguration config = AbstractChannelConfiguration.fromString(json, gson); - assertThat(config.name, is("A")); - assertThat(config.icon, is("2")); - assertThat(config.qos, is(1)); - assertThat(config.retain, is(true)); - assertThat(config.value_template, is("B")); - assertThat(config.unique_id, is("C")); - assertThat(config.availability_topic, is("D/E")); - assertThat(config.payload_available, is("F")); - assertThat(config.payload_not_available, is("G")); + assertThat(config.getName(), is("A")); + assertThat(config.getIcon(), is("2")); + assertThat(config.getQos(), is(1)); + assertThat(config.isRetain(), is(true)); + assertThat(config.getValueTemplate(), is("B")); + assertThat(config.getUniqueId(), is("C")); + assertThat(config.getAvailabilityTopic(), is("D/E")); + assertThat(config.getPayloadAvailable(), is("F")); + assertThat(config.getPayloadNotAvailable(), is("G")); - assertThat(config.device, is(notNullValue())); + assertThat(config.getDevice(), is(notNullValue())); - BaseChannelConfiguration.Device device = config.device; + Device device = config.getDevice(); if (device != null) { - assertThat(device.identifiers, contains("H")); - assertThat(device.connections, is(notNullValue())); - List<@NonNull Connection> connections = device.connections; + assertThat(device.getIdentifiers(), contains("H")); + assertThat(device.getConnections(), is(notNullValue())); + List<@NonNull Connection> connections = device.getConnections(); if (connections != null) { - assertThat(connections.get(0).type, is("I1")); - assertThat(connections.get(0).identifier, is("I2")); + assertThat(connections.get(0).getType(), is("I1")); + assertThat(connections.get(0).getIdentifier(), is("I2")); } - assertThat(device.name, is("J")); - assertThat(device.model, is("K")); - assertThat(device.sw_version, is("L")); - assertThat(device.manufacturer, is("M")); + assertThat(device.getName(), is("J")); + assertThat(device.getModel(), is("K")); + assertThat(device.getSw_version(), is("L")); + assertThat(device.getManufacturer(), is("M")); } } @@ -91,17 +94,17 @@ public void testAbbreviations() { public void testTildeSubstritution() { String json = readTestJson("configB.json"); - ComponentSwitch.ChannelConfiguration config = BaseChannelConfiguration.fromString(json, gson, - ComponentSwitch.ChannelConfiguration.class); + Switch.ChannelConfiguration config = AbstractChannelConfiguration.fromString(json, gson, + Switch.ChannelConfiguration.class); - assertThat(config.availability_topic, is("D/E")); + assertThat(config.getAvailabilityTopic(), is("D/E")); assertThat(config.state_topic, is("O/D/")); assertThat(config.command_topic, is("P~Q")); - assertThat(config.device, is(notNullValue())); + assertThat(config.getDevice(), is(notNullValue())); - BaseChannelConfiguration.Device device = config.device; + Device device = config.getDevice(); if (device != null) { - assertThat(device.identifiers, contains("H")); + assertThat(device.getIdentifiers(), contains("H")); } } @@ -109,22 +112,22 @@ public void testTildeSubstritution() { public void testSampleFanConfig() { String json = readTestJson("configFan.json"); - ComponentFan.ChannelConfiguration config = BaseChannelConfiguration.fromString(json, gson, - ComponentFan.ChannelConfiguration.class); - assertThat(config.name, is("Bedroom Fan")); + Fan.ChannelConfiguration config = AbstractChannelConfiguration.fromString(json, gson, + Fan.ChannelConfiguration.class); + assertThat(config.getName(), is("Bedroom Fan")); } @Test public void testDeviceListConfig() { String json = readTestJson("configDeviceList.json"); - ComponentFan.ChannelConfiguration config = BaseChannelConfiguration.fromString(json, gson, - ComponentFan.ChannelConfiguration.class); - assertThat(config.device, is(notNullValue())); + Fan.ChannelConfiguration config = AbstractChannelConfiguration.fromString(json, gson, + Fan.ChannelConfiguration.class); + assertThat(config.getDevice(), is(notNullValue())); - BaseChannelConfiguration.Device device = config.device; + Device device = config.getDevice(); if (device != null) { - assertThat(device.identifiers, is(Arrays.asList("A", "B", "C"))); + assertThat(device.getIdentifiers(), is(Arrays.asList("A", "B", "C"))); } } @@ -132,28 +135,28 @@ public void testDeviceListConfig() { public void testDeviceSingleStringConfig() { String json = readTestJson("configDeviceSingleString.json"); - ComponentFan.ChannelConfiguration config = BaseChannelConfiguration.fromString(json, gson, - ComponentFan.ChannelConfiguration.class); - assertThat(config.device, is(notNullValue())); + Fan.ChannelConfiguration config = AbstractChannelConfiguration.fromString(json, gson, + Fan.ChannelConfiguration.class); + assertThat(config.getDevice(), is(notNullValue())); - BaseChannelConfiguration.Device device = config.device; + Device device = config.getDevice(); if (device != null) { - assertThat(device.identifiers, is(Arrays.asList("A"))); + assertThat(device.getIdentifiers(), is(Arrays.asList("A"))); } } @Test public void testTS0601ClimateConfig() { String json = readTestJson("configTS0601ClimateThermostat.json"); - ComponentClimate.ChannelConfiguration config = BaseChannelConfiguration.fromString(json, gson, - ComponentClimate.ChannelConfiguration.class); - assertThat(config.device, is(notNullValue())); - assertThat(config.device.identifiers, is(notNullValue())); - assertThat(config.device.identifiers.get(0), is("zigbee2mqtt_0x847127fffe11dd6a")); - assertThat(config.device.manufacturer, is("TuYa")); - assertThat(config.device.model, is("Radiator valve with thermostat (TS0601_thermostat)")); - assertThat(config.device.name, is("th1")); - assertThat(config.device.sw_version, is("Zigbee2MQTT 1.18.2")); + Climate.ChannelConfiguration config = AbstractChannelConfiguration.fromString(json, gson, + Climate.ChannelConfiguration.class); + assertThat(config.getDevice(), is(notNullValue())); + assertThat(config.getDevice().getIdentifiers(), is(notNullValue())); + assertThat(config.getDevice().getIdentifiers().get(0), is("zigbee2mqtt_0x847127fffe11dd6a")); + assertThat(config.getDevice().getManufacturer(), is("TuYa")); + assertThat(config.getDevice().getModel(), is("Radiator valve with thermostat (TS0601_thermostat)")); + assertThat(config.getDevice().getName(), is("th1")); + assertThat(config.getDevice().getSw_version(), is("Zigbee2MQTT 1.18.2")); assertThat(config.action_template, is( "{% set values = {'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'} %}{{ values[value_json.running_state] }}")); @@ -174,13 +177,13 @@ public void testTS0601ClimateConfig() { assertThat(config.mode_state_template, is("{{ value_json.system_mode }}")); assertThat(config.mode_state_topic, is("zigbee2mqtt/th1")); assertThat(config.modes, is(List.of("heat", "auto", "off"))); - assertThat(config.name, is("th1")); + assertThat(config.getName(), is("th1")); assertThat(config.temp_step, is(0.5f)); assertThat(config.temperature_command_topic, is("zigbee2mqtt/th1/set/current_heating_setpoint")); assertThat(config.temperature_state_template, is("{{ value_json.current_heating_setpoint }}")); assertThat(config.temperature_state_topic, is("zigbee2mqtt/th1")); assertThat(config.temperature_unit, is("C")); - assertThat(config.unique_id, is("0x847127fffe11dd6a_climate_zigbee2mqtt")); + assertThat(config.getUniqueId(), is("0x847127fffe11dd6a_climate_zigbee2mqtt")); assertThat(config.initial, is(21)); assertThat(config.send_if_off, is(true)); @@ -189,8 +192,8 @@ public void testTS0601ClimateConfig() { @Test public void testClimateConfig() { String json = readTestJson("configClimate.json"); - ComponentClimate.ChannelConfiguration config = BaseChannelConfiguration.fromString(json, gson, - ComponentClimate.ChannelConfiguration.class); + Climate.ChannelConfiguration config = AbstractChannelConfiguration.fromString(json, gson, + Climate.ChannelConfiguration.class); assertThat(config.action_template, is("a")); assertThat(config.action_topic, is("b")); assertThat(config.aux_command_topic, is("c")); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configA.json b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configA.json similarity index 100% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configA.json rename to bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configA.json diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configB.json b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configB.json similarity index 100% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configB.json rename to bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configB.json diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configClimate.json b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configClimate.json similarity index 100% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configClimate.json rename to bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configClimate.json diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configDeviceList.json b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configDeviceList.json similarity index 100% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configDeviceList.json rename to bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configDeviceList.json diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configDeviceSingleString.json b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configDeviceSingleString.json similarity index 100% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configDeviceSingleString.json rename to bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configDeviceSingleString.json diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configFan.json b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configFan.json similarity index 100% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configFan.json rename to bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configFan.json diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configTS0601ClimateThermostat.json b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configTS0601ClimateThermostat.json similarity index 100% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/configTS0601ClimateThermostat.json rename to bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configTS0601ClimateThermostat.json From 2e2e06ac1e869b80eda442cbbba7164497536c97 Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Fri, 30 Apr 2021 14:14:47 +0300 Subject: [PATCH 04/23] MQTT.Homeassistant discovery test added Signed-off-by: Anton Kharuzhy --- .../HomeAssistantDiscoveryTests.java | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscoveryTests.java diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscoveryTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscoveryTests.java new file mode 100644 index 0000000000000..2f9ca583113df --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscoveryTests.java @@ -0,0 +1,117 @@ +package org.openhab.binding.mqtt.homeassistant.internal.discovery; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider; +import org.openhab.binding.mqtt.homeassistant.internal.component.HAConfigurationTests; +import org.openhab.core.config.discovery.DiscoveryListener; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.type.ThingType; +import org.openhab.core.thing.type.ThingTypeBuilder; +import org.openhab.core.thing.type.ThingTypeRegistry; + +@ExtendWith(MockitoExtension.class) +public class HomeAssistantDiscoveryTests { + static final String BINDING_ID = "mqtt"; + static final String BRODGE_TYPE_ID = "homeassistant"; + static final String BRIDGE_TYPE_LABEL = "Homeassistant"; + static final ThingTypeUID BRIDGE_TYPE_UID = new ThingTypeUID(BINDING_ID, BRODGE_TYPE_ID); + static final String BRIDGE_ID = UUID.randomUUID().toString(); + static final ThingUID BRIDGE_UID = new ThingUID(BRIDGE_TYPE_UID, BRIDGE_ID); + + private @Mock MqttBrokerConnection brokerConnection; + private @Mock ThingTypeRegistry thingTypeRegistry; + private HomeAssistantDiscovery discovery; + + @BeforeEach + public void beforeEach() { + discovery = new TestHomeAssistantDiscovery(new MqttChannelTypeProvider(thingTypeRegistry)); + ThingType baseThingType = ThingTypeBuilder.instance(BRIDGE_TYPE_UID, BRIDGE_TYPE_LABEL).buildBridge(); + Mockito.when(thingTypeRegistry.getThingType(BRIDGE_TYPE_UID)).thenReturn(baseThingType); + } + + @Test + public void testClimateThingDiscovery() throws Exception { + var json = Files.readString( + Path.of(HAConfigurationTests.class.getResource("configTS0601ClimateThermostat.json").toURI())); + var climateUID = "0x847127fffe11dd6a_climate_zigbee2mqtt"; + var discoveryListener = new LatchDiscoveryListener(); + var latch = discoveryListener.createWaitForThingsDiscoveredLatch(1); + + discovery.addDiscoveryListener(discoveryListener); + discovery.receivedMessage(BRIDGE_UID, brokerConnection, "homeassistant/climate/" + climateUID + "/config", + json.getBytes(StandardCharsets.UTF_8)); + + assert latch.await(3, TimeUnit.SECONDS); + var discoveryResults = discoveryListener.getDiscoveryResults(); + assertThat(discoveryResults.size(), is(1)); + var result = discoveryResults.get(0); + + assertThat(result.getBridgeUID(), is(BRIDGE_UID)); + assertThat(result.getProperties().get(Thing.PROPERTY_MODEL_ID), + is("Radiator valve with thermostat (TS0601_thermostat)")); + assertThat(result.getProperties().get(Thing.PROPERTY_VENDOR), is("TuYa")); + assertThat(result.getProperties().get(Thing.PROPERTY_FIRMWARE_VERSION), is("Zigbee2MQTT 1.18.2")); + } + + private static class TestHomeAssistantDiscovery extends HomeAssistantDiscovery { + public TestHomeAssistantDiscovery(MqttChannelTypeProvider typeProvider) { + this.typeProvider = typeProvider; + } + } + + @NonNullByDefault + private static class LatchDiscoveryListener implements DiscoveryListener { + private final CopyOnWriteArrayList discoveryResults = new CopyOnWriteArrayList<>(); + private @Nullable CountDownLatch latch; + + public void thingDiscovered(DiscoveryService source, DiscoveryResult result) { + discoveryResults.add(result); + if (latch != null) { + latch.countDown(); + } + } + + public void thingRemoved(DiscoveryService source, ThingUID thingUID) { + } + + public @Nullable Collection removeOlderResults(DiscoveryService source, long timestamp, + @Nullable Collection thingTypeUIDs, @Nullable ThingUID bridgeUID) { + return Collections.emptyList(); + } + + public CopyOnWriteArrayList getDiscoveryResults() { + return discoveryResults; + } + + public CountDownLatch createWaitForThingsDiscoveredLatch(int count) { + final var newLatch = new CountDownLatch(count); + latch = newLatch; + return newLatch; + } + } +} From 720ef5b2d2e5e751f0b7ce555d3a4c3e5dc61899 Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Thu, 13 May 2021 16:18:26 +0300 Subject: [PATCH 05/23] MQTT.Homeassistant thing handler test added Signed-off-by: Anton Kharuzhy --- .../pom.xml | 7 + .../internal/HandlerConfiguration.java | 9 +- .../internal/AbstractHomeAssistantTests.java | 125 ++++++++++++++++ .../HomeAssistantDiscoveryTests.java | 56 +++---- .../HomeAssistantThingHandlerTests.java | 138 ++++++++++++++++++ .../component/configTS0601AutoLock.json | 26 ++++ 6 files changed, 324 insertions(+), 37 deletions(-) create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configTS0601AutoLock.json diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/pom.xml b/bundles/org.openhab.binding.mqtt.homeassistant/pom.xml index f4742d31daf9e..d7acac77c0e86 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/pom.xml +++ b/bundles/org.openhab.binding.mqtt.homeassistant/pom.xml @@ -27,5 +27,12 @@ ${project.version} provided + + + org.openhab.addons.bundles + org.openhab.transform.jinja + ${project.version} + test + diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HandlerConfiguration.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HandlerConfiguration.java index 11b23ad71772a..9936d91969389 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HandlerConfiguration.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HandlerConfiguration.java @@ -28,6 +28,9 @@ */ @NonNullByDefault public class HandlerConfiguration { + public static final String PROPERTY_BASETOPIC = "basetopic"; + public static final String PROPERTY_TOPICS = "topics"; + public static final String DEFAULT_BASETOPIC = "homeassistant"; /** * hint: cannot be final, or getConfigAs will not work. * The MQTT prefix topic @@ -64,7 +67,7 @@ public class HandlerConfiguration { public List topics; public HandlerConfiguration() { - this("homeassistant", Collections.emptyList()); + this(DEFAULT_BASETOPIC, Collections.emptyList()); } public HandlerConfiguration(String basetopic, List topics) { @@ -80,8 +83,8 @@ public HandlerConfiguration(String basetopic, List topics) { * @return the modified properties */ public > T appendToProperties(T properties) { - properties.put("basetopic", basetopic); - properties.put("topics", topics); + properties.put(PROPERTY_BASETOPIC, basetopic); + properties.put(PROPERTY_TOPICS, topics); return properties; } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java new file mode 100644 index 0000000000000..70c7aedfe4d47 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java @@ -0,0 +1,125 @@ +package org.openhab.binding.mqtt.homeassistant.internal; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider; +import org.openhab.binding.mqtt.generic.TransformationServiceProvider; +import org.openhab.binding.mqtt.handler.BrokerHandler; +import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.builder.BridgeBuilder; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.thing.type.ThingTypeBuilder; +import org.openhab.core.thing.type.ThingTypeRegistry; +import org.openhab.transform.jinja.internal.JinjaTransformationService; +import org.openhab.transform.jinja.internal.profiles.JinjaTransformationProfile; + +@SuppressWarnings({ "ConstantConditions" }) +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.WARN) +@NonNullByDefault +public abstract class AbstractHomeAssistantTests { + public static final String BINDING_ID = "mqtt"; + + public static final String BRIDGE_TYPE_ID = "broker"; + public static final String BRIDGE_TYPE_LABEL = "MQTT Broker"; + public static final ThingTypeUID BRIDGE_TYPE_UID = new ThingTypeUID(BINDING_ID, BRIDGE_TYPE_ID); + public static final String BRIDGE_ID = UUID.randomUUID().toString(); + public static final ThingUID BRIDGE_UID = new ThingUID(BRIDGE_TYPE_UID, BRIDGE_ID); + + public static final String HA_TYPE_ID = "homeassistant"; + public static final String HA_TYPE_LABEL = "Homeassistant"; + public static final ThingTypeUID HA_TYPE_UID = new ThingTypeUID(BINDING_ID, HA_TYPE_ID); + public static final String HA_ID = UUID.randomUUID().toString(); + public static final ThingUID HA_UID = new ThingUID(HA_TYPE_UID, HA_ID); + + protected @Mock @NonNullByDefault({}) MqttBrokerConnection bridgeConnection; + protected @Mock @NonNullByDefault({}) ThingTypeRegistry thingTypeRegistry; + protected @Mock @NonNullByDefault({}) TransformationServiceProvider transformationServiceProvider; + + protected final MqttChannelTypeProvider channelTypeProvider = spy(new MqttChannelTypeProvider(thingTypeRegistry)); + protected final Bridge bridgeThing = BridgeBuilder.create(BRIDGE_TYPE_UID, BRIDGE_UID).build(); + protected final BrokerHandler bridgeHandler = spy(new BrokerHandler(bridgeThing)); + protected final Thing haThing = ThingBuilder.create(HA_TYPE_UID, HA_UID).withBridge(BRIDGE_UID).build(); + + private final JinjaTransformationService jinjaTransformationService = new JinjaTransformationService(); + + @BeforeEach + public void beforeEachAbstractHomeAssistantTests() { + when(thingTypeRegistry.getThingType(BRIDGE_TYPE_UID)) + .thenReturn(ThingTypeBuilder.instance(BRIDGE_TYPE_UID, BRIDGE_TYPE_LABEL).build()); + when(thingTypeRegistry.getThingType(HA_TYPE_UID)) + .thenReturn(ThingTypeBuilder.instance(HA_TYPE_UID, HA_TYPE_LABEL).build()); + when(transformationServiceProvider + .getTransformationService(JinjaTransformationProfile.PROFILE_TYPE_UID.getId())) + .thenReturn(jinjaTransformationService); + + doReturn(CompletableFuture.completedFuture(true)).when(bridgeConnection).subscribe(any(), any()); + doReturn(CompletableFuture.completedFuture(true)).when(bridgeConnection).unsubscribe(any(), any()); + doReturn(CompletableFuture.completedFuture(true)).when(bridgeConnection).unsubscribeAll(); + doReturn(CompletableFuture.completedFuture(true)).when(bridgeConnection).publish(any(), any(), anyInt(), + anyBoolean()); + + // Return the mocked connection object if the bridge handler is asked for it + when(bridgeHandler.getConnectionAsync()).thenReturn(CompletableFuture.completedFuture(bridgeConnection)); + + bridgeThing.setStatusInfo(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.ONLINE.NONE, "")); + bridgeThing.setHandler(bridgeHandler); + + haThing.setStatusInfo(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.ONLINE.NONE, "")); + } + + /** + * @param relativePath path from src/test/java/org/openhab/binding/mqtt/homeassistant/internal + * @return path + */ + protected Path getResourcePath(String relativePath) { + try { + return Paths.get(AbstractHomeAssistantTests.class.getResource(relativePath).toURI()); + } catch (URISyntaxException e) { + Assertions.fail(e); + } + throw new IllegalArgumentException(); + } + + protected String getResourceAsString(String relativePath) { + try { + return Files.readString(getResourcePath(relativePath)); + } catch (IOException e) { + Assertions.fail(e); + } + throw new IllegalArgumentException(); + } + + protected byte[] getResourceAsByteArray(String relativePath) { + return getResourceAsString(relativePath).getBytes(StandardCharsets.UTF_8); + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscoveryTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscoveryTests.java index 2f9ca583113df..467d70af4abc0 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscoveryTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscoveryTests.java @@ -1,14 +1,12 @@ package org.openhab.binding.mqtt.homeassistant.internal.discovery; -import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.Collection; import java.util.Collections; -import java.util.UUID; +import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -18,64 +16,54 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider; -import org.openhab.binding.mqtt.homeassistant.internal.component.HAConfigurationTests; +import org.openhab.binding.mqtt.homeassistant.internal.AbstractHomeAssistantTests; +import org.openhab.binding.mqtt.homeassistant.internal.HandlerConfiguration; import org.openhab.core.config.discovery.DiscoveryListener; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryService; -import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingUID; -import org.openhab.core.thing.type.ThingType; -import org.openhab.core.thing.type.ThingTypeBuilder; -import org.openhab.core.thing.type.ThingTypeRegistry; +@SuppressWarnings({ "ConstantConditions", "unchecked" }) @ExtendWith(MockitoExtension.class) -public class HomeAssistantDiscoveryTests { - static final String BINDING_ID = "mqtt"; - static final String BRODGE_TYPE_ID = "homeassistant"; - static final String BRIDGE_TYPE_LABEL = "Homeassistant"; - static final ThingTypeUID BRIDGE_TYPE_UID = new ThingTypeUID(BINDING_ID, BRODGE_TYPE_ID); - static final String BRIDGE_ID = UUID.randomUUID().toString(); - static final ThingUID BRIDGE_UID = new ThingUID(BRIDGE_TYPE_UID, BRIDGE_ID); - - private @Mock MqttBrokerConnection brokerConnection; - private @Mock ThingTypeRegistry thingTypeRegistry; +public class HomeAssistantDiscoveryTests extends AbstractHomeAssistantTests { private HomeAssistantDiscovery discovery; @BeforeEach public void beforeEach() { - discovery = new TestHomeAssistantDiscovery(new MqttChannelTypeProvider(thingTypeRegistry)); - ThingType baseThingType = ThingTypeBuilder.instance(BRIDGE_TYPE_UID, BRIDGE_TYPE_LABEL).buildBridge(); - Mockito.when(thingTypeRegistry.getThingType(BRIDGE_TYPE_UID)).thenReturn(baseThingType); + discovery = new TestHomeAssistantDiscovery(channelTypeProvider); } @Test - public void testClimateThingDiscovery() throws Exception { - var json = Files.readString( - Path.of(HAConfigurationTests.class.getResource("configTS0601ClimateThermostat.json").toURI())); - var climateUID = "0x847127fffe11dd6a_climate_zigbee2mqtt"; + public void testOneThingDiscovery() throws Exception { var discoveryListener = new LatchDiscoveryListener(); var latch = discoveryListener.createWaitForThingsDiscoveredLatch(1); + // When discover one thing with two channels discovery.addDiscoveryListener(discoveryListener); - discovery.receivedMessage(BRIDGE_UID, brokerConnection, "homeassistant/climate/" + climateUID + "/config", - json.getBytes(StandardCharsets.UTF_8)); - + discovery.receivedMessage(HA_UID, bridgeConnection, + "homeassistant/climate/0x847127fffe11dd6a_climate_zigbee2mqtt/config", + getResourceAsByteArray("component/configTS0601ClimateThermostat.json")); + discovery.receivedMessage(HA_UID, bridgeConnection, + "homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config", + getResourceAsByteArray("component/configTS0601AutoLock.json")); + + // Then one thing found assert latch.await(3, TimeUnit.SECONDS); var discoveryResults = discoveryListener.getDiscoveryResults(); assertThat(discoveryResults.size(), is(1)); var result = discoveryResults.get(0); - - assertThat(result.getBridgeUID(), is(BRIDGE_UID)); + assertThat(result.getBridgeUID(), is(HA_UID)); assertThat(result.getProperties().get(Thing.PROPERTY_MODEL_ID), is("Radiator valve with thermostat (TS0601_thermostat)")); assertThat(result.getProperties().get(Thing.PROPERTY_VENDOR), is("TuYa")); assertThat(result.getProperties().get(Thing.PROPERTY_FIRMWARE_VERSION), is("Zigbee2MQTT 1.18.2")); + assertThat(result.getProperties().get(HandlerConfiguration.PROPERTY_BASETOPIC), is("homeassistant")); + assertThat((List) result.getProperties().get(HandlerConfiguration.PROPERTY_TOPICS), hasItems( + "climate/0x847127fffe11dd6a_climate_zigbee2mqtt", "switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt")); } private static class TestHomeAssistantDiscovery extends HomeAssistantDiscovery { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java new file mode 100644 index 0000000000000..a7c1deac50789 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java @@ -0,0 +1,138 @@ +package org.openhab.binding.mqtt.homeassistant.internal.handler; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.hamcrest.CoreMatchers; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openhab.binding.mqtt.homeassistant.internal.AbstractHomeAssistantTests; +import org.openhab.binding.mqtt.homeassistant.internal.HaID; +import org.openhab.binding.mqtt.homeassistant.internal.HandlerConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.component.Climate; +import org.openhab.binding.mqtt.homeassistant.internal.component.Switch; +import org.openhab.core.thing.binding.ThingHandlerCallback; + +@SuppressWarnings({ "ConstantConditions" }) +@ExtendWith(MockitoExtension.class) +public class HomeAssistantThingHandlerTests extends AbstractHomeAssistantTests { + private final static int SUBSCRIBE_TIMEOUT = 10000; + private final static int ATTRIBUTE_RECEIVE_TIMEOUT = 2000; + + private static final List CONFIG_TOPICS = Arrays.asList("climate/0x847127fffe11dd6a_climate_zigbee2mqtt", + "switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt", + + "sensor/0x1111111111111111_test_sensor_zigbee2mqtt", "camera/0x1111111111111111_test_camera_zigbee2mqtt", + + "cover/0x2222222222222222_test_cover_zigbee2mqtt", "fan/0x2222222222222222_test_fan_zigbee2mqtt", + "light/0x2222222222222222_test_light_zigbee2mqtt", "lock/0x2222222222222222_test_lock_zigbee2mqtt"); + + private static final List MQTT_TOPICS = CONFIG_TOPICS.stream() + .map(s -> HandlerConfiguration.DEFAULT_BASETOPIC + "/" + s + "/config").collect(Collectors.toList()); + + private @Mock ThingHandlerCallback callback; + private HomeAssistantThingHandler thingHandler; + + @BeforeEach + public void setup() { + final var config = haThing.getConfiguration(); + + config.put(HandlerConfiguration.PROPERTY_BASETOPIC, HandlerConfiguration.DEFAULT_BASETOPIC); + config.put(HandlerConfiguration.PROPERTY_TOPICS, CONFIG_TOPICS); + + when(callback.getBridge(eq(BRIDGE_UID))).thenReturn(bridgeThing); + + thingHandler = new HomeAssistantThingHandler(haThing, channelTypeProvider, transformationServiceProvider, + SUBSCRIBE_TIMEOUT, ATTRIBUTE_RECEIVE_TIMEOUT); + thingHandler.setConnection(bridgeConnection); + thingHandler.setCallback(callback); + thingHandler = spy(thingHandler); + } + + @Test + public void testInitialize() { + // When initialize + thingHandler.initialize(); + + verify(callback).statusUpdated(eq(haThing), any()); + // Expect a call to the bridge status changed, the start, the propertiesChanged method + verify(thingHandler).bridgeStatusChanged(any()); + verify(thingHandler, timeout(SUBSCRIBE_TIMEOUT)).start(any()); + + // Expect subscription on each topic from config + MQTT_TOPICS.forEach(t -> { + verify(bridgeConnection, timeout(SUBSCRIBE_TIMEOUT)).subscribe(eq(t), any()); + }); + + verify(thingHandler, never()).componentDiscovered(any(), any()); + assertThat(haThing.getChannels().size(), CoreMatchers.is(0)); + // Components discovered after messages in corresponding topics + var configTopic = "homeassistant/climate/0x847127fffe11dd6a_climate_zigbee2mqtt/config"; + thingHandler.discoverComponents.processMessage(configTopic, + getResourceAsByteArray("component/configTS0601ClimateThermostat.json")); + verify(thingHandler, times(1)).componentDiscovered(eq(new HaID(configTopic)), any(Climate.class)); + + thingHandler.delayedProcessing.forceProcessNow(); + assertThat(haThing.getChannels().size(), CoreMatchers.is(6)); + verify(channelTypeProvider, times(6)).setChannelType(any(), any()); + verify(channelTypeProvider, times(1)).setChannelGroupType(any(), any()); + + configTopic = "homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config"; + thingHandler.discoverComponents.processMessage(configTopic, + getResourceAsByteArray("component/configTS0601AutoLock.json")); + verify(thingHandler, times(2)).componentDiscovered(any(), any()); + verify(thingHandler, times(1)).componentDiscovered(eq(new HaID(configTopic)), any(Switch.class)); + + thingHandler.delayedProcessing.forceProcessNow(); + assertThat(haThing.getChannels().size(), CoreMatchers.is(7)); + verify(channelTypeProvider, times(7)).setChannelType(any(), any()); + verify(channelTypeProvider, times(2)).setChannelGroupType(any(), any()); + } + + @Test + public void testDispose() { + thingHandler.initialize(); + + // Expect subscription on each topic from config + CONFIG_TOPICS.forEach(t -> { + var fullTopic = HandlerConfiguration.DEFAULT_BASETOPIC + "/" + t + "/config"; + verify(bridgeConnection, timeout(SUBSCRIBE_TIMEOUT)).subscribe(eq(fullTopic), any()); + }); + thingHandler.discoverComponents.processMessage( + "homeassistant/climate/0x847127fffe11dd6a_climate_zigbee2mqtt/config", + getResourceAsByteArray("component/configTS0601ClimateThermostat.json")); + thingHandler.discoverComponents.processMessage( + "homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config", + getResourceAsByteArray("component/configTS0601AutoLock.json")); + thingHandler.delayedProcessing.forceProcessNow(); + assertThat(haThing.getChannels().size(), CoreMatchers.is(7)); + verify(channelTypeProvider, times(7)).setChannelType(any(), any()); + + // When dispose + thingHandler.dispose(); + + // Expect unsubscription on each topic from config + MQTT_TOPICS.forEach(t -> { + verify(bridgeConnection, timeout(SUBSCRIBE_TIMEOUT)).unsubscribe(eq(t), any()); + }); + + // Expect channel types removed, 6 for climate and 1 for switch + verify(channelTypeProvider, times(7)).removeChannelType(any()); + // Expect channel group types removed, 1 for each component + verify(channelTypeProvider, times(2)).removeChannelGroupType(any()); + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configTS0601AutoLock.json b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configTS0601AutoLock.json new file mode 100644 index 0000000000000..2ad9c4410d3fe --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/configTS0601AutoLock.json @@ -0,0 +1,26 @@ +{ + "availability": [ + { + "topic": "zigbee2mqtt/bridge/state" + } + ], + "command_topic": "zigbee2mqtt/th1/set/auto_lock", + "device": { + "identifiers": [ + "zigbee2mqtt_0x847127fffe11dd6a" + ], + "manufacturer": "TuYa", + "model": "Radiator valve with thermostat (TS0601_thermostat)", + "name": "th1", + "sw_version": "Zigbee2MQTT 1.18.2" + }, + "json_attributes_topic": "zigbee2mqtt/th1", + "name": "th1 auto lock", + "payload_off": "MANUAL", + "payload_on": "AUTO", + "state_off": "MANUAL", + "state_on": "AUTO", + "state_topic": "zigbee2mqtt/th1", + "unique_id": "0x847127fffe11dd6a_auto_lock_zigbee2mqtt", + "value_template": "{{ value_json.auto_lock }}" +} \ No newline at end of file From 2901820c4dcdca26bbb08116b4c8d24dc86878f6 Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Thu, 13 May 2021 16:18:42 +0300 Subject: [PATCH 06/23] MQTT.Homeassistant switch test added Signed-off-by: Anton Kharuzhy --- .../component/AbstractComponentTests.java | 86 ++++++++++++++++++ .../internal/component/SwitchTests.java | 89 +++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java new file mode 100644 index 0000000000000..a5f3f73079d3a --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java @@ -0,0 +1,86 @@ +package org.openhab.binding.mqtt.homeassistant.internal.component; + +import static org.hamcrest.MatcherAssert.assertThat; + +import java.nio.charset.StandardCharsets; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.hamcrest.CoreMatchers; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openhab.binding.mqtt.generic.AvailabilityTracker; +import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener; +import org.openhab.binding.mqtt.homeassistant.internal.AbstractHomeAssistantTests; +import org.openhab.binding.mqtt.homeassistant.internal.DiscoverComponents; +import org.openhab.binding.mqtt.homeassistant.internal.HaID; +import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.ChannelConfigurationTypeAdapterFactory; +import org.openhab.core.common.ThreadPoolManager; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +@ExtendWith(MockitoExtension.class) +public abstract class AbstractComponentTests extends AbstractHomeAssistantTests { + protected LatchComponentDiscoveredListener componentDiscoveredListener = new LatchComponentDiscoveredListener(); + protected ScheduledExecutorService executorService = ThreadPoolManager + .getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON); + protected @Mock ChannelStateUpdateListener channelStateUpdateListener; + protected @Mock AvailabilityTracker availabilityTracker; + protected Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ChannelConfigurationTypeAdapterFactory()) + .create(); + protected DiscoverComponents discoverComponents = new DiscoverComponents(HA_UID, executorService, + channelStateUpdateListener, availabilityTracker, gson, transformationServiceProvider); + + protected AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> discoverComponent(String topic, + String json) { + return discoverComponent(topic, json.getBytes(StandardCharsets.UTF_8)); + } + + protected AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> discoverComponent(String topic, + byte[] jsonPayload) { + var haID = new HaID(topic); + var latch = componentDiscoveredListener.createWaitForComponentDiscoveredLatch(1); + discoverComponents.startDiscovery(bridgeConnection, 0, Set.of(haID), componentDiscoveredListener); + discoverComponents.processMessage(topic, jsonPayload); + try { + assert latch.await(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + assertThat(e.getMessage(), false); + } + var component = componentDiscoveredListener.getDiscoveredComponent(); + assertThat(component, CoreMatchers.notNullValue()); + return component; + } + + @NonNullByDefault + protected static class LatchComponentDiscoveredListener implements DiscoverComponents.ComponentDiscovered { + private @Nullable CountDownLatch latch; + private @Nullable AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> discoveredComponent; + + public void componentDiscovered(HaID homeAssistantTopicID, AbstractComponent<@NonNull ?> component) { + discoveredComponent = component; + if (latch != null) { + latch.countDown(); + } + } + + public CountDownLatch createWaitForComponentDiscoveredLatch(int count) { + final var newLatch = new CountDownLatch(count); + latch = newLatch; + return newLatch; + } + + public @Nullable AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> getDiscoveredComponent() { + return discoveredComponent; + } + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java new file mode 100644 index 0000000000000..e6b2fda00908e --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java @@ -0,0 +1,89 @@ +package org.openhab.binding.mqtt.homeassistant.internal.component; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import org.junit.jupiter.api.Test; +import org.openhab.binding.mqtt.generic.values.OnOffValue; + +@SuppressWarnings("ConstantConditions") +public class SwitchTests extends AbstractComponentTests { + + @Test + public void testSwitchWithStateAndCommand() { + var component = discoverComponent("homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config", + "" + "{\n" + " \"availability\": [\n" + " {\n" + " \"topic\": \"zigbee2mqtt/bridge/state\"\n" + + " }\n" + " ],\n" + " \"command_topic\": \"zigbee2mqtt/th1/set/auto_lock\",\n" + + " \"device\": {\n" + " \"identifiers\": [\n" + + " \"zigbee2mqtt_0x847127fffe11dd6a\"\n" + " ],\n" + + " \"manufacturer\": \"TuYa\",\n" + + " \"model\": \"Radiator valve with thermostat (TS0601_thermostat)\",\n" + + " \"name\": \"th1\",\n" + " \"sw_version\": \"Zigbee2MQTT 1.18.2\"\n" + " },\n" + + " \"json_attributes_topic\": \"zigbee2mqtt/th1\",\n" + " \"name\": \"th1 auto lock\",\n" + + " \"payload_off\": \"MANUAL\",\n" + " \"payload_on\": \"AUTO\",\n" + + " \"state_off\": \"MANUAL\",\n" + " \"state_on\": \"AUTO\",\n" + + " \"state_topic\": \"zigbee2mqtt/th1\",\n" + + " \"unique_id\": \"0x847127fffe11dd6a_auto_lock_zigbee2mqtt\",\n" + + " \"value_template\": \"{{ value_json.auto_lock }}\"\n" + "}"); + + assertThat(component.channels.size(), is(1)); + assertThat(component.getName(), is("th1 auto lock")); + + var stateChannel = component.getChannel(Switch.switchChannelID); + assertThat(stateChannel.getChannel().getLabel(), is("state")); + assertThat(stateChannel.getState().getStateTopic(), is("zigbee2mqtt/th1")); + assertThat(stateChannel.getState().getCommandTopic(), is("zigbee2mqtt/th1/set/auto_lock")); + assertThat(stateChannel.getState().getCache(), is(instanceOf(OnOffValue.class))); + } + + @Test + public void testSwitchWithState() { + var component = discoverComponent("homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config", + "" + "{\n" + " \"availability\": [\n" + " {\n" + " \"topic\": \"zigbee2mqtt/bridge/state\"\n" + + " }\n" + " ],\n" + " \"device\": {\n" + " \"identifiers\": [\n" + + " \"zigbee2mqtt_0x847127fffe11dd6a\"\n" + " ],\n" + + " \"manufacturer\": \"TuYa\",\n" + + " \"model\": \"Radiator valve with thermostat (TS0601_thermostat)\",\n" + + " \"name\": \"th1\",\n" + " \"sw_version\": \"Zigbee2MQTT 1.18.2\"\n" + " },\n" + + " \"json_attributes_topic\": \"zigbee2mqtt/th1\",\n" + " \"name\": \"th1 auto lock\",\n" + + " \"state_off\": \"MANUAL\",\n" + " \"state_on\": \"AUTO\",\n" + + " \"state_topic\": \"zigbee2mqtt/th1\",\n" + + " \"unique_id\": \"0x847127fffe11dd6a_auto_lock_zigbee2mqtt\",\n" + + " \"value_template\": \"{{ value_json.auto_lock }}\"\n" + "}"); + + assertThat(component.channels.size(), is(1)); + assertThat(component.getName(), is("th1 auto lock")); + + var stateChannel = component.getChannel(Switch.switchChannelID); + assertThat(stateChannel.getChannel().getLabel(), is("state")); + assertThat(stateChannel.getState().getStateTopic(), is("zigbee2mqtt/th1")); + assertThat(stateChannel.getState().getCommandTopic(), is("")); + assertThat(stateChannel.getState().getCache(), is(instanceOf(OnOffValue.class))); + } + + @Test + public void testSwitchWithCommand() { + var component = discoverComponent("homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config", + "" + "{\n" + " \"availability\": [\n" + " {\n" + " \"topic\": \"zigbee2mqtt/bridge/state\"\n" + + " }\n" + " ],\n" + " \"command_topic\": \"zigbee2mqtt/th1/set/auto_lock\",\n" + + " \"device\": {\n" + " \"identifiers\": [\n" + + " \"zigbee2mqtt_0x847127fffe11dd6a\"\n" + " ],\n" + + " \"manufacturer\": \"TuYa\",\n" + + " \"model\": \"Radiator valve with thermostat (TS0601_thermostat)\",\n" + + " \"name\": \"th1\",\n" + " \"sw_version\": \"Zigbee2MQTT 1.18.2\"\n" + " },\n" + + " \"json_attributes_topic\": \"zigbee2mqtt/th1\",\n" + " \"name\": \"th1 auto lock\",\n" + + " \"payload_off\": \"MANUAL\",\n" + " \"payload_on\": \"AUTO\",\n" + + " \"unique_id\": \"0x847127fffe11dd6a_auto_lock_zigbee2mqtt\",\n" + + " \"value_template\": \"{{ value_json.auto_lock }}\"\n" + "}"); + + assertThat(component.channels.size(), is(1)); + assertThat(component.getName(), is("th1 auto lock")); + + var stateChannel = component.getChannel(Switch.switchChannelID); + assertThat(stateChannel.getChannel().getLabel(), is("state")); + assertThat(stateChannel.getState().getStateTopic(), is("")); + assertThat(stateChannel.getState().getCommandTopic(), is("zigbee2mqtt/th1/set/auto_lock")); + assertThat(stateChannel.getState().getCache(), is(instanceOf(OnOffValue.class))); + } +} From b907c4ee8a40c1a737759cbed978e9ca83cbcbf8 Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Thu, 13 May 2021 17:19:59 +0300 Subject: [PATCH 07/23] MQTT.Homeassistant Climate test added Signed-off-by: Anton Kharuzhy --- .../internal/component/Climate.java | 24 ++++---- .../component/AbstractComponentTests.java | 13 +++++ .../internal/component/ClimateTests.java | 57 +++++++++++++++++++ .../internal/component/SwitchTests.java | 22 ++----- 4 files changed, 87 insertions(+), 29 deletions(-) create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java index ad3b434b12165..8fbfcf2b56ffd 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java @@ -39,18 +39,18 @@ */ @NonNullByDefault public class Climate extends AbstractComponent { - private static final String ACTION_CH_ID = "action"; - private static final String AUX_CH_ID = "aux"; - private static final String AWAY_MODE_CH_ID = "awayMode"; - private static final String CURRENT_TEMPERATURE_CH_ID = "currentTemperature"; - private static final String FAN_MODE_CH_ID = "fanMode"; - private static final String HOLD_CH_ID = "hold"; - private static final String MODE_CH_ID = "mode"; - private static final String SWING_CH_ID = "swing"; - private static final String TEMPERATURE_CH_ID = "temperature"; - private static final String TEMPERATURE_HIGH_CH_ID = "temperatureHigh"; - private static final String TEMPERATURE_LOW_CH_ID = "temperatureLow"; - private static final String POWER_CH_ID = "power"; + public static final String ACTION_CH_ID = "action"; + public static final String AUX_CH_ID = "aux"; + public static final String AWAY_MODE_CH_ID = "awayMode"; + public static final String CURRENT_TEMPERATURE_CH_ID = "currentTemperature"; + public static final String FAN_MODE_CH_ID = "fanMode"; + public static final String HOLD_CH_ID = "hold"; + public static final String MODE_CH_ID = "mode"; + public static final String SWING_CH_ID = "swing"; + public static final String TEMPERATURE_CH_ID = "temperature"; + public static final String TEMPERATURE_HIGH_CH_ID = "temperatureHigh"; + public static final String TEMPERATURE_LOW_CH_ID = "temperatureLow"; + public static final String POWER_CH_ID = "power"; private static final String CELSIUM = "C"; private static final String FAHRENHEIT = "F"; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java index a5f3f73079d3a..2aaa4491408f6 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java @@ -1,5 +1,7 @@ package org.openhab.binding.mqtt.homeassistant.internal.component; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import java.nio.charset.StandardCharsets; @@ -17,6 +19,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.openhab.binding.mqtt.generic.AvailabilityTracker; import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener; +import org.openhab.binding.mqtt.generic.values.Value; import org.openhab.binding.mqtt.homeassistant.internal.AbstractHomeAssistantTests; import org.openhab.binding.mqtt.homeassistant.internal.DiscoverComponents; import org.openhab.binding.mqtt.homeassistant.internal.HaID; @@ -61,6 +64,16 @@ public abstract class AbstractComponentTests extends AbstractHomeAssistantTests return component; } + @SuppressWarnings("ConstantConditions") + protected static void assertChannel(AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> component, + String channelId, String stateTopic, String commandTopic, String label, Class valueClass) { + var stateChannel = component.getChannel(channelId); + assertThat(stateChannel.getChannel().getLabel(), is(label)); + assertThat(stateChannel.getState().getStateTopic(), is(stateTopic)); + assertThat(stateChannel.getState().getCommandTopic(), is(commandTopic)); + assertThat(stateChannel.getState().getCache(), is(instanceOf(valueClass))); + } + @NonNullByDefault protected static class LatchComponentDiscoveredListener implements DiscoverComponents.ComponentDiscovered { private @Nullable CountDownLatch latch; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java new file mode 100644 index 0000000000000..528ef55871292 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java @@ -0,0 +1,57 @@ +package org.openhab.binding.mqtt.homeassistant.internal.component; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import org.junit.jupiter.api.Test; +import org.openhab.binding.mqtt.generic.values.NumberValue; +import org.openhab.binding.mqtt.generic.values.OnOffValue; +import org.openhab.binding.mqtt.generic.values.TextValue; + +public class ClimateTests extends AbstractComponentTests { + + @Test + public void testTS0601Climate() { + var component = discoverComponent("homeassistant/climate/0x847127fffe11dd6a_climate_zigbee2mqtt/config", "{\n" + + " \"action_template\": \"{% set values = {'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'} %}{{ values[value_json.running_state] }}\",\n" + + " \"action_topic\": \"zigbee2mqtt/th1\",\n" + " \"availability\": [\n" + " {\n" + + " \"topic\": \"zigbee2mqtt/bridge/state\"\n" + " }\n" + " ],\n" + + " \"away_mode_command_topic\": \"zigbee2mqtt/th1/set/away_mode\",\n" + + " \"away_mode_state_template\": \"{{ value_json.away_mode }}\",\n" + + " \"away_mode_state_topic\": \"zigbee2mqtt/th1\",\n" + + " \"current_temperature_template\": \"{{ value_json.local_temperature }}\",\n" + + " \"current_temperature_topic\": \"zigbee2mqtt/th1\",\n" + " \"device\": {\n" + + " \"identifiers\": [\n" + " \"zigbee2mqtt_0x847127fffe11dd6a\"\n" + " ],\n" + + " \"manufacturer\": \"TuYa\",\n" + + " \"model\": \"Radiator valve with thermostat (TS0601_thermostat)\",\n" + + " \"name\": \"th1\",\n" + " \"sw_version\": \"Zigbee2MQTT 1.18.2\"\n" + " },\n" + + " \"hold_command_topic\": \"zigbee2mqtt/th1/set/preset\",\n" + " \"hold_modes\": [\n" + + " \"schedule\",\n" + " \"manual\",\n" + " \"boost\",\n" + " \"complex\",\n" + + " \"comfort\",\n" + " \"eco\"\n" + " ],\n" + + " \"hold_state_template\": \"{{ value_json.preset }}\",\n" + + " \"hold_state_topic\": \"zigbee2mqtt/th1\",\n" + + " \"json_attributes_topic\": \"zigbee2mqtt/th1\",\n" + " \"max_temp\": \"35\",\n" + + " \"min_temp\": \"5\",\n" + " \"mode_command_topic\": \"zigbee2mqtt/th1/set/system_mode\",\n" + + " \"mode_state_template\": \"{{ value_json.system_mode }}\",\n" + + " \"mode_state_topic\": \"zigbee2mqtt/th1\",\n" + " \"modes\": [\n" + " \"heat\",\n" + + " \"auto\",\n" + " \"off\"\n" + " ],\n" + " \"name\": \"th1\",\n" + " \"temp_step\": 0.5,\n" + + " \"temperature_command_topic\": \"zigbee2mqtt/th1/set/current_heating_setpoint\",\n" + + " \"temperature_state_template\": \"{{ value_json.current_heating_setpoint }}\",\n" + + " \"temperature_state_topic\": \"zigbee2mqtt/th1\",\n" + " \"temperature_unit\": \"C\",\n" + + " \"unique_id\": \"0x847127fffe11dd6a_climate_zigbee2mqtt\"\n" + "}"); + + assertThat(component.channels.size(), is(6)); + assertThat(component.getName(), is("th1")); + + assertChannel(component, Climate.ACTION_CH_ID, "zigbee2mqtt/th1", "", "th1", TextValue.class); + assertChannel(component, Climate.AWAY_MODE_CH_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/set/away_mode", "th1", + OnOffValue.class); + assertChannel(component, Climate.CURRENT_TEMPERATURE_CH_ID, "zigbee2mqtt/th1", "", "th1", NumberValue.class); + assertChannel(component, Climate.HOLD_CH_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/set/preset", "th1", + TextValue.class); + assertChannel(component, Climate.MODE_CH_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/set/system_mode", "th1", + TextValue.class); + assertChannel(component, Climate.TEMPERATURE_CH_ID, "zigbee2mqtt/th1", + "zigbee2mqtt/th1/set/current_heating_setpoint", "th1", NumberValue.class); + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java index e6b2fda00908e..a5713a5e6526f 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java @@ -1,13 +1,11 @@ package org.openhab.binding.mqtt.homeassistant.internal.component; -import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import org.junit.jupiter.api.Test; import org.openhab.binding.mqtt.generic.values.OnOffValue; -@SuppressWarnings("ConstantConditions") public class SwitchTests extends AbstractComponentTests { @Test @@ -30,11 +28,8 @@ public void testSwitchWithStateAndCommand() { assertThat(component.channels.size(), is(1)); assertThat(component.getName(), is("th1 auto lock")); - var stateChannel = component.getChannel(Switch.switchChannelID); - assertThat(stateChannel.getChannel().getLabel(), is("state")); - assertThat(stateChannel.getState().getStateTopic(), is("zigbee2mqtt/th1")); - assertThat(stateChannel.getState().getCommandTopic(), is("zigbee2mqtt/th1/set/auto_lock")); - assertThat(stateChannel.getState().getCache(), is(instanceOf(OnOffValue.class))); + assertChannel(component, Switch.switchChannelID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/set/auto_lock", "state", + OnOffValue.class); } @Test @@ -55,11 +50,7 @@ public void testSwitchWithState() { assertThat(component.channels.size(), is(1)); assertThat(component.getName(), is("th1 auto lock")); - var stateChannel = component.getChannel(Switch.switchChannelID); - assertThat(stateChannel.getChannel().getLabel(), is("state")); - assertThat(stateChannel.getState().getStateTopic(), is("zigbee2mqtt/th1")); - assertThat(stateChannel.getState().getCommandTopic(), is("")); - assertThat(stateChannel.getState().getCache(), is(instanceOf(OnOffValue.class))); + assertChannel(component, Switch.switchChannelID, "zigbee2mqtt/th1", "", "state", OnOffValue.class); } @Test @@ -80,10 +71,7 @@ public void testSwitchWithCommand() { assertThat(component.channels.size(), is(1)); assertThat(component.getName(), is("th1 auto lock")); - var stateChannel = component.getChannel(Switch.switchChannelID); - assertThat(stateChannel.getChannel().getLabel(), is("state")); - assertThat(stateChannel.getState().getStateTopic(), is("")); - assertThat(stateChannel.getState().getCommandTopic(), is("zigbee2mqtt/th1/set/auto_lock")); - assertThat(stateChannel.getState().getCache(), is(instanceOf(OnOffValue.class))); + assertChannel(component, Switch.switchChannelID, "", "zigbee2mqtt/th1/set/auto_lock", "state", + OnOffValue.class); } } From 7edef9d7f843cea70a69aa62f7a73c95b17db5e6 Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Thu, 13 May 2021 17:29:34 +0300 Subject: [PATCH 08/23] MQTT.Homeassistant author header added Signed-off-by: Anton Kharuzhy --- .../homeassistant/internal/AbstractHomeAssistantTests.java | 5 +++++ .../internal/component/AbstractComponentTests.java | 6 ++++++ .../mqtt/homeassistant/internal/component/ClimateTests.java | 5 +++++ .../mqtt/homeassistant/internal/component/SwitchTests.java | 5 +++++ .../internal/discovery/HomeAssistantDiscoveryTests.java | 5 +++++ .../internal/handler/HomeAssistantThingHandlerTests.java | 5 +++++ 6 files changed, 31 insertions(+) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java index 70c7aedfe4d47..f6ad3cc35dfa1 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java @@ -42,6 +42,11 @@ import org.openhab.transform.jinja.internal.JinjaTransformationService; import org.openhab.transform.jinja.internal.profiles.JinjaTransformationProfile; +/** + * Abstract class for HomeAssistant unit tests. + * + * @author Anton Kharuzhy - Initial contribution + */ @SuppressWarnings({ "ConstantConditions" }) @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.WARN) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java index 2aaa4491408f6..dd468a1990bfd 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java @@ -30,6 +30,12 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; +/** + * Abstract class for components tests. + * TODO: need a way to test all channel properties, not only topics. + * + * @author Anton Kharuzhy - Initial contribution + */ @ExtendWith(MockitoExtension.class) public abstract class AbstractComponentTests extends AbstractHomeAssistantTests { protected LatchComponentDiscoveredListener componentDiscoveredListener = new LatchComponentDiscoveredListener(); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java index 528ef55871292..edc4d1fec3062 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java @@ -8,6 +8,11 @@ import org.openhab.binding.mqtt.generic.values.OnOffValue; import org.openhab.binding.mqtt.generic.values.TextValue; +/** + * Tests for {@link Climate} + * + * @author Anton Kharuzhy - Initial contribution + */ public class ClimateTests extends AbstractComponentTests { @Test diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java index a5713a5e6526f..2b713c7ddeea5 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java @@ -6,6 +6,11 @@ import org.junit.jupiter.api.Test; import org.openhab.binding.mqtt.generic.values.OnOffValue; +/** + * Tests for {@link Switch} + * + * @author Anton Kharuzhy - Initial contribution + */ public class SwitchTests extends AbstractComponentTests { @Test diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscoveryTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscoveryTests.java index 467d70af4abc0..bf33ecb6b835e 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscoveryTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscoveryTests.java @@ -27,6 +27,11 @@ import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingUID; +/** + * Tests for {@link HomeAssistantDiscovery} + * + * @author Anton Kharuzhy - Initial contribution + */ @SuppressWarnings({ "ConstantConditions", "unchecked" }) @ExtendWith(MockitoExtension.class) public class HomeAssistantDiscoveryTests extends AbstractHomeAssistantTests { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java index a7c1deac50789..73796b737a453 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java @@ -27,6 +27,11 @@ import org.openhab.binding.mqtt.homeassistant.internal.component.Switch; import org.openhab.core.thing.binding.ThingHandlerCallback; +/** + * Tests for {@link HomeAssistantThingHandler} + * + * @author Anton Kharuzhy - Initial contribution + */ @SuppressWarnings({ "ConstantConditions" }) @ExtendWith(MockitoExtension.class) public class HomeAssistantThingHandlerTests extends AbstractHomeAssistantTests { From 5ac0699dc8a95bdc3369cf1614b3eeee4499557a Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Thu, 13 May 2021 17:31:52 +0300 Subject: [PATCH 09/23] MQTT.Homeassistant copyright header added Signed-off-by: Anton Kharuzhy --- .../internal/AbstractHomeAssistantTests.java | 12 ++++++++++++ .../internal/component/AbstractComponentTests.java | 12 ++++++++++++ .../internal/component/ClimateTests.java | 12 ++++++++++++ .../internal/component/SwitchTests.java | 12 ++++++++++++ .../discovery/HomeAssistantDiscoveryTests.java | 12 ++++++++++++ .../handler/HomeAssistantThingHandlerTests.java | 12 ++++++++++++ 6 files changed, 72 insertions(+) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java index f6ad3cc35dfa1..5f9ffe6c20546 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java @@ -1,3 +1,15 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal; import static org.mockito.Mockito.any; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java index dd468a1990bfd..8ad9deddb8877 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java @@ -1,3 +1,15 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.component; import static org.hamcrest.CoreMatchers.instanceOf; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java index edc4d1fec3062..36de8f98f2733 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java @@ -1,3 +1,15 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.component; import static org.hamcrest.CoreMatchers.is; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java index 2b713c7ddeea5..ef24517e5efa1 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java @@ -1,3 +1,15 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.component; import static org.hamcrest.CoreMatchers.is; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscoveryTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscoveryTests.java index bf33ecb6b835e..4218114d1e93e 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscoveryTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscoveryTests.java @@ -1,3 +1,15 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.discovery; import static org.hamcrest.CoreMatchers.hasItems; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java index 73796b737a453..700db06025a6a 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java @@ -1,3 +1,15 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.handler; import static org.hamcrest.MatcherAssert.assertThat; From eebc82856c0c31e10bfd1f114adab3976345e5f1 Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Thu, 13 May 2021 17:48:14 +0300 Subject: [PATCH 10/23] MQTT.Homeassistant test fixed Signed-off-by: Anton Kharuzhy --- .../homeassistant/internal/AbstractHomeAssistantTests.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java index 5f9ffe6c20546..5065db88197e2 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java @@ -82,7 +82,9 @@ public abstract class AbstractHomeAssistantTests { protected @Mock @NonNullByDefault({}) ThingTypeRegistry thingTypeRegistry; protected @Mock @NonNullByDefault({}) TransformationServiceProvider transformationServiceProvider; - protected final MqttChannelTypeProvider channelTypeProvider = spy(new MqttChannelTypeProvider(thingTypeRegistry)); + @SuppressWarnings("NotNullFieldNotInitialized") + protected @NonNullByDefault({}) MqttChannelTypeProvider channelTypeProvider; + protected final Bridge bridgeThing = BridgeBuilder.create(BRIDGE_TYPE_UID, BRIDGE_UID).build(); protected final BrokerHandler bridgeHandler = spy(new BrokerHandler(bridgeThing)); protected final Thing haThing = ThingBuilder.create(HA_TYPE_UID, HA_UID).withBridge(BRIDGE_UID).build(); @@ -99,6 +101,8 @@ public void beforeEachAbstractHomeAssistantTests() { .getTransformationService(JinjaTransformationProfile.PROFILE_TYPE_UID.getId())) .thenReturn(jinjaTransformationService); + channelTypeProvider = spy(new MqttChannelTypeProvider(thingTypeRegistry)); + doReturn(CompletableFuture.completedFuture(true)).when(bridgeConnection).subscribe(any(), any()); doReturn(CompletableFuture.completedFuture(true)).when(bridgeConnection).unsubscribe(any(), any()); doReturn(CompletableFuture.completedFuture(true)).when(bridgeConnection).unsubscribeAll(); From 15f49dc1ae81e23a7e3859c8163c1cbf7c9ad2b7 Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Fri, 14 May 2021 16:14:52 +0300 Subject: [PATCH 11/23] MQTT.Homeassistant test fixed Signed-off-by: Anton Kharuzhy --- .../homeassistant/internal/component/AbstractComponentTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java index 8ad9deddb8877..7aa2cef3c8701 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java @@ -74,7 +74,6 @@ public abstract class AbstractComponentTests extends AbstractHomeAssistantTests try { assert latch.await(1, TimeUnit.SECONDS); } catch (InterruptedException e) { - e.printStackTrace(); assertThat(e.getMessage(), false); } var component = componentDiscoveredListener.getDiscoveredComponent(); From f1ae619e8952ae1f3ae5a93635361e98bfe4f99f Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Mon, 17 May 2021 15:07:06 +0300 Subject: [PATCH 12/23] MQTT.Homeassistant test infrastructure updated. Added tests with mqtt publishing and commands posting. Signed-off-by: Anton Kharuzhy --- .../internal/AbstractHomeAssistantTests.java | 48 ++++- .../component/AbstractComponentTests.java | 182 +++++++++++++++--- .../internal/component/ClimateTests.java | 163 +++++++++++++--- .../internal/component/SwitchTests.java | 35 +++- .../HomeAssistantThingHandlerTests.java | 2 +- 5 files changed, 363 insertions(+), 67 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java index 5065db88197e2..416ccc72fc82b 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java @@ -15,6 +15,7 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -25,6 +26,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -40,6 +45,7 @@ import org.openhab.binding.mqtt.generic.TransformationServiceProvider; import org.openhab.binding.mqtt.handler.BrokerHandler; import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; +import org.openhab.core.io.transport.mqtt.MqttMessageSubscriber; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; @@ -88,6 +94,7 @@ public abstract class AbstractHomeAssistantTests { protected final Bridge bridgeThing = BridgeBuilder.create(BRIDGE_TYPE_UID, BRIDGE_UID).build(); protected final BrokerHandler bridgeHandler = spy(new BrokerHandler(bridgeThing)); protected final Thing haThing = ThingBuilder.create(HA_TYPE_UID, HA_UID).withBridge(BRIDGE_UID).build(); + protected final Map> subscriptions = new HashMap<>(); private final JinjaTransformationService jinjaTransformationService = new JinjaTransformationService(); @@ -103,11 +110,7 @@ public void beforeEachAbstractHomeAssistantTests() { channelTypeProvider = spy(new MqttChannelTypeProvider(thingTypeRegistry)); - doReturn(CompletableFuture.completedFuture(true)).when(bridgeConnection).subscribe(any(), any()); - doReturn(CompletableFuture.completedFuture(true)).when(bridgeConnection).unsubscribe(any(), any()); - doReturn(CompletableFuture.completedFuture(true)).when(bridgeConnection).unsubscribeAll(); - doReturn(CompletableFuture.completedFuture(true)).when(bridgeConnection).publish(any(), any(), anyInt(), - anyBoolean()); + setupConnection(); // Return the mocked connection object if the bridge handler is asked for it when(bridgeHandler.getConnectionAsync()).thenReturn(CompletableFuture.completedFuture(bridgeConnection)); @@ -118,6 +121,37 @@ public void beforeEachAbstractHomeAssistantTests() { haThing.setStatusInfo(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.ONLINE.NONE, "")); } + protected void setupConnection() { + doAnswer(invocation -> { + final var topic = (String) invocation.getArgument(0); + final var subscriber = (MqttMessageSubscriber) invocation.getArgument(1); + final var topicSubscriptions = subscriptions.getOrDefault(topic, new HashSet<>()); + + topicSubscriptions.add(subscriber); + subscriptions.put(topic, topicSubscriptions); + return CompletableFuture.completedFuture(true); + }).when(bridgeConnection).subscribe(any(), any()); + + doAnswer(invocation -> { + final var topic = (String) invocation.getArgument(0); + final var subscriber = (MqttMessageSubscriber) invocation.getArgument(1); + final var topicSubscriptions = subscriptions.get(topic); + + if (topicSubscriptions != null) { + topicSubscriptions.remove(subscriber); + } + return CompletableFuture.completedFuture(true); + }).when(bridgeConnection).unsubscribe(any(), any()); + + doAnswer(invocation -> { + subscriptions.clear(); + return CompletableFuture.completedFuture(true); + }).when(bridgeConnection).unsubscribeAll(); + + doReturn(CompletableFuture.completedFuture(true)).when(bridgeConnection).publish(any(), any(), anyInt(), + anyBoolean()); + } + /** * @param relativePath path from src/test/java/org/openhab/binding/mqtt/homeassistant/internal * @return path @@ -143,4 +177,8 @@ protected String getResourceAsString(String relativePath) { protected byte[] getResourceAsByteArray(String relativePath) { return getResourceAsString(relativePath).getBytes(StandardCharsets.UTF_8); } + + protected static String configTopicToMqtt(String configTopic) { + return HandlerConfiguration.DEFAULT_BASETOPIC + "/" + configTopic + "/config"; + } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java index 7aa2cef3c8701..045140212ec50 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java @@ -15,32 +15,37 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.hamcrest.CoreMatchers; -import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.BeforeEach; import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.openhab.binding.mqtt.generic.AvailabilityTracker; -import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener; +import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider; +import org.openhab.binding.mqtt.generic.TransformationServiceProvider; import org.openhab.binding.mqtt.generic.values.Value; import org.openhab.binding.mqtt.homeassistant.internal.AbstractHomeAssistantTests; -import org.openhab.binding.mqtt.homeassistant.internal.DiscoverComponents; import org.openhab.binding.mqtt.homeassistant.internal.HaID; +import org.openhab.binding.mqtt.homeassistant.internal.HandlerConfiguration; import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; -import org.openhab.binding.mqtt.homeassistant.internal.config.ChannelConfigurationTypeAdapterFactory; -import org.openhab.core.common.ThreadPoolManager; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; +import org.openhab.binding.mqtt.homeassistant.internal.handler.HomeAssistantThingHandler; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.types.State; /** * Abstract class for components tests. @@ -48,39 +53,85 @@ * * @author Anton Kharuzhy - Initial contribution */ -@ExtendWith(MockitoExtension.class) +@SuppressWarnings({ "ConstantConditions" }) public abstract class AbstractComponentTests extends AbstractHomeAssistantTests { - protected LatchComponentDiscoveredListener componentDiscoveredListener = new LatchComponentDiscoveredListener(); - protected ScheduledExecutorService executorService = ThreadPoolManager - .getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON); - protected @Mock ChannelStateUpdateListener channelStateUpdateListener; - protected @Mock AvailabilityTracker availabilityTracker; - protected Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ChannelConfigurationTypeAdapterFactory()) - .create(); - protected DiscoverComponents discoverComponents = new DiscoverComponents(HA_UID, executorService, - channelStateUpdateListener, availabilityTracker, gson, transformationServiceProvider); - - protected AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> discoverComponent(String topic, + private final static int SUBSCRIBE_TIMEOUT = 10000; + private final static int ATTRIBUTE_RECEIVE_TIMEOUT = 2000; + + private @Mock ThingHandlerCallback callback; + private LatchThingHandler thingHandler; + + @BeforeEach + public void setupThingHandler() { + final var config = haThing.getConfiguration(); + + config.put(HandlerConfiguration.PROPERTY_BASETOPIC, HandlerConfiguration.DEFAULT_BASETOPIC); + config.put(HandlerConfiguration.PROPERTY_TOPICS, getConfigTopics()); + + when(callback.getBridge(eq(BRIDGE_UID))).thenReturn(bridgeThing); + + thingHandler = new LatchThingHandler(haThing, channelTypeProvider, transformationServiceProvider, + SUBSCRIBE_TIMEOUT, ATTRIBUTE_RECEIVE_TIMEOUT); + thingHandler.setConnection(bridgeConnection); + thingHandler.setCallback(callback); + thingHandler = spy(thingHandler); + + thingHandler.initialize(); + } + + /** + * {@link org.openhab.binding.mqtt.homeassistant.internal.DiscoverComponents} will wait a config on specified + * topics. + * Topics in config must be without prefix and suffix, they can be converted to full with method + * {@link #configTopicToMqtt(String)} + * + * @return config topics + */ + protected abstract Set getConfigTopics(); + + /** + * Process payload to discover and configure component. Topic should be added to {@link #getConfigTopics()} + * + * @param mqttTopic mqtt topic with configuration + * @param json configuration payload in Json + * @return discovered component + */ + protected AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> discoverComponent(String mqttTopic, String json) { - return discoverComponent(topic, json.getBytes(StandardCharsets.UTF_8)); + return discoverComponent(mqttTopic, json.getBytes(StandardCharsets.UTF_8)); } - protected AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> discoverComponent(String topic, + /** + * Process payload to discover and configure component. Topic should be added to {@link #getConfigTopics()} + * + * @param mqttTopic mqtt topic with configuration + * @param jsonPayload configuration payload in Json + * @return discovered component + */ + protected AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> discoverComponent(String mqttTopic, byte[] jsonPayload) { - var haID = new HaID(topic); - var latch = componentDiscoveredListener.createWaitForComponentDiscoveredLatch(1); - discoverComponents.startDiscovery(bridgeConnection, 0, Set.of(haID), componentDiscoveredListener); - discoverComponents.processMessage(topic, jsonPayload); + var latch = thingHandler.createWaitForComponentDiscoveredLatch(1); + assertThat(publishMessage(mqttTopic, jsonPayload), is(true)); try { assert latch.await(1, TimeUnit.SECONDS); } catch (InterruptedException e) { assertThat(e.getMessage(), false); } - var component = componentDiscoveredListener.getDiscoveredComponent(); + var component = thingHandler.getDiscoveredComponent(); assertThat(component, CoreMatchers.notNullValue()); return component; } + /** + * Assert channel topics, label and value class + * + * @param component component + * @param channelId channel + * @param stateTopic state topic or empty string + * @param commandTopic command topic or empty string + * @param label label + * @param valueClass value class + */ @SuppressWarnings("ConstantConditions") protected static void assertChannel(AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> component, String channelId, String stateTopic, String commandTopic, String label, Class valueClass) { @@ -91,12 +142,81 @@ protected static void assertChannel(AbstractComponent<@NonNull ? extends Abstrac assertThat(stateChannel.getState().getCache(), is(instanceOf(valueClass))); } + /** + * Assert channel state + * + * @param component component + * @param channelId channel + * @param state expected state + */ + protected static void assertState(AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> component, + String channelId, State state) { + assertThat(component.getChannel(channelId).getState().getCache().getChannelState(), is(state)); + } + + /** + * Assert that given payload was published on given topic. + * + * @param mqttTopic Mqtt topic + * @param payload payload + */ + protected void assertPublished(String mqttTopic, String payload) { + verify(bridgeConnection).publish(eq(mqttTopic), eq(payload.getBytes(StandardCharsets.UTF_8)), anyInt(), + anyBoolean()); + } + + /** + * Assert that given payload was not published on given topic. + * + * @param mqttTopic Mqtt topic + * @param payload payload + */ + protected void assertNotPublished(String mqttTopic, String payload) { + verify(bridgeConnection, never()).publish(eq(mqttTopic), eq(payload.getBytes(StandardCharsets.UTF_8)), anyInt(), + anyBoolean()); + } + + /** + * Publish payload to all subscribers on specified topic. + * + * @param mqttTopic Mqtt topic + * @param payload payload + * @return true when at least one subscriber found + */ + protected boolean publishMessage(String mqttTopic, String payload) { + return publishMessage(mqttTopic, payload.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Publish payload to all subscribers on specified topic. + * + * @param mqttTopic Mqtt topic + * @param payload payload + * @return true when at least one subscriber found + */ + protected boolean publishMessage(String mqttTopic, byte[] payload) { + final var topicSubscribers = subscriptions.get(mqttTopic); + + if (topicSubscribers != null && !topicSubscribers.isEmpty()) { + topicSubscribers.forEach(mqttMessageSubscriber -> mqttMessageSubscriber.processMessage(mqttTopic, payload)); + return true; + } + return false; + } + @NonNullByDefault - protected static class LatchComponentDiscoveredListener implements DiscoverComponents.ComponentDiscovered { + protected static class LatchThingHandler extends HomeAssistantThingHandler { private @Nullable CountDownLatch latch; private @Nullable AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> discoveredComponent; + public LatchThingHandler(Thing thing, MqttChannelTypeProvider channelTypeProvider, + TransformationServiceProvider transformationServiceProvider, int subscribeTimeout, + int attributeReceiveTimeout) { + super(thing, channelTypeProvider, transformationServiceProvider, subscribeTimeout, attributeReceiveTimeout); + } + public void componentDiscovered(HaID homeAssistantTopicID, AbstractComponent<@NonNull ?> component) { + accept(List.of(component)); discoveredComponent = component; if (latch != null) { latch.countDown(); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java index 36de8f98f2733..a1ebfcb1e3ebc 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java @@ -15,47 +15,115 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import java.util.Set; + import org.junit.jupiter.api.Test; import org.openhab.binding.mqtt.generic.values.NumberValue; import org.openhab.binding.mqtt.generic.values.OnOffValue; import org.openhab.binding.mqtt.generic.values.TextValue; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; /** * Tests for {@link Climate} * * @author Anton Kharuzhy - Initial contribution */ +@SuppressWarnings("ConstantConditions") public class ClimateTests extends AbstractComponentTests { + public static final String CONFIG_TOPIC = "climate/0x847127fffe11dd6a_climate_zigbee2mqtt"; @Test public void testTS0601Climate() { - var component = discoverComponent("homeassistant/climate/0x847127fffe11dd6a_climate_zigbee2mqtt/config", "{\n" - + " \"action_template\": \"{% set values = {'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'} %}{{ values[value_json.running_state] }}\",\n" - + " \"action_topic\": \"zigbee2mqtt/th1\",\n" + " \"availability\": [\n" + " {\n" - + " \"topic\": \"zigbee2mqtt/bridge/state\"\n" + " }\n" + " ],\n" - + " \"away_mode_command_topic\": \"zigbee2mqtt/th1/set/away_mode\",\n" - + " \"away_mode_state_template\": \"{{ value_json.away_mode }}\",\n" - + " \"away_mode_state_topic\": \"zigbee2mqtt/th1\",\n" - + " \"current_temperature_template\": \"{{ value_json.local_temperature }}\",\n" - + " \"current_temperature_topic\": \"zigbee2mqtt/th1\",\n" + " \"device\": {\n" - + " \"identifiers\": [\n" + " \"zigbee2mqtt_0x847127fffe11dd6a\"\n" + " ],\n" - + " \"manufacturer\": \"TuYa\",\n" - + " \"model\": \"Radiator valve with thermostat (TS0601_thermostat)\",\n" - + " \"name\": \"th1\",\n" + " \"sw_version\": \"Zigbee2MQTT 1.18.2\"\n" + " },\n" - + " \"hold_command_topic\": \"zigbee2mqtt/th1/set/preset\",\n" + " \"hold_modes\": [\n" - + " \"schedule\",\n" + " \"manual\",\n" + " \"boost\",\n" + " \"complex\",\n" - + " \"comfort\",\n" + " \"eco\"\n" + " ],\n" - + " \"hold_state_template\": \"{{ value_json.preset }}\",\n" - + " \"hold_state_topic\": \"zigbee2mqtt/th1\",\n" - + " \"json_attributes_topic\": \"zigbee2mqtt/th1\",\n" + " \"max_temp\": \"35\",\n" - + " \"min_temp\": \"5\",\n" + " \"mode_command_topic\": \"zigbee2mqtt/th1/set/system_mode\",\n" - + " \"mode_state_template\": \"{{ value_json.system_mode }}\",\n" - + " \"mode_state_topic\": \"zigbee2mqtt/th1\",\n" + " \"modes\": [\n" + " \"heat\",\n" - + " \"auto\",\n" + " \"off\"\n" + " ],\n" + " \"name\": \"th1\",\n" + " \"temp_step\": 0.5,\n" - + " \"temperature_command_topic\": \"zigbee2mqtt/th1/set/current_heating_setpoint\",\n" - + " \"temperature_state_template\": \"{{ value_json.current_heating_setpoint }}\",\n" - + " \"temperature_state_topic\": \"zigbee2mqtt/th1\",\n" + " \"temperature_unit\": \"C\",\n" - + " \"unique_id\": \"0x847127fffe11dd6a_climate_zigbee2mqtt\"\n" + "}"); + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), "{\n" + + " \"action_template\": \"{% set values = {'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'} %}{{ values[value_json.running_state] }}\",\n" + + " \"action_topic\": \"zigbee2mqtt/th1\", \"availability\": [ {\n" + + " \"topic\": \"zigbee2mqtt/bridge/state\" } ],\n" + + " \"away_mode_command_topic\": \"zigbee2mqtt/th1/set/away_mode\",\n" + + " \"away_mode_state_template\": \"{{ value_json.away_mode }}\",\n" + + " \"away_mode_state_topic\": \"zigbee2mqtt/th1\",\n" + + " \"current_temperature_template\": \"{{ value_json.local_temperature }}\",\n" + + " \"current_temperature_topic\": \"zigbee2mqtt/th1\", \"device\": {\n" + + " \"identifiers\": [ \"zigbee2mqtt_0x847127fffe11dd6a\" ],\n" + " \"manufacturer\": \"TuYa\",\n" + + " \"model\": \"Radiator valve with thermostat (TS0601_thermostat)\",\n" + + " \"name\": \"th1\", \"sw_version\": \"Zigbee2MQTT 1.18.2\" },\n" + + " \"hold_command_topic\": \"zigbee2mqtt/th1/set/preset\", \"hold_modes\": [\n" + + " \"schedule\", \"manual\", \"boost\", \"complex\",\n" + " \"comfort\", \"eco\" ],\n" + + " \"hold_state_template\": \"{{ value_json.preset }}\",\n" + + " \"hold_state_topic\": \"zigbee2mqtt/th1\",\n" + + " \"json_attributes_topic\": \"zigbee2mqtt/th1\", \"max_temp\": \"35\",\n" + + " \"min_temp\": \"5\", \"mode_command_topic\": \"zigbee2mqtt/th1/set/system_mode\",\n" + + " \"mode_state_template\": \"{{ value_json.system_mode }}\",\n" + + " \"mode_state_topic\": \"zigbee2mqtt/th1\", \"modes\": [ \"heat\",\n" + + " \"auto\", \"off\" ], \"name\": \"th1\", \"temp_step\": 0.5,\n" + + " \"temperature_command_topic\": \"zigbee2mqtt/th1/set/current_heating_setpoint\",\n" + + " \"temperature_state_template\": \"{{ value_json.current_heating_setpoint }}\",\n" + + " \"temperature_state_topic\": \"zigbee2mqtt/th1\", \"temperature_unit\": \"C\",\n" + + " \"unique_id\": \"0x847127fffe11dd6a_climate_zigbee2mqtt\"}"); + + assertThat(component.channels.size(), is(6)); + assertThat(component.getName(), is("th1")); + + assertChannel(component, Climate.ACTION_CH_ID, "zigbee2mqtt/th1", "", "th1", TextValue.class); + assertChannel(component, Climate.AWAY_MODE_CH_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/set/away_mode", "th1", + OnOffValue.class); + assertChannel(component, Climate.CURRENT_TEMPERATURE_CH_ID, "zigbee2mqtt/th1", "", "th1", NumberValue.class); + assertChannel(component, Climate.HOLD_CH_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/set/preset", "th1", + TextValue.class); + assertChannel(component, Climate.MODE_CH_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/set/system_mode", "th1", + TextValue.class); + assertChannel(component, Climate.TEMPERATURE_CH_ID, "zigbee2mqtt/th1", + "zigbee2mqtt/th1/set/current_heating_setpoint", "th1", NumberValue.class); + + publishMessage("zigbee2mqtt/th1", + "{\"running_state\": \"idle\", \"away_mode\": \"ON\", " + + "\"local_temperature\": \"22.2\", \"preset\": \"schedule\", \"system_mode\": \"heat\", " + + "\"current_heating_setpoint\": \"24\"}"); + assertState(component, Climate.ACTION_CH_ID, new StringType("off")); + assertState(component, Climate.AWAY_MODE_CH_ID, OnOffType.ON); + assertState(component, Climate.CURRENT_TEMPERATURE_CH_ID, new DecimalType(22.2)); + assertState(component, Climate.HOLD_CH_ID, new StringType("schedule")); + assertState(component, Climate.MODE_CH_ID, new StringType("heat")); + assertState(component, Climate.TEMPERATURE_CH_ID, new DecimalType(24)); + + component.getChannel(Climate.AWAY_MODE_CH_ID).getState().publishValue(OnOffType.OFF); + assertPublished("zigbee2mqtt/th1/set/away_mode", "OFF"); + component.getChannel(Climate.HOLD_CH_ID).getState().publishValue(new StringType("eco")); + assertPublished("zigbee2mqtt/th1/set/preset", "eco"); + component.getChannel(Climate.MODE_CH_ID).getState().publishValue(new StringType("auto")); + assertPublished("zigbee2mqtt/th1/set/system_mode", "auto"); + component.getChannel(Climate.TEMPERATURE_CH_ID).getState().publishValue(new DecimalType(25)); + assertPublished("zigbee2mqtt/th1/set/current_heating_setpoint", "25"); + } + + @Test + public void testTS0601ClimateNotSendIfOff() { + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), "{\n" + + " \"action_template\": \"{% set values = {'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'} %}{{ values[value_json.running_state] }}\",\n" + + " \"action_topic\": \"zigbee2mqtt/th1\", \"availability\": [ {\n" + + " \"topic\": \"zigbee2mqtt/bridge/state\" } ],\n" + + " \"away_mode_command_topic\": \"zigbee2mqtt/th1/set/away_mode\",\n" + + " \"away_mode_state_template\": \"{{ value_json.away_mode }}\",\n" + + " \"away_mode_state_topic\": \"zigbee2mqtt/th1\",\n" + + " \"current_temperature_template\": \"{{ value_json.local_temperature }}\",\n" + + " \"current_temperature_topic\": \"zigbee2mqtt/th1\", \"device\": {\n" + + " \"identifiers\": [ \"zigbee2mqtt_0x847127fffe11dd6a\" ],\n" + " \"manufacturer\": \"TuYa\",\n" + + " \"model\": \"Radiator valve with thermostat (TS0601_thermostat)\",\n" + + " \"name\": \"th1\", \"sw_version\": \"Zigbee2MQTT 1.18.2\" },\n" + + " \"hold_command_topic\": \"zigbee2mqtt/th1/set/preset\", \"hold_modes\": [\n" + + " \"schedule\", \"manual\", \"boost\", \"complex\",\n" + " \"comfort\", \"eco\" ],\n" + + " \"hold_state_template\": \"{{ value_json.preset }}\",\n" + + " \"hold_state_topic\": \"zigbee2mqtt/th1\",\n" + + " \"json_attributes_topic\": \"zigbee2mqtt/th1\", \"max_temp\": \"35\",\n" + + " \"min_temp\": \"5\", \"mode_command_topic\": \"zigbee2mqtt/th1/set/system_mode\",\n" + + " \"mode_state_template\": \"{{ value_json.system_mode }}\",\n" + + " \"mode_state_topic\": \"zigbee2mqtt/th1\", \"modes\": [ \"heat\",\n" + + " \"auto\", \"off\" ], \"name\": \"th1\", \"temp_step\": 0.5,\n" + + " \"temperature_command_topic\": \"zigbee2mqtt/th1/set/current_heating_setpoint\",\n" + + " \"temperature_state_template\": \"{{ value_json.current_heating_setpoint }}\",\n" + + " \"temperature_state_topic\": \"zigbee2mqtt/th1\", \"temperature_unit\": \"C\",\n" + + " \"unique_id\": \"0x847127fffe11dd6a_climate_zigbee2mqtt\", \"send_if_off\": \"false\"}"); assertThat(component.channels.size(), is(6)); assertThat(component.getName(), is("th1")); @@ -70,5 +138,46 @@ public void testTS0601Climate() { TextValue.class); assertChannel(component, Climate.TEMPERATURE_CH_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/set/current_heating_setpoint", "th1", NumberValue.class); + + publishMessage("zigbee2mqtt/th1", + "{\"running_state\": \"idle\", \"away_mode\": \"ON\", " + + "\"local_temperature\": \"22.2\", \"preset\": \"schedule\", \"system_mode\": \"heat\", " + + "\"current_heating_setpoint\": \"24\"}"); + assertState(component, Climate.ACTION_CH_ID, new StringType("off")); + assertState(component, Climate.AWAY_MODE_CH_ID, OnOffType.ON); + assertState(component, Climate.CURRENT_TEMPERATURE_CH_ID, new DecimalType(22.2)); + assertState(component, Climate.HOLD_CH_ID, new StringType("schedule")); + assertState(component, Climate.MODE_CH_ID, new StringType("heat")); + assertState(component, Climate.TEMPERATURE_CH_ID, new DecimalType(24)); + + // Climate is in OFF state + component.getChannel(Climate.AWAY_MODE_CH_ID).getState().publishValue(OnOffType.OFF); + assertNotPublished("zigbee2mqtt/th1/set/away_mode", "OFF"); + component.getChannel(Climate.HOLD_CH_ID).getState().publishValue(new StringType("eco")); + assertNotPublished("zigbee2mqtt/th1/set/preset", "eco"); + component.getChannel(Climate.MODE_CH_ID).getState().publishValue(new StringType("auto")); + assertNotPublished("zigbee2mqtt/th1/set/system_mode", "auto"); + component.getChannel(Climate.TEMPERATURE_CH_ID).getState().publishValue(new DecimalType(25)); + assertNotPublished("zigbee2mqtt/th1/set/current_heating_setpoint", "25"); + + // Enabled + publishMessage("zigbee2mqtt/th1", + "{\"running_state\": \"heat\", \"away_mode\": \"ON\", " + + "\"local_temperature\": \"22.2\", \"preset\": \"schedule\", \"system_mode\": \"heat\", " + + "\"current_heating_setpoint\": \"24\"}"); + + // Climate is in ON state + component.getChannel(Climate.AWAY_MODE_CH_ID).getState().publishValue(OnOffType.OFF); + assertPublished("zigbee2mqtt/th1/set/away_mode", "OFF"); + component.getChannel(Climate.HOLD_CH_ID).getState().publishValue(new StringType("eco")); + assertPublished("zigbee2mqtt/th1/set/preset", "eco"); + component.getChannel(Climate.MODE_CH_ID).getState().publishValue(new StringType("auto")); + assertPublished("zigbee2mqtt/th1/set/system_mode", "auto"); + component.getChannel(Climate.TEMPERATURE_CH_ID).getState().publishValue(new DecimalType(25)); + assertPublished("zigbee2mqtt/th1/set/current_heating_setpoint", "25"); + } + + protected Set getConfigTopics() { + return Set.of(CONFIG_TOPIC); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java index ef24517e5efa1..975d5f2202563 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SwitchTests.java @@ -15,19 +15,24 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import java.util.Set; + import org.junit.jupiter.api.Test; import org.openhab.binding.mqtt.generic.values.OnOffValue; +import org.openhab.core.library.types.OnOffType; /** * Tests for {@link Switch} * * @author Anton Kharuzhy - Initial contribution */ +@SuppressWarnings("ConstantConditions") public class SwitchTests extends AbstractComponentTests { + public static final String CONFIG_TOPIC = "switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt"; @Test public void testSwitchWithStateAndCommand() { - var component = discoverComponent("homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config", + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), "" + "{\n" + " \"availability\": [\n" + " {\n" + " \"topic\": \"zigbee2mqtt/bridge/state\"\n" + " }\n" + " ],\n" + " \"command_topic\": \"zigbee2mqtt/th1/set/auto_lock\",\n" + " \"device\": {\n" + " \"identifiers\": [\n" @@ -47,11 +52,21 @@ public void testSwitchWithStateAndCommand() { assertChannel(component, Switch.switchChannelID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/set/auto_lock", "state", OnOffValue.class); + + publishMessage("zigbee2mqtt/th1", "{\"auto_lock\": \"MANUAL\"}"); + assertState(component, Switch.switchChannelID, OnOffType.OFF); + publishMessage("zigbee2mqtt/th1", "{\"auto_lock\": \"AUTO\"}"); + assertState(component, Switch.switchChannelID, OnOffType.ON); + + component.getChannel(Switch.switchChannelID).getState().publishValue(OnOffType.OFF); + assertPublished("zigbee2mqtt/th1/set/auto_lock", "MANUAL"); + component.getChannel(Switch.switchChannelID).getState().publishValue(OnOffType.ON); + assertPublished("zigbee2mqtt/th1/set/auto_lock", "AUTO"); } @Test public void testSwitchWithState() { - var component = discoverComponent("homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config", + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), "" + "{\n" + " \"availability\": [\n" + " {\n" + " \"topic\": \"zigbee2mqtt/bridge/state\"\n" + " }\n" + " ],\n" + " \"device\": {\n" + " \"identifiers\": [\n" + " \"zigbee2mqtt_0x847127fffe11dd6a\"\n" + " ],\n" @@ -68,11 +83,16 @@ public void testSwitchWithState() { assertThat(component.getName(), is("th1 auto lock")); assertChannel(component, Switch.switchChannelID, "zigbee2mqtt/th1", "", "state", OnOffValue.class); + + publishMessage("zigbee2mqtt/th1", "{\"auto_lock\": \"MANUAL\"}"); + assertState(component, Switch.switchChannelID, OnOffType.OFF); + publishMessage("zigbee2mqtt/th1", "{\"auto_lock\": \"AUTO\"}"); + assertState(component, Switch.switchChannelID, OnOffType.ON); } @Test public void testSwitchWithCommand() { - var component = discoverComponent("homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config", + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), "" + "{\n" + " \"availability\": [\n" + " {\n" + " \"topic\": \"zigbee2mqtt/bridge/state\"\n" + " }\n" + " ],\n" + " \"command_topic\": \"zigbee2mqtt/th1/set/auto_lock\",\n" + " \"device\": {\n" + " \"identifiers\": [\n" @@ -90,5 +110,14 @@ public void testSwitchWithCommand() { assertChannel(component, Switch.switchChannelID, "", "zigbee2mqtt/th1/set/auto_lock", "state", OnOffValue.class); + + component.getChannel(Switch.switchChannelID).getState().publishValue(OnOffType.OFF); + assertPublished("zigbee2mqtt/th1/set/auto_lock", "MANUAL"); + component.getChannel(Switch.switchChannelID).getState().publishValue(OnOffType.ON); + assertPublished("zigbee2mqtt/th1/set/auto_lock", "AUTO"); + } + + protected Set getConfigTopics() { + return Set.of(CONFIG_TOPIC); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java index 700db06025a6a..2a4d451fd48df 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandlerTests.java @@ -59,7 +59,7 @@ public class HomeAssistantThingHandlerTests extends AbstractHomeAssistantTests { "light/0x2222222222222222_test_light_zigbee2mqtt", "lock/0x2222222222222222_test_lock_zigbee2mqtt"); private static final List MQTT_TOPICS = CONFIG_TOPICS.stream() - .map(s -> HandlerConfiguration.DEFAULT_BASETOPIC + "/" + s + "/config").collect(Collectors.toList()); + .map(AbstractHomeAssistantTests::configTopicToMqtt).collect(Collectors.toList()); private @Mock ThingHandlerCallback callback; private HomeAssistantThingHandler thingHandler; From 93173b0a604aa8e57772bfaff59aa5ea351ab47a Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Mon, 17 May 2021 15:57:43 +0300 Subject: [PATCH 13/23] MQTT.Homeassistant fixed Climate#send_if_off handling Signed-off-by: Anton Kharuzhy --- .../internal/ComponentChannel.java | 12 ++- .../internal/HomeAssistantChannelState.java | 65 ++++++++++++++++ .../internal/component/Climate.java | 74 +++++++------------ 3 files changed, 102 insertions(+), 49 deletions(-) create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HomeAssistantChannelState.java diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java index 0ab22e71b2d25..8f2609f10f6fe 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java @@ -15,6 +15,7 @@ import java.net.URI; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Predicate; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -37,6 +38,7 @@ import org.openhab.core.thing.type.ChannelType; import org.openhab.core.thing.type.ChannelTypeBuilder; import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.types.Command; import org.openhab.core.types.StateDescriptionFragment; /** @@ -128,6 +130,7 @@ public static class Builder { private boolean retain; private boolean trigger; private @Nullable Integer qos; + private @Nullable Predicate commandFilter; private @Nullable String templateIn; private @Nullable String templateOut; @@ -193,6 +196,11 @@ public Builder trigger(boolean trigger) { return this; } + public Builder commandFilter(@Nullable Predicate commandFilter) { + this.commandFilter = commandFilter; + return this; + } + public ComponentChannel build() { return build(true); } @@ -207,10 +215,10 @@ public ComponentChannel build(boolean addToComponent) { channelUID = new ChannelUID(component.getGroupUID(), channelID); channelTypeUID = new ChannelTypeUID(MqttBindingConstants.BINDING_ID, channelUID.getGroupId() + "_" + channelID); - channelState = new ChannelState( + channelState = new HomeAssistantChannelState( ChannelConfigBuilder.create().withRetain(retain).withQos(qos).withStateTopic(state_topic) .withCommandTopic(command_topic).makeTrigger(trigger).build(), - channelUID, valueState, channelStateUpdateListener); + channelUID, valueState, channelStateUpdateListener, commandFilter); @Nullable String localStateTopic = state_topic; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HomeAssistantChannelState.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HomeAssistantChannelState.java new file mode 100644 index 0000000000000..8ffc0a446b7dd --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HomeAssistantChannelState.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mqtt.generic.ChannelConfig; +import org.openhab.binding.mqtt.generic.ChannelState; +import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener; +import org.openhab.binding.mqtt.generic.values.Value; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Extended {@link ChannelState} with added filter for {@link #publishValue(Command)} + * + * @author Anton Kharuzhy - Initial contribution + */ +@NonNullByDefault +public class HomeAssistantChannelState extends ChannelState { + private final Logger logger = LoggerFactory.getLogger(HomeAssistantChannelState.class); + private final @Nullable Predicate commandFilter; + + /** + * Creates a new channel state. + * + * @param config The channel configuration + * @param channelUID The channelUID is used for the {@link ChannelStateUpdateListener} to notify about value changes + * @param cachedValue MQTT only notifies us once about a value, during the subscribe. The channel state therefore + * needs a cache for the current value. + * @param channelStateUpdateListener A channel state update listener + * @param commandFilter A filter for commands, on true command will be published, on + * false ignored. Can be null to publish all commands. + */ + public HomeAssistantChannelState(ChannelConfig config, ChannelUID channelUID, Value cachedValue, + @Nullable ChannelStateUpdateListener channelStateUpdateListener, + @Nullable Predicate commandFilter) { + super(config, channelUID, cachedValue, channelStateUpdateListener); + this.commandFilter = commandFilter; + } + + @Override + public CompletableFuture publishValue(Command command) { + if (commandFilter != null && !commandFilter.test(command)) { + logger.trace("Channel {} updates are disabled by command filter, ignoring command {}", channelUID, command); + return CompletableFuture.completedFuture(false); + } + return super.publishValue(command); + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java index 8fbfcf2b56ffd..f808d07c91a37 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java @@ -15,8 +15,8 @@ import java.math.BigDecimal; import java.util.Arrays; import java.util.List; +import java.util.function.Predicate; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener; @@ -26,9 +26,7 @@ import org.openhab.binding.mqtt.generic.values.Value; import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel; import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; -import org.openhab.binding.mqtt.homeassistant.internal.listener.ChannelStateUpdateListenerProxy; import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.ChannelUID; import org.openhab.core.types.Command; import org.openhab.core.types.State; @@ -149,112 +147,94 @@ public Climate(ComponentFactory.ComponentConfiguration componentConfiguration) { float precision = channelConfiguration.precision != null ? channelConfiguration.precision : (FAHRENHEIT.equals(channelConfiguration.temperature_unit) ? DEFAULT_FAHRENHEIT_PRECISION : DEFAULT_CELSIUM_PRECISION); + final ChannelStateUpdateListener updateListener = componentConfiguration.getUpdateListener(); @Nullable ComponentChannel actionChannel = buildOptionalChannel(ACTION_CH_ID, - new TextValue(ACTION_MODES.toArray(new String[0])), componentConfiguration.getUpdateListener(), null, - null, channelConfiguration.action_template, channelConfiguration.action_topic); + new TextValue(ACTION_MODES.toArray(new String[0])), updateListener, null, null, + channelConfiguration.action_template, channelConfiguration.action_topic, null); - ChannelStateUpdateListener updateListener = getListener(channelConfiguration.send_if_off, actionChannel, - componentConfiguration.getUpdateListener()); + @Nullable + final Predicate commandFilter = channelConfiguration.send_if_off ? null + : getCommandFilter(actionChannel); buildOptionalChannel(AUX_CH_ID, new OnOffValue(), updateListener, null, channelConfiguration.aux_command_topic, - channelConfiguration.aux_state_template, channelConfiguration.aux_state_topic); + channelConfiguration.aux_state_template, channelConfiguration.aux_state_topic, commandFilter); buildOptionalChannel(AWAY_MODE_CH_ID, new OnOffValue(), updateListener, null, channelConfiguration.away_mode_command_topic, channelConfiguration.away_mode_state_template, - channelConfiguration.away_mode_state_topic); + channelConfiguration.away_mode_state_topic, commandFilter); buildOptionalChannel(CURRENT_TEMPERATURE_CH_ID, new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(precision), channelConfiguration.temperature_unit), updateListener, null, null, channelConfiguration.current_temperature_template, - channelConfiguration.current_temperature_topic); + channelConfiguration.current_temperature_topic, commandFilter); buildOptionalChannel(FAN_MODE_CH_ID, new TextValue(channelConfiguration.fan_modes.toArray(new String[0])), updateListener, channelConfiguration.fan_mode_command_template, channelConfiguration.fan_mode_command_topic, channelConfiguration.fan_mode_state_template, - channelConfiguration.fan_mode_state_topic); + channelConfiguration.fan_mode_state_topic, commandFilter); if (channelConfiguration.hold_modes != null && !channelConfiguration.hold_modes.isEmpty()) { buildOptionalChannel(HOLD_CH_ID, new TextValue(channelConfiguration.hold_modes.toArray(new String[0])), updateListener, channelConfiguration.hold_command_template, channelConfiguration.hold_command_topic, - channelConfiguration.hold_state_template, channelConfiguration.hold_state_topic); + channelConfiguration.hold_state_template, channelConfiguration.hold_state_topic, commandFilter); } buildOptionalChannel(MODE_CH_ID, new TextValue(channelConfiguration.modes.toArray(new String[0])), updateListener, channelConfiguration.mode_command_template, channelConfiguration.mode_command_topic, - channelConfiguration.mode_state_template, channelConfiguration.mode_state_topic); + channelConfiguration.mode_state_template, channelConfiguration.mode_state_topic, commandFilter); buildOptionalChannel(SWING_CH_ID, new TextValue(channelConfiguration.swing_modes.toArray(new String[0])), updateListener, channelConfiguration.swing_command_template, channelConfiguration.swing_command_topic, - channelConfiguration.swing_state_template, channelConfiguration.swing_state_topic); + channelConfiguration.swing_state_template, channelConfiguration.swing_state_topic, commandFilter); buildOptionalChannel(TEMPERATURE_CH_ID, new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.temp_step), channelConfiguration.temperature_unit), updateListener, channelConfiguration.temperature_command_template, channelConfiguration.temperature_command_topic, channelConfiguration.temperature_state_template, - channelConfiguration.temperature_state_topic); + channelConfiguration.temperature_state_topic, commandFilter); buildOptionalChannel(TEMPERATURE_HIGH_CH_ID, new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.temp_step), channelConfiguration.temperature_unit), updateListener, channelConfiguration.temperature_high_command_template, channelConfiguration.temperature_high_command_topic, - channelConfiguration.temperature_high_state_template, - channelConfiguration.temperature_high_state_topic); + channelConfiguration.temperature_high_state_template, channelConfiguration.temperature_high_state_topic, + commandFilter); buildOptionalChannel(TEMPERATURE_LOW_CH_ID, new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.temp_step), channelConfiguration.temperature_unit), updateListener, channelConfiguration.temperature_low_command_template, channelConfiguration.temperature_low_command_topic, channelConfiguration.temperature_low_state_template, - channelConfiguration.temperature_low_state_topic); + channelConfiguration.temperature_low_state_topic, commandFilter); buildOptionalChannel(POWER_CH_ID, new OnOffValue(), updateListener, null, - channelConfiguration.power_command_topic, null, null); + channelConfiguration.power_command_topic, null, null, commandFilter); } @Nullable private ComponentChannel buildOptionalChannel(String channelId, Value valueState, ChannelStateUpdateListener channelStateUpdateListener, @Nullable String commandTemplate, - @Nullable String commandTopic, @Nullable String stateTemplate, @Nullable String stateTopic) { + @Nullable String commandTopic, @Nullable String stateTemplate, @Nullable String stateTopic, + @Nullable Predicate commandFilter) { if ((commandTopic != null && !commandTopic.isBlank()) || (stateTopic != null && !stateTopic.isBlank())) { return buildChannel(channelId, valueState, channelConfiguration.getName(), channelStateUpdateListener) .stateTopic(stateTopic, stateTemplate, channelConfiguration.getValueTemplate()) .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(), commandTemplate) - .build(); + .commandFilter(commandFilter).build(); } return null; } - private ChannelStateUpdateListener getListener(boolean sendIfOff, @Nullable ComponentChannel actionChannel, - ChannelStateUpdateListener original) { - if (!sendIfOff && actionChannel != null) { - return new ChannelStateUpdateListenerProxy(original) { - @Override - public void postChannelCommand(@NonNull ChannelUID channelUID, @NonNull Command value) { - if (isOff()) { - return; // Do not send command if action is off - } - super.postChannelCommand(channelUID, value); - } - - @Override - public void triggerChannel(@NonNull ChannelUID channelUID, @NonNull String eventPayload) { - if (isOff()) { - return; // Do not trigger if action is off - } - super.triggerChannel(channelUID, eventPayload); - } - - private boolean isOff() { - Value val = actionChannel.getState().getCache(); - return ACTION_OFF_STATE.equals(val.getChannelState()); - } - }; + private @Nullable Predicate getCommandFilter(@Nullable ComponentChannel actionChannel) { + if (actionChannel == null) { + return null; } - return original; + final var val = actionChannel.getState().getCache(); + return command -> !ACTION_OFF_STATE.equals(val.getChannelState()); } } From aababd2474690fa75b4166c5dce368c13ee3c5d0 Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Tue, 18 May 2021 12:45:57 +0300 Subject: [PATCH 14/23] MQTT.Homeassistant do not filter the power command Signed-off-by: Anton Kharuzhy --- .../binding/mqtt/homeassistant/internal/component/Climate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java index f808d07c91a37..761058ee22033 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java @@ -212,7 +212,7 @@ public Climate(ComponentFactory.ComponentConfiguration componentConfiguration) { channelConfiguration.temperature_low_state_topic, commandFilter); buildOptionalChannel(POWER_CH_ID, new OnOffValue(), updateListener, null, - channelConfiguration.power_command_topic, null, null, commandFilter); + channelConfiguration.power_command_topic, null, null, null); } @Nullable From cba1d07a224549dd7806dc9f5278faf17caa413c Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Tue, 18 May 2021 14:20:33 +0300 Subject: [PATCH 15/23] MQTT.Homeassistant climate unit test added Signed-off-by: Anton Kharuzhy --- .../component/AbstractComponentTests.java | 6 + .../internal/component/ClimateTests.java | 212 ++++++++++++++---- 2 files changed, 169 insertions(+), 49 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java index 045140212ec50..339cc8096f546 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java @@ -33,6 +33,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.hamcrest.CoreMatchers; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.mockito.Mock; import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider; @@ -79,6 +80,11 @@ public void setupThingHandler() { thingHandler.initialize(); } + @AfterEach + public void disposeThingHandler() { + thingHandler.dispose(); + } + /** * {@link org.openhab.binding.mqtt.homeassistant.internal.DiscoverComponents} will wait a config on specified * topics. diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java index a1ebfcb1e3ebc..3ceadb3dff0b7 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java @@ -36,30 +36,30 @@ public class ClimateTests extends AbstractComponentTests { @Test public void testTS0601Climate() { - var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), "{\n" - + " \"action_template\": \"{% set values = {'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'} %}{{ values[value_json.running_state] }}\",\n" - + " \"action_topic\": \"zigbee2mqtt/th1\", \"availability\": [ {\n" - + " \"topic\": \"zigbee2mqtt/bridge/state\" } ],\n" - + " \"away_mode_command_topic\": \"zigbee2mqtt/th1/set/away_mode\",\n" - + " \"away_mode_state_template\": \"{{ value_json.away_mode }}\",\n" - + " \"away_mode_state_topic\": \"zigbee2mqtt/th1\",\n" - + " \"current_temperature_template\": \"{{ value_json.local_temperature }}\",\n" - + " \"current_temperature_topic\": \"zigbee2mqtt/th1\", \"device\": {\n" - + " \"identifiers\": [ \"zigbee2mqtt_0x847127fffe11dd6a\" ],\n" + " \"manufacturer\": \"TuYa\",\n" - + " \"model\": \"Radiator valve with thermostat (TS0601_thermostat)\",\n" - + " \"name\": \"th1\", \"sw_version\": \"Zigbee2MQTT 1.18.2\" },\n" - + " \"hold_command_topic\": \"zigbee2mqtt/th1/set/preset\", \"hold_modes\": [\n" - + " \"schedule\", \"manual\", \"boost\", \"complex\",\n" + " \"comfort\", \"eco\" ],\n" - + " \"hold_state_template\": \"{{ value_json.preset }}\",\n" - + " \"hold_state_topic\": \"zigbee2mqtt/th1\",\n" - + " \"json_attributes_topic\": \"zigbee2mqtt/th1\", \"max_temp\": \"35\",\n" - + " \"min_temp\": \"5\", \"mode_command_topic\": \"zigbee2mqtt/th1/set/system_mode\",\n" - + " \"mode_state_template\": \"{{ value_json.system_mode }}\",\n" - + " \"mode_state_topic\": \"zigbee2mqtt/th1\", \"modes\": [ \"heat\",\n" - + " \"auto\", \"off\" ], \"name\": \"th1\", \"temp_step\": 0.5,\n" - + " \"temperature_command_topic\": \"zigbee2mqtt/th1/set/current_heating_setpoint\",\n" - + " \"temperature_state_template\": \"{{ value_json.current_heating_setpoint }}\",\n" - + " \"temperature_state_topic\": \"zigbee2mqtt/th1\", \"temperature_unit\": \"C\",\n" + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), "{" + + " \"action_template\": \"{% set values = {'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'} %}{{ values[value_json.running_state] }}\"," + + " \"action_topic\": \"zigbee2mqtt/th1\", \"availability\": [ {" + + " \"topic\": \"zigbee2mqtt/bridge/state\" } ]," + + " \"away_mode_command_topic\": \"zigbee2mqtt/th1/set/away_mode\"," + + " \"away_mode_state_template\": \"{{ value_json.away_mode }}\"," + + " \"away_mode_state_topic\": \"zigbee2mqtt/th1\"," + + " \"current_temperature_template\": \"{{ value_json.local_temperature }}\"," + + " \"current_temperature_topic\": \"zigbee2mqtt/th1\", \"device\": {" + + " \"identifiers\": [ \"zigbee2mqtt_0x847127fffe11dd6a\" ], \"manufacturer\": \"TuYa\"," + + " \"model\": \"Radiator valve with thermostat (TS0601_thermostat)\"," + + " \"name\": \"th1\", \"sw_version\": \"Zigbee2MQTT 1.18.2\" }," + + " \"hold_command_topic\": \"zigbee2mqtt/th1/set/preset\", \"hold_modes\": [" + + " \"schedule\", \"manual\", \"boost\", \"complex\", \"comfort\", \"eco\" ]," + + " \"hold_state_template\": \"{{ value_json.preset }}\"," + + " \"hold_state_topic\": \"zigbee2mqtt/th1\"," + + " \"json_attributes_topic\": \"zigbee2mqtt/th1\", \"max_temp\": \"35\"," + + " \"min_temp\": \"5\", \"mode_command_topic\": \"zigbee2mqtt/th1/set/system_mode\"," + + " \"mode_state_template\": \"{{ value_json.system_mode }}\"," + + " \"mode_state_topic\": \"zigbee2mqtt/th1\", \"modes\": [ \"heat\"," + + " \"auto\", \"off\" ], \"name\": \"th1\", \"temp_step\": 0.5," + + " \"temperature_command_topic\": \"zigbee2mqtt/th1/set/current_heating_setpoint\"," + + " \"temperature_state_template\": \"{{ value_json.current_heating_setpoint }}\"," + + " \"temperature_state_topic\": \"zigbee2mqtt/th1\", \"temperature_unit\": \"C\"," + " \"unique_id\": \"0x847127fffe11dd6a_climate_zigbee2mqtt\"}"); assertThat(component.channels.size(), is(6)); @@ -99,33 +99,34 @@ public void testTS0601Climate() { @Test public void testTS0601ClimateNotSendIfOff() { - var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), "{\n" - + " \"action_template\": \"{% set values = {'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'} %}{{ values[value_json.running_state] }}\",\n" - + " \"action_topic\": \"zigbee2mqtt/th1\", \"availability\": [ {\n" - + " \"topic\": \"zigbee2mqtt/bridge/state\" } ],\n" - + " \"away_mode_command_topic\": \"zigbee2mqtt/th1/set/away_mode\",\n" - + " \"away_mode_state_template\": \"{{ value_json.away_mode }}\",\n" - + " \"away_mode_state_topic\": \"zigbee2mqtt/th1\",\n" - + " \"current_temperature_template\": \"{{ value_json.local_temperature }}\",\n" - + " \"current_temperature_topic\": \"zigbee2mqtt/th1\", \"device\": {\n" - + " \"identifiers\": [ \"zigbee2mqtt_0x847127fffe11dd6a\" ],\n" + " \"manufacturer\": \"TuYa\",\n" - + " \"model\": \"Radiator valve with thermostat (TS0601_thermostat)\",\n" - + " \"name\": \"th1\", \"sw_version\": \"Zigbee2MQTT 1.18.2\" },\n" - + " \"hold_command_topic\": \"zigbee2mqtt/th1/set/preset\", \"hold_modes\": [\n" - + " \"schedule\", \"manual\", \"boost\", \"complex\",\n" + " \"comfort\", \"eco\" ],\n" - + " \"hold_state_template\": \"{{ value_json.preset }}\",\n" - + " \"hold_state_topic\": \"zigbee2mqtt/th1\",\n" - + " \"json_attributes_topic\": \"zigbee2mqtt/th1\", \"max_temp\": \"35\",\n" - + " \"min_temp\": \"5\", \"mode_command_topic\": \"zigbee2mqtt/th1/set/system_mode\",\n" - + " \"mode_state_template\": \"{{ value_json.system_mode }}\",\n" - + " \"mode_state_topic\": \"zigbee2mqtt/th1\", \"modes\": [ \"heat\",\n" - + " \"auto\", \"off\" ], \"name\": \"th1\", \"temp_step\": 0.5,\n" - + " \"temperature_command_topic\": \"zigbee2mqtt/th1/set/current_heating_setpoint\",\n" - + " \"temperature_state_template\": \"{{ value_json.current_heating_setpoint }}\",\n" - + " \"temperature_state_topic\": \"zigbee2mqtt/th1\", \"temperature_unit\": \"C\",\n" + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), "{" + + " \"action_template\": \"{% set values = {'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'} %}{{ values[value_json.running_state] }}\"," + + " \"action_topic\": \"zigbee2mqtt/th1\", \"availability\": [ {" + + " \"topic\": \"zigbee2mqtt/bridge/state\" } ]," + + " \"away_mode_command_topic\": \"zigbee2mqtt/th1/set/away_mode\"," + + " \"away_mode_state_template\": \"{{ value_json.away_mode }}\"," + + " \"away_mode_state_topic\": \"zigbee2mqtt/th1\"," + + " \"current_temperature_template\": \"{{ value_json.local_temperature }}\"," + + " \"current_temperature_topic\": \"zigbee2mqtt/th1\", \"device\": {" + + " \"identifiers\": [ \"zigbee2mqtt_0x847127fffe11dd6a\" ], \"manufacturer\": \"TuYa\"," + + " \"model\": \"Radiator valve with thermostat (TS0601_thermostat)\"," + + " \"name\": \"th1\", \"sw_version\": \"Zigbee2MQTT 1.18.2\" }," + + " \"hold_command_topic\": \"zigbee2mqtt/th1/set/preset\", \"hold_modes\": [" + + " \"schedule\", \"manual\", \"boost\", \"complex\", \"comfort\", \"eco\" ]," + + " \"hold_state_template\": \"{{ value_json.preset }}\"," + + " \"hold_state_topic\": \"zigbee2mqtt/th1\"," + + " \"json_attributes_topic\": \"zigbee2mqtt/th1\", \"max_temp\": \"35\"," + + " \"min_temp\": \"5\", \"mode_command_topic\": \"zigbee2mqtt/th1/set/system_mode\"," + + " \"mode_state_template\": \"{{ value_json.system_mode }}\"," + + " \"mode_state_topic\": \"zigbee2mqtt/th1\", \"modes\": [ \"heat\"," + + " \"auto\", \"off\" ], \"name\": \"th1\", \"temp_step\": 0.5," + + " \"temperature_command_topic\": \"zigbee2mqtt/th1/set/current_heating_setpoint\"," + + " \"temperature_state_template\": \"{{ value_json.current_heating_setpoint }}\"," + + " \"temperature_state_topic\": \"zigbee2mqtt/th1\", \"temperature_unit\": \"C\"," + + " \"power_command_topic\": \"zigbee2mqtt/th1/power\"," + " \"unique_id\": \"0x847127fffe11dd6a_climate_zigbee2mqtt\", \"send_if_off\": \"false\"}"); - assertThat(component.channels.size(), is(6)); + assertThat(component.channels.size(), is(7)); assertThat(component.getName(), is("th1")); assertChannel(component, Climate.ACTION_CH_ID, "zigbee2mqtt/th1", "", "th1", TextValue.class); @@ -159,6 +160,8 @@ public void testTS0601ClimateNotSendIfOff() { assertNotPublished("zigbee2mqtt/th1/set/system_mode", "auto"); component.getChannel(Climate.TEMPERATURE_CH_ID).getState().publishValue(new DecimalType(25)); assertNotPublished("zigbee2mqtt/th1/set/current_heating_setpoint", "25"); + component.getChannel(Climate.POWER_CH_ID).getState().publishValue(OnOffType.ON); + assertPublished("zigbee2mqtt/th1/power", "ON"); // Enabled publishMessage("zigbee2mqtt/th1", @@ -177,6 +180,117 @@ public void testTS0601ClimateNotSendIfOff() { assertPublished("zigbee2mqtt/th1/set/current_heating_setpoint", "25"); } + @Test + public void testClimate() { + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), + "{\"action_template\": \"{{ value_json.action }}\", \"action_topic\": \"zigbee2mqtt/th1\"," + + " \"aux_command_topic\": \"zigbee2mqtt/th1/aux\"," + + " \"aux_state_template\": \"{{ value_json.aux }}\", \"aux_state_topic\": \"zigbee2mqtt/th1\"," + + " \"away_mode_command_topic\": \"zigbee2mqtt/th1/away_mode\"," + + " \"away_mode_state_template\": \"{{ value_json.away_mode }}\"," + + " \"away_mode_state_topic\": \"zigbee2mqtt/th1\"," + + " \"current_temperature_template\": \"{{ value_json.current_temperature }}\"," + + " \"current_temperature_topic\": \"zigbee2mqtt/th1\"," + + " \"fan_mode_command_template\": \"fan_mode={{ value }}\"," + + " \"fan_mode_command_topic\": \"zigbee2mqtt/th1/fan_mode\"," + + " \"fan_mode_state_template\": \"{{ value_json.fan_mode }}\"," + + " \"fan_mode_state_topic\": \"zigbee2mqtt/th1\", \"fan_modes\": [ \"p1\"," + + " \"p2\" ], \"hold_command_template\": \"hold={{ value }}\"," + + " \"hold_command_topic\": \"zigbee2mqtt/th1/hold\"," + + " \"hold_state_template\": \"{{ value_json.hold }}\"," + + " \"hold_state_topic\": \"zigbee2mqtt/th1\", \"hold_modes\": [ \"u1\", \"u2\"," + + " \"u3\" ], \"json_attributes_template\": \"{{ value_json.attrs }}\"," + + " \"json_attributes_topic\": \"zigbee2mqtt/th1\"," + + " \"mode_command_template\": \"mode={{ value }}\"," + + " \"mode_command_topic\": \"zigbee2mqtt/th1/mode\"," + + " \"mode_state_template\": \"{{ value_json.mode }}\"," + + " \"mode_state_topic\": \"zigbee2mqtt/th1\", \"modes\": [ \"B1\", \"B2\"" + + " ], \"swing_command_template\": \"swing={{ value }}\"," + + " \"swing_command_topic\": \"zigbee2mqtt/th1/swing\"," + + " \"swing_state_template\": \"{{ value_json.swing }}\"," + + " \"swing_state_topic\": \"zigbee2mqtt/th1\", \"swing_modes\": [ \"G1\"," + + " \"G2\" ], \"temperature_command_template\": \"temperature={{ value }}\"," + + " \"temperature_command_topic\": \"zigbee2mqtt/th1/temperature\"," + + " \"temperature_state_template\": \"{{ value_json.temperature }}\"," + + " \"temperature_state_topic\": \"zigbee2mqtt/th1\"," + + " \"temperature_high_command_template\": \"temperature_high={{ value }}\"," + + " \"temperature_high_command_topic\": \"zigbee2mqtt/th1/temperature_high\"," + + " \"temperature_high_state_template\": \"{{ value_json.temperature_high }}\"," + + " \"temperature_high_state_topic\": \"zigbee2mqtt/th1\"," + + " \"temperature_low_command_template\": \"temperature_low={{ value }}\"," + + " \"temperature_low_command_topic\": \"zigbee2mqtt/th1/temperature_low\"," + + " \"temperature_low_state_template\": \"{{ value_json.temperature_low }}\"," + + " \"temperature_low_state_topic\": \"zigbee2mqtt/th1\"," + + " \"power_command_topic\": \"zigbee2mqtt/th1/power\", \"initial\": \"10\"," + + " \"max_temp\": \"40\", \"min_temp\": \"0\", \"temperature_unit\": \"F\"," + + " \"temp_step\": \"1\", \"precision\": \"0.5\", \"send_if_off\": \"false\" }"); + + assertThat(component.channels.size(), is(12)); + assertThat(component.getName(), is("MQTT HVAC")); + + assertChannel(component, Climate.ACTION_CH_ID, "zigbee2mqtt/th1", "", "MQTT HVAC", TextValue.class); + assertChannel(component, Climate.AUX_CH_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/aux", "MQTT HVAC", + OnOffValue.class); + assertChannel(component, Climate.AWAY_MODE_CH_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/away_mode", "MQTT HVAC", + OnOffValue.class); + assertChannel(component, Climate.CURRENT_TEMPERATURE_CH_ID, "zigbee2mqtt/th1", "", "MQTT HVAC", + NumberValue.class); + assertChannel(component, Climate.FAN_MODE_CH_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/fan_mode", "MQTT HVAC", + TextValue.class); + assertChannel(component, Climate.HOLD_CH_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/hold", "MQTT HVAC", + TextValue.class); + assertChannel(component, Climate.MODE_CH_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/mode", "MQTT HVAC", + TextValue.class); + assertChannel(component, Climate.SWING_CH_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/swing", "MQTT HVAC", + TextValue.class); + assertChannel(component, Climate.TEMPERATURE_CH_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/temperature", + "MQTT HVAC", NumberValue.class); + assertChannel(component, Climate.TEMPERATURE_HIGH_CH_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/temperature_high", + "MQTT HVAC", NumberValue.class); + assertChannel(component, Climate.TEMPERATURE_LOW_CH_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/temperature_low", + "MQTT HVAC", NumberValue.class); + assertChannel(component, Climate.POWER_CH_ID, "", "zigbee2mqtt/th1/power", "MQTT HVAC", OnOffValue.class); + + publishMessage("zigbee2mqtt/th1", + "{ \"action\": \"fan\", \"aux\": \"ON\", \"away_mode\": \"OFF\", " + + "\"current_temperature\": \"35.5\", \"fan_mode\": \"p2\", \"hold\": \"u2\", " + + "\"mode\": \"B1\", \"swing\": \"G1\", \"temperature\": \"30\", " + + "\"temperature_high\": \"37\", \"temperature_low\": \"20\" }"); + + assertState(component, Climate.ACTION_CH_ID, new StringType("fan")); + assertState(component, Climate.AUX_CH_ID, OnOffType.ON); + assertState(component, Climate.AWAY_MODE_CH_ID, OnOffType.OFF); + assertState(component, Climate.CURRENT_TEMPERATURE_CH_ID, new DecimalType(35.5)); + assertState(component, Climate.FAN_MODE_CH_ID, new StringType("p2")); + assertState(component, Climate.HOLD_CH_ID, new StringType("u2")); + assertState(component, Climate.MODE_CH_ID, new StringType("B1")); + assertState(component, Climate.SWING_CH_ID, new StringType("G1")); + assertState(component, Climate.TEMPERATURE_CH_ID, new DecimalType(30)); + assertState(component, Climate.TEMPERATURE_HIGH_CH_ID, new DecimalType(37)); + assertState(component, Climate.TEMPERATURE_LOW_CH_ID, new DecimalType(20)); + + component.getChannel(Climate.AUX_CH_ID).getState().publishValue(OnOffType.OFF); + assertPublished("zigbee2mqtt/th1/aux", "OFF"); + component.getChannel(Climate.AWAY_MODE_CH_ID).getState().publishValue(OnOffType.ON); + assertPublished("zigbee2mqtt/th1/away_mode", "ON"); + component.getChannel(Climate.FAN_MODE_CH_ID).getState().publishValue(new StringType("p1")); + assertPublished("zigbee2mqtt/th1/fan_mode", "fan_mode=p1"); + component.getChannel(Climate.HOLD_CH_ID).getState().publishValue(new StringType("u3")); + assertPublished("zigbee2mqtt/th1/hold", "hold=u3"); + component.getChannel(Climate.MODE_CH_ID).getState().publishValue(new StringType("B2")); + assertPublished("zigbee2mqtt/th1/mode", "mode=B2"); + component.getChannel(Climate.SWING_CH_ID).getState().publishValue(new StringType("G2")); + assertPublished("zigbee2mqtt/th1/swing", "swing=G2"); + component.getChannel(Climate.TEMPERATURE_CH_ID).getState().publishValue(new DecimalType(30.5)); + assertPublished("zigbee2mqtt/th1/temperature", "temperature=30.5"); + component.getChannel(Climate.TEMPERATURE_HIGH_CH_ID).getState().publishValue(new DecimalType(39.5)); + assertPublished("zigbee2mqtt/th1/temperature_high", "temperature_high=39.5"); + component.getChannel(Climate.TEMPERATURE_LOW_CH_ID).getState().publishValue(new DecimalType(19.5)); + assertPublished("zigbee2mqtt/th1/temperature_low", "temperature_low=19.5"); + component.getChannel(Climate.POWER_CH_ID).getState().publishValue(OnOffType.OFF); + assertPublished("zigbee2mqtt/th1/power", "OFF"); + } + protected Set getConfigTopics() { return Set.of(CONFIG_TOPIC); } From 595fa0fcbc2ce96fed1bd987a0691ac1a3cfba80 Mon Sep 17 00:00:00 2001 From: antroids <36043354+antroids@users.noreply.github.com> Date: Tue, 22 Jun 2021 11:20:57 +0300 Subject: [PATCH 16/23] Update bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java Redundant annotation removed Co-authored-by: Fabian Wolter --- .../binding/mqtt/homeassistant/internal/DiscoverComponents.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java index 3523c3c32e9af..c91430f8499e7 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java @@ -57,7 +57,7 @@ public class DiscoverComponents implements MqttMessageSubscriber { private @Nullable ScheduledFuture stopDiscoveryFuture; private WeakReference<@Nullable MqttBrokerConnection> connectionRef = new WeakReference<>(null); - protected @Nullable @NonNullByDefault({}) ComponentDiscovered discoveredListener; + protected @Nullable ComponentDiscovered discoveredListener; private int discoverTime; private Set topics = new HashSet<>(); From 0f7960d75280668868ab9cd84a9f4db0d3dd2c3c Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Thu, 24 Jun 2021 11:05:02 +0300 Subject: [PATCH 17/23] MQTT.Homeassistant Redundant @Nullable annotations removed Signed-off-by: Anton Kharuzhy --- .../mqtt/homeassistant/internal/ComponentChannel.java | 7 +------ .../mqtt/homeassistant/internal/DiscoverComponents.java | 5 ----- .../openhab/binding/mqtt/homeassistant/internal/HaID.java | 1 - .../internal/component/AbstractComponent.java | 1 - .../internal/component/AlarmControlPanel.java | 1 - .../mqtt/homeassistant/internal/component/Climate.java | 4 ---- .../mqtt/homeassistant/internal/component/Light.java | 3 --- .../mqtt/homeassistant/internal/component/Sensor.java | 2 -- .../internal/config/AbstractChannelConfiguration.java | 6 ------ .../binding/mqtt/homeassistant/internal/config/Device.java | 3 ++- .../internal/listener/OffDelayUpdateStateListener.java | 1 - 11 files changed, 3 insertions(+), 31 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java index 8f2609f10f6fe..61d557f0472e3 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java @@ -152,8 +152,7 @@ public Builder stateTopic(@Nullable String state_topic) { public Builder stateTopic(@Nullable String state_topic, @Nullable String... templates) { this.state_topic = state_topic; if (state_topic != null && !state_topic.isBlank()) { - for (@Nullable - String template : templates) { + for (String template : templates) { if (template != null && !template.isBlank()) { this.templateIn = template; break; @@ -220,7 +219,6 @@ public ComponentChannel build(boolean addToComponent) { .withCommandTopic(command_topic).makeTrigger(trigger).build(), channelUID, valueState, channelStateUpdateListener, commandFilter); - @Nullable String localStateTopic = state_topic; if (localStateTopic == null || localStateTopic.isBlank() || this.trigger) { type = ChannelTypeBuilder.trigger(channelTypeUID, label) @@ -242,16 +240,13 @@ public ComponentChannel build(boolean addToComponent) { ComponentChannel result = new ComponentChannel(channelUID, channelState, channel, type, channelTypeUID, channelStateUpdateListener); - @Nullable TransformationServiceProvider transformationProvider = component.getTransformationServiceProvider(); - @Nullable final String templateIn = this.templateIn; if (templateIn != null && transformationProvider != null) { channelState .addTransformation(new ChannelStateTransformation(JINJA, templateIn, transformationProvider)); } - @Nullable final String templateOut = this.templateOut; if (templateOut != null && transformationProvider != null) { channelState.addTransformationOut( diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java index c91430f8499e7..a24c5cabb418a 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java @@ -94,8 +94,6 @@ public void processMessage(String topic, byte[] payload) { HaID haID = new HaID(topic); String config = new String(payload); - - @Nullable AbstractComponent component = null; if (config.length() > 0) { @@ -147,7 +145,6 @@ public void processMessage(String topic, byte[] payload) { } private void subscribeSuccess() { - @Nullable final MqttBrokerConnection connection = connectionRef.get(); // Set up a scheduled future that will stop the discovery after the given time if (connection != null && discoverTime > 0) { @@ -164,14 +161,12 @@ private void subscribeSuccess() { } private @Nullable Void subscribeFail(Throwable e) { - @Nullable final ScheduledFuture scheduledFuture = this.stopDiscoveryFuture; if (scheduledFuture != null) { // Cancel timeout scheduledFuture.cancel(false); this.stopDiscoveryFuture = null; } this.discoveredListener = null; - @Nullable final MqttBrokerConnection connection = connectionRef.get(); if (connection != null) { this.topics.parallelStream().forEach(t -> connection.unsubscribe(t, this)); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HaID.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HaID.java index 2552bd3d860ee..51a2509205d55 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HaID.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HaID.java @@ -188,7 +188,6 @@ public String toShortTopic() { * @return group id */ public String getGroupId(@Nullable final String uniqueId) { - @Nullable String result = uniqueId; // the null test is only here so the compile knows, result is not null afterwards diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java index 9e0573d22658a..30a3eecdfa37e 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java @@ -86,7 +86,6 @@ public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfig this.configSeen = false; - @Nullable String availability_topic = this.channelConfiguration.getAvailabilityTopic(); if (availability_topic != null) { componentConfiguration.getTracker().addAvailabilityTopic(availability_topic, diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java index 8065910cab585..05e3062e158c3 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java @@ -67,7 +67,6 @@ public AlarmControlPanel(ComponentFactory.ComponentConfiguration componentConfig .stateTopic(channelConfiguration.state_topic, channelConfiguration.getValueTemplate())// .build(); - @Nullable String command_topic = channelConfiguration.command_topic; if (command_topic != null) { buildChannel(switchDisarmChannelID, new TextValue(new String[] { channelConfiguration.payload_disarm }), diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java index 761058ee22033..e143668b07de8 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java @@ -138,10 +138,8 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { public Climate(ComponentFactory.ComponentConfiguration componentConfiguration) { super(componentConfiguration, ChannelConfiguration.class); - @Nullable BigDecimal minTemp = channelConfiguration.min_temp != null ? BigDecimal.valueOf(channelConfiguration.min_temp) : null; - @Nullable BigDecimal maxTemp = channelConfiguration.max_temp != null ? BigDecimal.valueOf(channelConfiguration.max_temp) : null; float precision = channelConfiguration.precision != null ? channelConfiguration.precision @@ -149,12 +147,10 @@ public Climate(ComponentFactory.ComponentConfiguration componentConfiguration) { : DEFAULT_CELSIUM_PRECISION); final ChannelStateUpdateListener updateListener = componentConfiguration.getUpdateListener(); - @Nullable ComponentChannel actionChannel = buildOptionalChannel(ACTION_CH_ID, new TextValue(ACTION_MODES.toArray(new String[0])), updateListener, null, null, channelConfiguration.action_template, channelConfiguration.action_topic, null); - @Nullable final Predicate commandFilter = channelConfiguration.send_if_off ? null : getCommandFilter(actionChannel); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java index c5860e00c204f..66549b25060a5 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java @@ -148,7 +148,6 @@ public Light(ComponentFactory.ComponentConfiguration builder) { */ @Override public void updateChannelState(ChannelUID channelUID, State value) { - @Nullable ChannelStateUpdateListener listener = channelStateUpdateListener; if (listener != null) { listener.updateChannelState(colorChannel.getChannelUID(), value); @@ -160,7 +159,6 @@ public void updateChannelState(ChannelUID channelUID, State value) { */ @Override public void postChannelCommand(ChannelUID channelUID, Command value) { - @Nullable ChannelStateUpdateListener listener = channelStateUpdateListener; if (listener != null) { listener.postChannelCommand(colorChannel.getChannelUID(), value); @@ -172,7 +170,6 @@ public void postChannelCommand(ChannelUID channelUID, Command value) { */ @Override public void triggerChannel(ChannelUID channelUID, String eventPayload) { - @Nullable ChannelStateUpdateListener listener = channelStateUpdateListener; if (listener != null) { listener.triggerChannel(colorChannel.getChannelUID(), eventPayload); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java index fd71eced7c8b8..aea0447562e3f 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java @@ -58,8 +58,6 @@ public Sensor(ComponentFactory.ComponentConfiguration componentConfiguration) { super(componentConfiguration, ChannelConfiguration.class); Value value; - - @Nullable String uom = channelConfiguration.unit_of_measurement; if (uom != null && !uom.isBlank()) { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/AbstractChannelConfiguration.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/AbstractChannelConfiguration.java index a1e54f767f9a0..4aa072d1ad09d 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/AbstractChannelConfiguration.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/AbstractChannelConfiguration.java @@ -75,7 +75,6 @@ protected AbstractChannelConfiguration(String defaultName) { } public String getThingName() { - @Nullable String result = null; if (this.device != null) { @@ -88,7 +87,6 @@ public String getThingName() { } public String getThingId(String defaultId) { - @Nullable String result = null; if (this.device != null) { result = this.device.getId(); @@ -100,22 +98,18 @@ public String getThingId(String defaultId) { } public Map appendToProperties(Map properties) { - @Nullable final Device device_ = device; if (device_ == null) { return properties; } - @Nullable final String manufacturer = device_.manufacturer; if (manufacturer != null) { properties.put(Thing.PROPERTY_VENDOR, manufacturer); } - @Nullable final String model = device_.model; if (model != null) { properties.put(Thing.PROPERTY_MODEL_ID, model); } - @Nullable final String sw_version = device_.sw_version; if (sw_version != null) { properties.put(Thing.PROPERTY_FIRMWARE_VERSION, sw_version); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Device.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Device.java index 924cbe68fffea..e39515b158372 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Device.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Device.java @@ -14,6 +14,7 @@ import java.util.List; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import com.google.gson.annotations.JsonAdapter; @@ -23,6 +24,7 @@ * * @author Jochen Klein - Initial contribution */ +@NonNullByDefault public class Device { @JsonAdapter(ListOrStringDeserializer.class) protected @Nullable List identifiers; @@ -33,7 +35,6 @@ public class Device { protected @Nullable String sw_version; public @Nullable String getId() { - @Nullable List identifiers = this.identifiers; return identifiers == null ? null : String.join("_", identifiers); } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/OffDelayUpdateStateListener.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/OffDelayUpdateStateListener.java index c531975a204a3..9b117705bdf6c 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/OffDelayUpdateStateListener.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/listener/OffDelayUpdateStateListener.java @@ -51,7 +51,6 @@ public OffDelayUpdateStateListener(ChannelStateUpdateListener original, int offD public void updateChannelState(final ChannelUID channelUID, State state) { super.updateChannelState(channelUID, state); - @Nullable ScheduledFuture newDelay = null; if (OnOffType.ON == state) { From 3906c9d898ff5bed18c6f8049b1c91708ea3f3c6 Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Tue, 29 Jun 2021 15:22:09 +0300 Subject: [PATCH 18/23] MQTT.Homeassistant Unit tests added for all components Signed-off-by: Anton Kharuzhy --- .../internal/AbstractHomeAssistantTests.java | 8 +- .../component/AbstractComponentTests.java | 31 ++++- .../component/AlarmControlPanelTests.java | 95 ++++++++++++++ .../internal/component/BinarySensorTests.java | 89 +++++++++++++ .../internal/component/CameraTests.java | 69 ++++++++++ .../internal/component/CoverTests.java | 88 +++++++++++++ .../internal/component/FanTests.java | 84 ++++++++++++ .../internal/component/LightTests.java | 91 +++++++++++++ .../internal/component/LockTests.java | 120 ++++++++++++++++++ .../internal/component/SensorTests.java | 82 ++++++++++++ .../internal/component/image.png | Bin 0 -> 2041 bytes 11 files changed, 753 insertions(+), 4 deletions(-) create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanelTests.java create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/CameraTests.java create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/CoverTests.java create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/FanTests.java create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/LightTests.java create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/LockTests.java create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/image.png diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java index 416ccc72fc82b..1ecb50d75a3a0 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java @@ -22,7 +22,6 @@ import java.io.IOException; import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -175,7 +174,12 @@ protected String getResourceAsString(String relativePath) { } protected byte[] getResourceAsByteArray(String relativePath) { - return getResourceAsString(relativePath).getBytes(StandardCharsets.UTF_8); + try { + return Files.readAllBytes(getResourcePath(relativePath)); + } catch (IOException e) { + Assertions.fail(e); + } + throw new IllegalArgumentException(); } protected static String configTopicToMqtt(String configTopic) { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java index 339cc8096f546..f8efcc076a3c7 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java @@ -20,6 +20,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -40,6 +41,7 @@ import org.openhab.binding.mqtt.generic.TransformationServiceProvider; import org.openhab.binding.mqtt.generic.values.Value; import org.openhab.binding.mqtt.homeassistant.internal.AbstractHomeAssistantTests; +import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel; import org.openhab.binding.mqtt.homeassistant.internal.HaID; import org.openhab.binding.mqtt.homeassistant.internal.HandlerConfiguration; import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; @@ -138,10 +140,23 @@ public void disposeThingHandler() { * @param label label * @param valueClass value class */ - @SuppressWarnings("ConstantConditions") protected static void assertChannel(AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> component, String channelId, String stateTopic, String commandTopic, String label, Class valueClass) { var stateChannel = component.getChannel(channelId); + assertChannel(stateChannel, stateTopic, commandTopic, label, valueClass); + } + + /** + * Assert channel topics, label and value class + * + * @param stateChannel channel + * @param stateTopic state topic or empty string + * @param commandTopic command topic or empty string + * @param label label + * @param valueClass value class + */ + protected static void assertChannel(ComponentChannel stateChannel, String stateTopic, String commandTopic, + String label, Class valueClass) { assertThat(stateChannel.getChannel().getLabel(), is(label)); assertThat(stateChannel.getState().getStateTopic(), is(stateTopic)); assertThat(stateChannel.getState().getCommandTopic(), is(commandTopic)); @@ -161,7 +176,7 @@ protected static void assertState(AbstractComponent<@NonNull ? extends AbstractC } /** - * Assert that given payload was published on given topic. + * Assert that given payload was published exact-once on given topic. * * @param mqttTopic Mqtt topic * @param payload payload @@ -171,6 +186,18 @@ protected void assertPublished(String mqttTopic, String payload) { anyBoolean()); } + /** + * Assert that given payload was published N times on given topic. + * + * @param mqttTopic Mqtt topic + * @param payload payload + * @param t payload must be published N times on given topic + */ + protected void assertPublished(String mqttTopic, String payload, int t) { + verify(bridgeConnection, times(t)).publish(eq(mqttTopic), eq(payload.getBytes(StandardCharsets.UTF_8)), + anyInt(), anyBoolean()); + } + /** * Assert that given payload was not published on given topic. * diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanelTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanelTests.java new file mode 100644 index 0000000000000..6aa60e9705e64 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanelTests.java @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.component; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.openhab.binding.mqtt.generic.values.TextValue; +import org.openhab.core.library.types.StringType; + +/** + * Tests for {@link AlarmControlPanel} + * + * @author Anton Kharuzhy - Initial contribution + */ +@SuppressWarnings("ConstantConditions") +public class AlarmControlPanelTests extends AbstractComponentTests { + public static final String CONFIG_TOPIC = "alarm_control_panel/0x0000000000000000_alarm_control_panel_zigbee2mqtt"; + + @Test + public void testAlarmControlPanel() { + // @formatter:off + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), + "{ " + + " \"availability\": [ " + + " { " + + " \"topic\": \"zigbee2mqtt/bridge/state\" " + + " } " + + " ], " + + " \"code\": \"12345\", " + + " \"command_topic\": \"zigbee2mqtt/alarm/set/state\", " + + " \"device\": { " + + " \"identifiers\": [ " + + " \"zigbee2mqtt_0x0000000000000000\" " + + " ], " + + " \"manufacturer\": \"BestAlarmEver\", " + + " \"model\": \"Heavy duty super duper alarm\", " + + " \"name\": \"Alarm\", " + + " \"sw_version\": \"Zigbee2MQTT 1.18.2\" " + + " }, " + + " \"name\": \"alarm\", " + + " \"payload_arm_away\": \"ARM_AWAY_\", " + + " \"payload_arm_home\": \"ARM_HOME_\", " + + " \"payload_arm_night\": \"ARM_NIGHT_\", " + + " \"payload_arm_custom_bypass\": \"ARM_CUSTOM_BYPASS_\", " + + " \"payload_disarm\": \"DISARM_\", " + + " \"state_topic\": \"zigbee2mqtt/alarm/state\" " + + "} "); + // @formatter:on + + assertThat(component.channels.size(), is(4)); + assertThat(component.getName(), is("alarm")); + + assertChannel(component, AlarmControlPanel.stateChannelID, "zigbee2mqtt/alarm/state", "", "alarm", + TextValue.class); + assertChannel(component, AlarmControlPanel.switchDisarmChannelID, "", "zigbee2mqtt/alarm/set/state", "alarm", + TextValue.class); + assertChannel(component, AlarmControlPanel.switchArmAwayChannelID, "", "zigbee2mqtt/alarm/set/state", "alarm", + TextValue.class); + assertChannel(component, AlarmControlPanel.switchArmHomeChannelID, "", "zigbee2mqtt/alarm/set/state", "alarm", + TextValue.class); + + publishMessage("zigbee2mqtt/alarm/state", "armed_home"); + assertState(component, AlarmControlPanel.stateChannelID, new StringType("armed_home")); + publishMessage("zigbee2mqtt/alarm/state", "armed_away"); + assertState(component, AlarmControlPanel.stateChannelID, new StringType("armed_away")); + + component.getChannel(AlarmControlPanel.switchDisarmChannelID).getState() + .publishValue(new StringType("DISARM_")); + assertPublished("zigbee2mqtt/alarm/set/state", "DISARM_"); + component.getChannel(AlarmControlPanel.switchArmAwayChannelID).getState() + .publishValue(new StringType("ARM_AWAY_")); + assertPublished("zigbee2mqtt/alarm/set/state", "ARM_AWAY_"); + component.getChannel(AlarmControlPanel.switchArmHomeChannelID).getState() + .publishValue(new StringType("ARM_HOME_")); + assertPublished("zigbee2mqtt/alarm/set/state", "ARM_HOME_"); + } + + protected Set getConfigTopics() { + return Set.of(CONFIG_TOPIC); + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java new file mode 100644 index 0000000000000..293e122bfb5ff --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.component; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.openhab.binding.mqtt.generic.values.OnOffValue; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.types.UnDefType; + +/** + * Tests for {@link BinarySensor} + * + * @author Anton Kharuzhy - Initial contribution + */ +public class BinarySensorTests extends AbstractComponentTests { + public static final String CONFIG_TOPIC = "binary_sensor/0x0000000000000000_binary_sensor_zigbee2mqtt"; + + @Test + public void test() throws InterruptedException { + // @formatter:off + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), + "{ " + + " \"availability\": [ " + + " { " + + " \"topic\": \"zigbee2mqtt/bridge/state\" " + + " } " + + " ], " + + " \"device\": { " + + " \"identifiers\": [ " + + " \"zigbee2mqtt_0x0000000000000000\" " + + " ], " + + " \"manufacturer\": \"Sensors inc\", " + + " \"model\": \"On Off Sensor\", " + + " \"name\": \"OnOffSensor\", " + + " \"sw_version\": \"Zigbee2MQTT 1.18.2\" " + + " }, " + + " \"name\": \"onoffsensor\", " + + " \"expire_after\": \"2\", " + + " \"force_update\": \"true\", " + + " \"off_delay\": \"1\", " + + " \"payload_off\": \"OFF_\", " + + " \"payload_on\": \"ON_\", " + + " \"state_topic\": \"zigbee2mqtt/sensor/state\", " + + " \"unique_id\": \"sn1\", " + + " \"value_template\": \"{{ value_json.state }}\" " + + "}"); + // @formatter:on + + assertThat(component.channels.size(), is(1)); + assertThat(component.getName(), is("onoffsensor")); + assertThat(component.getGroupUID().getId(), is("sn1")); + + assertChannel(component, BinarySensor.sensorChannelID, "zigbee2mqtt/sensor/state", "", "value", + OnOffValue.class); + + publishMessage("zigbee2mqtt/sensor/state", "{ \"state\": \"ON_\" }"); + assertState(component, BinarySensor.sensorChannelID, OnOffType.ON); + publishMessage("zigbee2mqtt/sensor/state", "{ \"state\": \"ON_\" }"); + assertState(component, BinarySensor.sensorChannelID, OnOffType.ON); + publishMessage("zigbee2mqtt/sensor/state", "{ \"state\": \"OFF_\" }"); + assertState(component, BinarySensor.sensorChannelID, OnOffType.OFF); + publishMessage("zigbee2mqtt/sensor/state", "{ \"state\": \"ON_\" }"); + assertState(component, BinarySensor.sensorChannelID, OnOffType.ON); + + Thread.sleep(1100); + assertState(component, BinarySensor.sensorChannelID, OnOffType.OFF); + Thread.sleep(2100); + assertState(component, BinarySensor.sensorChannelID, UnDefType.UNDEF); + } + + protected Set getConfigTopics() { + return Set.of(CONFIG_TOPIC); + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/CameraTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/CameraTests.java new file mode 100644 index 0000000000000..7b7f794dad862 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/CameraTests.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.component; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.openhab.binding.mqtt.generic.values.ImageValue; +import org.openhab.core.library.types.RawType; + +/** + * Tests for {@link Camera} + * + * @author Anton Kharuzhy - Initial contribution + */ +public class CameraTests extends AbstractComponentTests { + public static final String CONFIG_TOPIC = "camera/0x0000000000000000_camera_zigbee2mqtt"; + + @Test + public void test() throws InterruptedException { + // @formatter:off + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), + "{ " + + " \"availability\": [ " + + " { " + + " \"topic\": \"zigbee2mqtt/bridge/state\" " + + " } " + + " ], " + + " \"device\": { " + + " \"identifiers\": [ " + + " \"zigbee2mqtt_0x0000000000000000\" " + + " ], " + + " \"manufacturer\": \"Cameras inc\", " + + " \"model\": \"Camera\", " + + " \"name\": \"camera\", " + + " \"sw_version\": \"Zigbee2MQTT 1.18.2\" " + + " }, " + + " \"name\": \"cam1\", " + + " \"topic\": \"zigbee2mqtt/cam1/state\"" + + "}"); + // @formatter:on + + assertThat(component.channels.size(), is(1)); + assertThat(component.getName(), is("cam1")); + + assertChannel(component, Camera.cameraChannelID, "zigbee2mqtt/cam1/state", "", "cam1", ImageValue.class); + + var imageBytes = getResourceAsByteArray("component/image.png"); + publishMessage("zigbee2mqtt/cam1/state", imageBytes); + assertState(component, Camera.cameraChannelID, new RawType(imageBytes, "image/png")); + } + + protected Set getConfigTopics() { + return Set.of(CONFIG_TOPIC); + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/CoverTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/CoverTests.java new file mode 100644 index 0000000000000..c8ad1d6f1ab5f --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/CoverTests.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.component; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.openhab.binding.mqtt.generic.values.RollershutterValue; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.StopMoveType; + +/** + * Tests for {@link Cover} + * + * @author Anton Kharuzhy - Initial contribution + */ +@SuppressWarnings("ConstantConditions") +public class CoverTests extends AbstractComponentTests { + public static final String CONFIG_TOPIC = "cover/0x0000000000000000_cover_zigbee2mqtt"; + + @Test + public void test() throws InterruptedException { + // @formatter:off + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), + "{ " + + " \"availability\": [ " + + " { " + + " \"topic\": \"zigbee2mqtt/bridge/state\" " + + " } " + + " ], " + + " \"device\": { " + + " \"identifiers\": [ " + + " \"zigbee2mqtt_0x0000000000000000\" " + + " ], " + + " \"manufacturer\": \"Covers inc\", " + + " \"model\": \"cover v1\", " + + " \"name\": \"Cover\", " + + " \"sw_version\": \"Zigbee2MQTT 1.18.2\" " + + " }, " + + " \"name\": \"cover\", " + + " \"payload_open\": \"OPEN_\", " + + " \"payload_close\": \"CLOSE_\", " + + " \"payload_stop\": \"STOP_\", " + + " \"state_topic\": \"zigbee2mqtt/cover/state\", " + + " \"command_topic\": \"zigbee2mqtt/cover/set/state\" " + + "}"); + // @formatter:on + + assertThat(component.channels.size(), is(1)); + assertThat(component.getName(), is("cover")); + + assertChannel(component, Cover.switchChannelID, "zigbee2mqtt/cover/state", "zigbee2mqtt/cover/set/state", + "cover", RollershutterValue.class); + + publishMessage("zigbee2mqtt/cover/state", "100"); + assertState(component, Cover.switchChannelID, PercentType.HUNDRED); + publishMessage("zigbee2mqtt/cover/state", "0"); + assertState(component, Cover.switchChannelID, PercentType.ZERO); + + component.getChannel(Cover.switchChannelID).getState().publishValue(PercentType.ZERO); + assertPublished("zigbee2mqtt/cover/set/state", "OPEN_"); + component.getChannel(Cover.switchChannelID).getState().publishValue(PercentType.HUNDRED); + assertPublished("zigbee2mqtt/cover/set/state", "CLOSE_"); + component.getChannel(Cover.switchChannelID).getState().publishValue(StopMoveType.STOP); + assertPublished("zigbee2mqtt/cover/set/state", "STOP_"); + component.getChannel(Cover.switchChannelID).getState().publishValue(PercentType.ZERO); + assertPublished("zigbee2mqtt/cover/set/state", "OPEN_", 2); + component.getChannel(Cover.switchChannelID).getState().publishValue(StopMoveType.STOP); + assertPublished("zigbee2mqtt/cover/set/state", "STOP_", 2); + } + + protected Set getConfigTopics() { + return Set.of(CONFIG_TOPIC); + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/FanTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/FanTests.java new file mode 100644 index 0000000000000..34c5c7f954f54 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/FanTests.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.component; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.openhab.binding.mqtt.generic.values.OnOffValue; +import org.openhab.core.library.types.OnOffType; + +/** + * Tests for {@link Fan} + * + * @author Anton Kharuzhy - Initial contribution + */ +@SuppressWarnings("ALL") +public class FanTests extends AbstractComponentTests { + public static final String CONFIG_TOPIC = "fan/0x0000000000000000_fan_zigbee2mqtt"; + + @Test + public void test() throws InterruptedException { + // @formatter:off + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), + "{ " + + " \"availability\": [ " + + " { " + + " \"topic\": \"zigbee2mqtt/bridge/state\" " + + " } " + + " ], " + + " \"device\": { " + + " \"identifiers\": [ " + + " \"zigbee2mqtt_0x0000000000000000\" " + + " ], " + + " \"manufacturer\": \"Fans inc\", " + + " \"model\": \"Fan\", " + + " \"name\": \"FanBlower\", " + + " \"sw_version\": \"Zigbee2MQTT 1.18.2\" " + + " }, " + + " \"name\": \"fan\", " + + " \"payload_off\": \"OFF_\", " + + " \"payload_on\": \"ON_\", " + + " \"state_topic\": \"zigbee2mqtt/fan/state\", " + + " \"command_topic\": \"zigbee2mqtt/fan/set/state\" " + + "}"); + // @formatter:on + + assertThat(component.channels.size(), is(1)); + assertThat(component.getName(), is("fan")); + + assertChannel(component, Fan.switchChannelID, "zigbee2mqtt/fan/state", "zigbee2mqtt/fan/set/state", "fan", + OnOffValue.class); + + publishMessage("zigbee2mqtt/fan/state", "ON_"); + assertState(component, Fan.switchChannelID, OnOffType.ON); + publishMessage("zigbee2mqtt/fan/state", "ON_"); + assertState(component, Fan.switchChannelID, OnOffType.ON); + publishMessage("zigbee2mqtt/fan/state", "OFF_"); + assertState(component, Fan.switchChannelID, OnOffType.OFF); + publishMessage("zigbee2mqtt/fan/state", "ON_"); + assertState(component, Fan.switchChannelID, OnOffType.ON); + + component.getChannel(Fan.switchChannelID).getState().publishValue(OnOffType.OFF); + assertPublished("zigbee2mqtt/fan/set/state", "OFF_"); + component.getChannel(Fan.switchChannelID).getState().publishValue(OnOffType.ON); + assertPublished("zigbee2mqtt/fan/set/state", "ON_"); + } + + protected Set getConfigTopics() { + return Set.of(CONFIG_TOPIC); + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/LightTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/LightTests.java new file mode 100644 index 0000000000000..f71af007f6415 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/LightTests.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.component; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.openhab.binding.mqtt.generic.values.ColorValue; +import org.openhab.core.library.types.HSBType; +import org.openhab.core.library.types.OnOffType; + +/** + * Tests for {@link Light} + * The current {@link Light} is non-compliant with the Specification and must be rewritten from scratch. + * + * @author Anton Kharuzhy - Initial contribution + */ +public class LightTests extends AbstractComponentTests { + public static final String CONFIG_TOPIC = "light/0x0000000000000000_light_zigbee2mqtt"; + + @Test + public void test() throws InterruptedException { + // @formatter:off + var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC), + "{ " + + " \"availability\": [ " + + " { " + + " \"topic\": \"zigbee2mqtt/bridge/state\" " + + " } " + + " ], " + + " \"device\": { " + + " \"identifiers\": [ " + + " \"zigbee2mqtt_0x0000000000000000\" " + + " ], " + + " \"manufacturer\": \"Lights inc\", " + + " \"model\": \"light v1\", " + + " \"name\": \"Light\", " + + " \"sw_version\": \"Zigbee2MQTT 1.18.2\" " + + " }, " + + " \"name\": \"light\", " + + " \"state_topic\": \"zigbee2mqtt/light/state\", " + + " \"command_topic\": \"zigbee2mqtt/light/set/state\", " + + " \"state_value_template\": \"{{ value_json.power }}\", " + + " \"payload_on\": \"ON_\", " + + " \"payload_off\": \"OFF_\", " + + " \"rgb_state_topic\": \"zigbee2mqtt/light/rgb\", " + + " \"rgb_command_topic\": \"zigbee2mqtt/light/set/rgb\", " + + " \"rgb_value_template\": \"{{ value_json.rgb }}\", " + + " \"brightness_state_topic\": \"zigbee2mqtt/light/brightness\", " + + " \"brightness_command_topic\": \"zigbee2mqtt/light/set/brightness\", " + + " \"brightness_value_template\": \"{{ value_json.br }}\" " + + "}"); + // @formatter:on + + assertThat(component.channels.size(), is(1)); + assertThat(component.getName(), is("light")); + + assertChannel(component, Light.colorChannelID, "zigbee2mqtt/light/rgb", "zigbee2mqtt/light/set/rgb", "light", + ColorValue.class); + + assertChannel(component.switchChannel, "zigbee2mqtt/light/state", "zigbee2mqtt/light/set/state", "light", + ColorValue.class); + assertChannel(component.brightnessChannel, "zigbee2mqtt/light/brightness", "zigbee2mqtt/light/set/brightness", + "light", ColorValue.class); + + publishMessage("zigbee2mqtt/light/rgb", "{\"rgb\": \"255,255,255\"}"); + assertState(component, Light.colorChannelID, HSBType.fromRGB(255, 255, 255)); + publishMessage("zigbee2mqtt/light/rgb", "{\"rgb\": \"10,20,30\"}"); + assertState(component, Light.colorChannelID, HSBType.fromRGB(10, 20, 30)); + + component.switchChannel.getState().publishValue(OnOffType.OFF); + assertPublished("zigbee2mqtt/light/set/state", "0,0,0"); + } + + protected Set getConfigTopics() { + return Set.of(CONFIG_TOPIC); + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/LockTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/LockTests.java new file mode 100644 index 0000000000000..c16672f3e1a36 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/LockTests.java @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.component; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Set; + +import org.junit.Rule; +import org.junit.jupiter.api.Test; +import org.junit.rules.ExpectedException; +import org.openhab.binding.mqtt.generic.values.OnOffValue; +import org.openhab.core.library.types.OnOffType; + +/** + * Tests for {@link Lock} + * + * @author Anton Kharuzhy - Initial contribution + */ +@SuppressWarnings("ALL") +public class LockTests extends AbstractComponentTests { + public static final String CONFIG_TOPIC = "lock/0x0000000000000000_lock_zigbee2mqtt"; + + @Rule + public ExpectedException exceptionGrabber = ExpectedException.none(); + + @Test + public void test() throws InterruptedException { + // @formatter:off + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), + "{ " + + " \"availability\": [ " + + " { " + + " \"topic\": \"zigbee2mqtt/bridge/state\" " + + " } " + + " ], " + + " \"device\": { " + + " \"identifiers\": [ " + + " \"zigbee2mqtt_0x0000000000000000\" " + + " ], " + + " \"manufacturer\": \"Locks inc\", " + + " \"model\": \"Lock\", " + + " \"name\": \"LockBlower\", " + + " \"sw_version\": \"Zigbee2MQTT 1.18.2\" " + + " }, " + + " \"name\": \"lock\", " + + " \"payload_unlock\": \"UNLOCK_\", " + + " \"payload_lock\": \"LOCK_\", " + + " \"state_topic\": \"zigbee2mqtt/lock/state\", " + + " \"command_topic\": \"zigbee2mqtt/lock/set/state\" " + + "}"); + // @formatter:on + + assertThat(component.channels.size(), is(1)); + assertThat(component.getName(), is("lock")); + + assertChannel(component, Lock.switchChannelID, "zigbee2mqtt/lock/state", "zigbee2mqtt/lock/set/state", "lock", + OnOffValue.class); + + publishMessage("zigbee2mqtt/lock/state", "LOCK_"); + assertState(component, Lock.switchChannelID, OnOffType.ON); + publishMessage("zigbee2mqtt/lock/state", "LOCK_"); + assertState(component, Lock.switchChannelID, OnOffType.ON); + publishMessage("zigbee2mqtt/lock/state", "UNLOCK_"); + assertState(component, Lock.switchChannelID, OnOffType.OFF); + publishMessage("zigbee2mqtt/lock/state", "LOCK_"); + assertState(component, Lock.switchChannelID, OnOffType.ON); + + component.getChannel(Lock.switchChannelID).getState().publishValue(OnOffType.OFF); + assertPublished("zigbee2mqtt/lock/set/state", "UNLOCK_"); + component.getChannel(Lock.switchChannelID).getState().publishValue(OnOffType.ON); + assertPublished("zigbee2mqtt/lock/set/state", "LOCK_"); + } + + @Test + public void forceOptimisticIsNotSupported() { + exceptionGrabber.expect(UnsupportedOperationException.class); + + // @formatter:off + publishMessage(configTopicToMqtt(CONFIG_TOPIC), + "{ " + + " \"availability\": [ " + + " { " + + " \"topic\": \"zigbee2mqtt/bridge/state\" " + + " } " + + " ], " + + " \"device\": { " + + " \"identifiers\": [ " + + " \"zigbee2mqtt_0x0000000000000000\" " + + " ], " + + " \"manufacturer\": \"Locks inc\", " + + " \"model\": \"Lock\", " + + " \"name\": \"LockBlower\", " + + " \"sw_version\": \"Zigbee2MQTT 1.18.2\" " + + " }, " + + " \"name\": \"lock\", " + + " \"payload_unlock\": \"UNLOCK_\", " + + " \"payload_lock\": \"LOCK_\", " + + " \"optimistic\": \"true\", " + + " \"state_topic\": \"zigbee2mqtt/lock/state\", " + + " \"command_topic\": \"zigbee2mqtt/lock/set/state\" " + + "}"); + // @formatter:on + } + + protected Set getConfigTopics() { + return Set.of(CONFIG_TOPIC); + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java new file mode 100644 index 0000000000000..ce9638e6f32d0 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.component; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.openhab.binding.mqtt.generic.values.NumberValue; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.types.UnDefType; + +/** + * Tests for {@link Sensor} + * + * @author Anton Kharuzhy - Initial contribution + */ +@SuppressWarnings("ConstantConditions") +public class SensorTests extends AbstractComponentTests { + public static final String CONFIG_TOPIC = "sensor/0x0000000000000000_sensor_zigbee2mqtt"; + + @Test + public void test() throws InterruptedException { + // @formatter:off + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), + "{ " + + " \"availability\": [ " + + " { " + + " \"topic\": \"zigbee2mqtt/bridge/state\" " + + " } " + + " ], " + + " \"device\": { " + + " \"identifiers\": [ " + + " \"zigbee2mqtt_0x0000000000000000\" " + + " ], " + + " \"manufacturer\": \"Sensors inc\", " + + " \"model\": \"Sensor\", " + + " \"name\": \"Sensor\", " + + " \"sw_version\": \"Zigbee2MQTT 1.18.2\" " + + " }, " + + " \"name\": \"sensor1\", " + + " \"expire_after\": \"1\", " + + " \"force_update\": \"true\", " + + " \"unit_of_measurement\": \"W\", " + + " \"state_topic\": \"zigbee2mqtt/sensor/state\", " + + " \"unique_id\": \"sn1\" " + + "}"); + // @formatter:on + + assertThat(component.channels.size(), is(1)); + assertThat(component.getName(), is("sensor1")); + assertThat(component.getGroupUID().getId(), is("sn1")); + + assertChannel(component, Sensor.sensorChannelID, "zigbee2mqtt/sensor/state", "", "sensor1", NumberValue.class); + + publishMessage("zigbee2mqtt/sensor/state", "10"); + assertState(component, Sensor.sensorChannelID, DecimalType.valueOf("10")); + publishMessage("zigbee2mqtt/sensor/state", "20"); + assertState(component, Sensor.sensorChannelID, DecimalType.valueOf("20")); + assertThat(component.getChannel(Sensor.sensorChannelID).getState().getCache().createStateDescription(true) + .build().getPattern(), is("%s W")); + + Thread.sleep(1100); + assertState(component, Sensor.sensorChannelID, UnDefType.UNDEF); + } + + protected Set getConfigTopics() { + return Set.of(CONFIG_TOPIC); + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/image.png b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/resources/org/openhab/binding/mqtt/homeassistant/internal/component/image.png new file mode 100644 index 0000000000000000000000000000000000000000..100bcfd570b5e183665ec85e93b5f3526cfcff53 GIT binary patch literal 2041 zcmchVdo+}37{K2d6h=ygA?r-LORbpsW`;4o+zsDckjA79#mpFHavNsklHGMpN^(h5 z>R5HE?T(aERz#6n8%dJWDs++VHr1|uC%aC|?m7LjXTS5l-}64t^LsAuCELdvo26~6 z4FH(MVYB#9l2lVu4gOjCHZ?<`9_HfZ0zk!9!h4|x)Nu+v<_eBoGkK^Y#caM807>Qm zq+|f_7MfBX01$@&@F*Anv;+V{dFk2Z_5k4dKAr-12(Z~~4u^wbn1_c)Wo6|Mhd9VJ z9^@JgaE*t!M0a=hL5|S?$9RZGWMi0ujgD{)2YE&}Ii};hIWX#f_+W>hQson|e@q4U z3&(WVgnhbeIzOG9v`h(~5-*1mgImnIDgmSs1+dVkId7dY^ArvqU&w*U2gfBfU0 zI2xJ(H-|0g*X}u=?VkVJ2zx1W&1m?gA~naGH?)qljGRrv!ZDx9T<4AfZ1Y^CfBArbvGc#td2Dy>U=w}Vx zezd~3%tgoZr~tq8d3}DAygBn^hICHyF1QDrh=;M7#DpdBP|=dJS1AA(W~wGGW=Sht z;N3VZ7XkB%IGBV(XxW>xH}f{q=;A_2==N;|+w+SG3yX^Vd_sg5^HvJ@es($<7OjU0 z4(>+|{}3)U?YiFe^NkK$#G2+9AzHg3La*vf=74ZX_=12u61{5o`Sg3jMPY_DHf=cu z$FlD9I^6XyaX5c&_B$rMLA~T^sc!h2a$?Ky(05Ey-VXgob!Cme*GY3lqXfqY;|3k! z;SC!y-Ck8yV60fVD{F7Lo4CHbZDB>$z5azSOti8&dOrFsUuF54)obs&-f;cGi$D`C zBP=(hL9jSP!oxKg?NI0rnbXq;UKZruH>?*RJOfiQzUP9ag`%cp#{!EWzQ3RDiVJP4 zV`?h34?25}%5B<#>51f73oxrd_dCL;NoDw$Gs81N%aXrf?@3QDK7LBQJnh!q6QxX6 zsslZ6PikuVUY&CMe&_Osa=H^HCJ3OZcy@W(kFVtcOE2ad;PsrxXx?Nt^5S`lK&iRT z)ubh`^TwTy*nQ`6(g_Cb!2wC{-p*bIu;Xu7C+rs)er}PG&}#0rr=Wy%3 z=zagPXcyy~=C;O)yzc%IoAuWMg?g{PL@5O0RXQdw4J`vt5*^MKTRI%k!|TuYKV=`+ z#O+kCYJKu-EuA$tplVcJi02pZx&q=8ew&eD5IV92msQP(?89?2b3E4toVGL7?XFY1 z#jf66v@ zmmmm=AU#hh7Jmzfj1G~7CjNWC;mYFeFu+4qK@lCQj2FgA04GuyCh-!6$;9C#2HBR( zw6Y`75UQOOLW5OF%c%^KRH>As6iR%2JXxlq;e<3fIyTH@eg9>ck*LZjkj6!dP?Qxd zj*FB;DN)o1q6$DgFkVZU!hEQSqEd-0OsWJnR9bzz&vM8Zew61=W~D4dDODjR$!fh2 zG5QGcl0{0CiE;_}&=o}<73Ngw=RGBlmjF_LYGcb}*jm$=^xC1lYalZ3qddN{BzVkJ zvNg}ub*Oe*7j*l6g}} sMW{9v6fKuTNrj>ynK(Ksh)PGSt!>G$!L4PP=V5xlarb5&a}7@W1FyT|P5=M^ literal 0 HcmV?d00001 From 37d74f7d61c895621531bac8f446ce246053d75c Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Wed, 14 Jul 2021 10:46:40 +0300 Subject: [PATCH 19/23] MQTT.Homeassistant Unit tests stability fix Signed-off-by: Anton Kharuzhy --- .../homeassistant/internal/AbstractHomeAssistantTests.java | 3 ++- .../homeassistant/internal/component/BinarySensorTests.java | 6 ++---- .../mqtt/homeassistant/internal/component/SensorTests.java | 3 +-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java index 1ecb50d75a3a0..49879ac8c6a2c 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/AbstractHomeAssistantTests.java @@ -45,6 +45,7 @@ import org.openhab.binding.mqtt.handler.BrokerHandler; import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; import org.openhab.core.io.transport.mqtt.MqttMessageSubscriber; +import org.openhab.core.test.java.JavaTest; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; @@ -68,7 +69,7 @@ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.WARN) @NonNullByDefault -public abstract class AbstractHomeAssistantTests { +public abstract class AbstractHomeAssistantTests extends JavaTest { public static final String BINDING_ID = "mqtt"; public static final String BRIDGE_TYPE_ID = "broker"; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java index 293e122bfb5ff..82fff31132222 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java @@ -77,10 +77,8 @@ public void test() throws InterruptedException { publishMessage("zigbee2mqtt/sensor/state", "{ \"state\": \"ON_\" }"); assertState(component, BinarySensor.sensorChannelID, OnOffType.ON); - Thread.sleep(1100); - assertState(component, BinarySensor.sensorChannelID, OnOffType.OFF); - Thread.sleep(2100); - assertState(component, BinarySensor.sensorChannelID, UnDefType.UNDEF); + waitForAssert(() -> assertState(component, BinarySensor.sensorChannelID, OnOffType.OFF), 2000, 100); + waitForAssert(() -> assertState(component, BinarySensor.sensorChannelID, UnDefType.UNDEF), 3000, 100); } protected Set getConfigTopics() { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java index ce9638e6f32d0..53a9393571daf 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java @@ -72,8 +72,7 @@ public void test() throws InterruptedException { assertThat(component.getChannel(Sensor.sensorChannelID).getState().getCache().createStateDescription(true) .build().getPattern(), is("%s W")); - Thread.sleep(1100); - assertState(component, Sensor.sensorChannelID, UnDefType.UNDEF); + waitForAssert(() -> assertState(component, Sensor.sensorChannelID, UnDefType.UNDEF), 2000, 100); } protected Set getConfigTopics() { From 5bb66a894c02a84a2c9c926873744176e2a7afa7 Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Wed, 14 Jul 2021 11:35:42 +0300 Subject: [PATCH 20/23] MQTT.Homeassistant @NonNullByDefault removed from Device, config.dto package created Signed-off-by: Anton Kharuzhy --- .../internal/component/AbstractComponent.java | 2 +- .../internal/component/AlarmControlPanel.java | 2 +- .../homeassistant/internal/component/BinarySensor.java | 2 +- .../mqtt/homeassistant/internal/component/Camera.java | 2 +- .../mqtt/homeassistant/internal/component/Climate.java | 2 +- .../internal/component/ComponentFactory.java | 2 +- .../mqtt/homeassistant/internal/component/Cover.java | 2 +- .../mqtt/homeassistant/internal/component/Fan.java | 2 +- .../mqtt/homeassistant/internal/component/Light.java | 2 +- .../mqtt/homeassistant/internal/component/Lock.java | 2 +- .../mqtt/homeassistant/internal/component/Sensor.java | 2 +- .../mqtt/homeassistant/internal/component/Switch.java | 2 +- .../config/ChannelConfigurationTypeAdapterFactory.java | 10 ++++++---- .../internal/config/ConnectionDeserializer.java | 2 ++ .../config/{ => dto}/AbstractChannelConfiguration.java | 6 +++++- .../internal/config/{ => dto}/Availability.java | 2 +- .../internal/config/{ => dto}/AvailabilityMode.java | 2 +- .../internal/config/{ => dto}/Connection.java | 3 ++- .../internal/config/{ => dto}/Device.java | 5 ++--- .../internal/discovery/HomeAssistantDiscovery.java | 2 +- .../internal/component/AbstractComponentTests.java | 2 +- .../internal/component/HAConfigurationTests.java | 6 +++--- 22 files changed, 36 insertions(+), 28 deletions(-) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/{ => dto}/AbstractChannelConfiguration.java (97%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/{ => dto}/Availability.java (93%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/{ => dto}/AvailabilityMode.java (94%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/{ => dto}/Connection.java (87%) rename bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/{ => dto}/Device.java (91%) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java index 30a3eecdfa37e..6cdde589330db 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java @@ -30,7 +30,7 @@ import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel; import org.openhab.binding.mqtt.homeassistant.internal.HaID; import org.openhab.binding.mqtt.homeassistant.internal.component.ComponentFactory.ComponentConfiguration; -import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; import org.openhab.core.thing.ChannelGroupUID; import org.openhab.core.thing.type.ChannelDefinition; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java index 05e3062e158c3..cd1db7d2d01c9 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java @@ -15,7 +15,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.values.TextValue; -import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; /** * A MQTT alarm control panel, following the https://www.home-assistant.io/components/alarm_control_panel.mqtt/ diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensor.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensor.java index f2a987ce226e4..5fc140b76100d 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensor.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensor.java @@ -19,7 +19,7 @@ import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener; import org.openhab.binding.mqtt.generic.values.OnOffValue; import org.openhab.binding.mqtt.generic.values.Value; -import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; import org.openhab.binding.mqtt.homeassistant.internal.listener.ExpireUpdateStateListener; import org.openhab.binding.mqtt.homeassistant.internal.listener.OffDelayUpdateStateListener; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java index 58567f8ced710..0b877cf01800c 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java @@ -14,7 +14,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.mqtt.generic.values.ImageValue; -import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; /** * A MQTT camera, following the https://www.home-assistant.io/components/camera.mqtt/ specification. diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java index e143668b07de8..0145cc0257b78 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java @@ -25,7 +25,7 @@ import org.openhab.binding.mqtt.generic.values.TextValue; import org.openhab.binding.mqtt.generic.values.Value; import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel; -import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; import org.openhab.core.library.types.StringType; import org.openhab.core.types.Command; import org.openhab.core.types.State; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java index 7a2a1cba9328b..ed7b530ba9908 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java @@ -20,7 +20,7 @@ import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener; import org.openhab.binding.mqtt.generic.TransformationServiceProvider; import org.openhab.binding.mqtt.homeassistant.internal.HaID; -import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; import org.openhab.core.thing.ThingUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java index 1d706a1d99963..bf7af7b942ca0 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java @@ -15,7 +15,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.values.RollershutterValue; -import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; /** * A MQTT Cover component, following the https://www.home-assistant.io/components/cover.mqtt/ specification. diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java index 74ae40c43bdfa..9f74032fa167b 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java @@ -15,7 +15,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.values.OnOffValue; -import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; /** * A MQTT Fan component, following the https://www.home-assistant.io/components/fan.mqtt/ specification. diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java index 66549b25060a5..cf7f962c862d2 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java @@ -23,7 +23,7 @@ import org.openhab.binding.mqtt.generic.mapping.ColorMode; import org.openhab.binding.mqtt.generic.values.ColorValue; import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel; -import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; import org.openhab.core.thing.ChannelUID; import org.openhab.core.types.Command; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java index 3ac5df1452901..e2461fee260c4 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java @@ -15,7 +15,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.values.OnOffValue; -import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; /** * A MQTT lock, following the https://www.home-assistant.io/components/lock.mqtt/ specification. diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java index aea0447562e3f..43bbf4a7a1843 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java @@ -21,7 +21,7 @@ import org.openhab.binding.mqtt.generic.values.NumberValue; import org.openhab.binding.mqtt.generic.values.TextValue; import org.openhab.binding.mqtt.generic.values.Value; -import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; import org.openhab.binding.mqtt.homeassistant.internal.listener.ExpireUpdateStateListener; /** diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Switch.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Switch.java index 2dcf56655df6f..6d9996f035ea9 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Switch.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Switch.java @@ -15,7 +15,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.values.OnOffValue; -import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; /** * A MQTT switch, following the https://www.home-assistant.io/components/switch.mqtt/ specification. diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/ChannelConfigurationTypeAdapterFactory.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/ChannelConfigurationTypeAdapterFactory.java index e94bd60e4d462..ab4d08efb23fe 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/ChannelConfigurationTypeAdapterFactory.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/ChannelConfigurationTypeAdapterFactory.java @@ -18,6 +18,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.homeassistant.internal.MappingJsonReader; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.Device; import com.google.gson.Gson; import com.google.gson.TypeAdapter; @@ -31,14 +33,14 @@ * *

* It will create a type adapter for every class derived from {@link - * org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration} and ensures, + * AbstractChannelConfiguration} and ensures, * that abbreviated names are replaces with their long versions during the read. * *

* In elements, whose name end in'_topic' '~' replacement is performed. * *

- * The adapters also handle {@link org.openhab.binding.mqtt.homeassistant.internal.config.Device} + * The adapters also handle {@link Device} * * @author Jochen Klein - Initial contribution */ @@ -62,7 +64,7 @@ public TypeAdapter create(@Nullable Gson gson, @Nullable TypeToken typ /** * Handle {@link - * org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration} + * AbstractChannelConfiguration} * * @param gson parser * @param type type @@ -111,7 +113,7 @@ public void write(JsonWriter out, @Nullable T value) throws IOException { private void expandTidleInTopics(AbstractChannelConfiguration config) { Class type = config.getClass(); - String tilde = config.tilde; + String tilde = config.getTilde(); while (type != Object.class) { Field[] fields = type.getDeclaredFields(); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/ConnectionDeserializer.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/ConnectionDeserializer.java index 99a3985c256e4..405931b790e55 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/ConnectionDeserializer.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/ConnectionDeserializer.java @@ -14,6 +14,8 @@ import java.lang.reflect.Type; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.Connection; + import com.google.gson.JsonArray; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/AbstractChannelConfiguration.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AbstractChannelConfiguration.java similarity index 97% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/AbstractChannelConfiguration.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AbstractChannelConfiguration.java index 4aa072d1ad09d..c8d2893f37cb4 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/AbstractChannelConfiguration.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AbstractChannelConfiguration.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal.config; +package org.openhab.binding.mqtt.homeassistant.internal.config.dto; import java.util.List; import java.util.Map; @@ -166,6 +166,10 @@ public List getAvailability() { return availability; } + public String getTilde() { + return tilde; + } + public AvailabilityMode getAvailabilityMode() { return availability_mode; } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Availability.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Availability.java similarity index 93% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Availability.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Availability.java index fd9510a88af2b..ba4be867337e0 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Availability.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Availability.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal.config; +package org.openhab.binding.mqtt.homeassistant.internal.config.dto; /** * MQTT topic subscribed to receive availability (online/offline) updates. Must not be used together with diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/AvailabilityMode.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AvailabilityMode.java similarity index 94% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/AvailabilityMode.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AvailabilityMode.java index 22be1b0bf968b..7389994dd52da 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/AvailabilityMode.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AvailabilityMode.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal.config; +package org.openhab.binding.mqtt.homeassistant.internal.config.dto; import com.google.gson.annotations.SerializedName; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Connection.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Connection.java similarity index 87% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Connection.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Connection.java index 18d076daf60c0..fe2455286ab6b 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Connection.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Connection.java @@ -10,9 +10,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal.config; +package org.openhab.binding.mqtt.homeassistant.internal.config.dto; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mqtt.homeassistant.internal.config.ConnectionDeserializer; import com.google.gson.annotations.JsonAdapter; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Device.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Device.java similarity index 91% rename from bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Device.java rename to bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Device.java index e39515b158372..779be087ba4c2 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/Device.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Device.java @@ -10,12 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mqtt.homeassistant.internal.config; +package org.openhab.binding.mqtt.homeassistant.internal.config.dto; import java.util.List; -import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mqtt.homeassistant.internal.config.ListOrStringDeserializer; import com.google.gson.annotations.JsonAdapter; @@ -24,7 +24,6 @@ * * @author Jochen Klein - Initial contribution */ -@NonNullByDefault public class Device { @JsonAdapter(ListOrStringDeserializer.class) protected @Nullable List identifiers; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscovery.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscovery.java index e7accbbd6ded4..ed00f64a1e8ee 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscovery.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/discovery/HomeAssistantDiscovery.java @@ -35,8 +35,8 @@ import org.openhab.binding.mqtt.homeassistant.generic.internal.MqttBindingConstants; import org.openhab.binding.mqtt.homeassistant.internal.HaID; import org.openhab.binding.mqtt.homeassistant.internal.HandlerConfiguration; -import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; import org.openhab.binding.mqtt.homeassistant.internal.config.ChannelConfigurationTypeAdapterFactory; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryService; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java index f8efcc076a3c7..f35aa45db174c 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java @@ -44,7 +44,7 @@ import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel; import org.openhab.binding.mqtt.homeassistant.internal.HaID; import org.openhab.binding.mqtt.homeassistant.internal.HandlerConfiguration; -import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; import org.openhab.binding.mqtt.homeassistant.internal.handler.HomeAssistantThingHandler; import org.openhab.core.thing.Thing; import org.openhab.core.thing.binding.ThingHandlerCallback; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/HAConfigurationTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/HAConfigurationTests.java index 141c568a9d3d3..01429f59e0ff1 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/HAConfigurationTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/HAConfigurationTests.java @@ -24,10 +24,10 @@ import org.eclipse.jdt.annotation.NonNull; import org.junit.jupiter.api.Test; -import org.openhab.binding.mqtt.homeassistant.internal.config.AbstractChannelConfiguration; import org.openhab.binding.mqtt.homeassistant.internal.config.ChannelConfigurationTypeAdapterFactory; -import org.openhab.binding.mqtt.homeassistant.internal.config.Connection; -import org.openhab.binding.mqtt.homeassistant.internal.config.Device; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.Connection; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.Device; import com.google.gson.Gson; import com.google.gson.GsonBuilder; From c65e0eb6159cdc14d0dd6240a85de79c25d6578c Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Wed, 14 Jul 2021 11:40:46 +0300 Subject: [PATCH 21/23] MQTT.Homeassistant Climate author added Signed-off-by: Anton Kharuzhy --- .../binding/mqtt/homeassistant/internal/component/Climate.java | 1 + 1 file changed, 1 insertion(+) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java index 0145cc0257b78..c030d8a27e081 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java @@ -34,6 +34,7 @@ * A MQTT climate component, following the https://www.home-assistant.io/components/climate.mqtt/ specification. * * @author David Graeff - Initial contribution + * @author Anton Kharuzhy - Implementation */ @NonNullByDefault public class Climate extends AbstractComponent { From adefec051d616f669fc17b363353def7f999c272 Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Thu, 15 Jul 2021 10:48:02 +0300 Subject: [PATCH 22/23] MQTT.Homeassistant Device.sw_version renamed Signed-off-by: Anton Kharuzhy --- .../config/dto/AbstractChannelConfiguration.java | 2 +- .../mqtt/homeassistant/internal/config/dto/Device.java | 9 ++++++--- .../internal/component/HAConfigurationTests.java | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AbstractChannelConfiguration.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AbstractChannelConfiguration.java index c8d2893f37cb4..daa86aa38cd87 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AbstractChannelConfiguration.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AbstractChannelConfiguration.java @@ -110,7 +110,7 @@ public Map appendToProperties(Map properties) { if (model != null) { properties.put(Thing.PROPERTY_MODEL_ID, model); } - final String sw_version = device_.sw_version; + final String sw_version = device_.swVersion; if (sw_version != null) { properties.put(Thing.PROPERTY_FIRMWARE_VERSION, sw_version); } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Device.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Device.java index 779be087ba4c2..84b521508b828 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Device.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/Device.java @@ -18,6 +18,7 @@ import org.openhab.binding.mqtt.homeassistant.internal.config.ListOrStringDeserializer; import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; /** * Device configuration @@ -31,7 +32,9 @@ public class Device { protected @Nullable String manufacturer; protected @Nullable String model; protected @Nullable String name; - protected @Nullable String sw_version; + + @SerializedName("sw_version") + protected @Nullable String swVersion; public @Nullable String getId() { List identifiers = this.identifiers; @@ -59,8 +62,8 @@ public String getName() { } @Nullable - public String getSw_version() { - return sw_version; + public String getSwVersion() { + return swVersion; } @Nullable diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/HAConfigurationTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/HAConfigurationTests.java index 01429f59e0ff1..e454d0d2154d8 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/HAConfigurationTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/HAConfigurationTests.java @@ -85,7 +85,7 @@ public void testAbbreviations() { } assertThat(device.getName(), is("J")); assertThat(device.getModel(), is("K")); - assertThat(device.getSw_version(), is("L")); + assertThat(device.getSwVersion(), is("L")); assertThat(device.getManufacturer(), is("M")); } } @@ -156,7 +156,7 @@ public void testTS0601ClimateConfig() { assertThat(config.getDevice().getManufacturer(), is("TuYa")); assertThat(config.getDevice().getModel(), is("Radiator valve with thermostat (TS0601_thermostat)")); assertThat(config.getDevice().getName(), is("th1")); - assertThat(config.getDevice().getSw_version(), is("Zigbee2MQTT 1.18.2")); + assertThat(config.getDevice().getSwVersion(), is("Zigbee2MQTT 1.18.2")); assertThat(config.action_template, is( "{% set values = {'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'} %}{{ values[value_json.running_state] }}")); From ce46197f8b3ecc73c59f8a7f765807fc5eeea067 Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy Date: Sun, 18 Jul 2021 15:04:02 +0300 Subject: [PATCH 23/23] MQTT.Homeassistant tests wait timeout increased to 10s Signed-off-by: Anton Kharuzhy --- .../internal/component/BinarySensorTests.java | 75 ++++++++++++++++++- .../internal/component/SensorTests.java | 2 +- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java index 82fff31132222..8768853779e63 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java @@ -50,9 +50,7 @@ public void test() throws InterruptedException { " \"sw_version\": \"Zigbee2MQTT 1.18.2\" " + " }, " + " \"name\": \"onoffsensor\", " + - " \"expire_after\": \"2\", " + " \"force_update\": \"true\", " + - " \"off_delay\": \"1\", " + " \"payload_off\": \"OFF_\", " + " \"payload_on\": \"ON_\", " + " \"state_topic\": \"zigbee2mqtt/sensor/state\", " + @@ -76,9 +74,78 @@ public void test() throws InterruptedException { assertState(component, BinarySensor.sensorChannelID, OnOffType.OFF); publishMessage("zigbee2mqtt/sensor/state", "{ \"state\": \"ON_\" }"); assertState(component, BinarySensor.sensorChannelID, OnOffType.ON); + } + + @Test + public void offDelayTest() { + // @formatter:off + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), + "{ " + + " \"availability\": [ " + + " { " + + " \"topic\": \"zigbee2mqtt/bridge/state\" " + + " } " + + " ], " + + " \"device\": { " + + " \"identifiers\": [ " + + " \"zigbee2mqtt_0x0000000000000000\" " + + " ], " + + " \"manufacturer\": \"Sensors inc\", " + + " \"model\": \"On Off Sensor\", " + + " \"name\": \"OnOffSensor\", " + + " \"sw_version\": \"Zigbee2MQTT 1.18.2\" " + + " }, " + + " \"name\": \"onoffsensor\", " + + " \"force_update\": \"true\", " + + " \"off_delay\": \"1\", " + + " \"payload_off\": \"OFF_\", " + + " \"payload_on\": \"ON_\", " + + " \"state_topic\": \"zigbee2mqtt/sensor/state\", " + + " \"unique_id\": \"sn1\", " + + " \"value_template\": \"{{ value_json.state }}\" " + + "}"); + // @formatter:on + + publishMessage("zigbee2mqtt/sensor/state", "{ \"state\": \"ON_\" }"); + assertState(component, BinarySensor.sensorChannelID, OnOffType.ON); + + waitForAssert(() -> assertState(component, BinarySensor.sensorChannelID, OnOffType.OFF), 10000, 200); + } + + @Test + public void expireAfterTest() { + // @formatter:off + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), + "{ " + + " \"availability\": [ " + + " { " + + " \"topic\": \"zigbee2mqtt/bridge/state\" " + + " } " + + " ], " + + " \"device\": { " + + " \"identifiers\": [ " + + " \"zigbee2mqtt_0x0000000000000000\" " + + " ], " + + " \"manufacturer\": \"Sensors inc\", " + + " \"model\": \"On Off Sensor\", " + + " \"name\": \"OnOffSensor\", " + + " \"sw_version\": \"Zigbee2MQTT 1.18.2\" " + + " }, " + + " \"name\": \"onoffsensor\", " + + " \"expire_after\": \"1\", " + + " \"force_update\": \"true\", " + + " \"payload_off\": \"OFF_\", " + + " \"payload_on\": \"ON_\", " + + " \"state_topic\": \"zigbee2mqtt/sensor/state\", " + + " \"unique_id\": \"sn1\", " + + " \"value_template\": \"{{ value_json.state }}\" " + + "}"); + // @formatter:on + + publishMessage("zigbee2mqtt/sensor/state", "{ \"state\": \"OFF_\" }"); + assertState(component, BinarySensor.sensorChannelID, OnOffType.OFF); - waitForAssert(() -> assertState(component, BinarySensor.sensorChannelID, OnOffType.OFF), 2000, 100); - waitForAssert(() -> assertState(component, BinarySensor.sensorChannelID, UnDefType.UNDEF), 3000, 100); + waitForAssert(() -> assertState(component, BinarySensor.sensorChannelID, UnDefType.UNDEF), 10000, 200); } protected Set getConfigTopics() { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java index 53a9393571daf..5f219fde81b10 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java @@ -72,7 +72,7 @@ public void test() throws InterruptedException { assertThat(component.getChannel(Sensor.sensorChannelID).getState().getCache().createStateDescription(true) .build().getPattern(), is("%s W")); - waitForAssert(() -> assertState(component, Sensor.sensorChannelID, UnDefType.UNDEF), 2000, 100); + waitForAssert(() -> assertState(component, Sensor.sensorChannelID, UnDefType.UNDEF), 10000, 200); } protected Set getConfigTopics() {