[mqtt.homeassistant] document which channels a component might have (#17618)

* [mqtt.homeassistant] document which channels a component might have

Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
Cody Cutrer 2024-10-26 12:24:55 -05:00 committed by GitHub
parent fd4284ab97
commit f52cede9ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 262 additions and 205 deletions

View File

@ -11,28 +11,182 @@ Each component will be represented as a Channel Group, with the attributes of th
Any device that publishes the component configuration under the `homeassistant` prefix in MQTT will have their components automatically discovered and added to the Inbox.
You can also manually create a Thing, and provide the individual component topics, as well as a different discovery prefix.
## Supported Components
## Supported Components and Channels
The following components (and their associated channels) are supported.
If a component has multiple channels, they are put together in a channel group with the component's ID.
If a component only has a single channel, that channel is renamed with the component's ID, and placed directly on the Thing, without a group.<br>
Note that most channels are optional, and may not be present.<br>
Note also that just because these tables show that a channel may be read/write, full functionality is dependent on the device.
### [Alarm Control Panel](https://www.home-assistant.io/integrations/alarm_control_panel.mqtt/)
| Channel ID | Type | R/W | Description |
|-----------------|--------|-----|------------------------------------------------------------------------------------------------------------------------------------------|
| state | String | R/W | The current state of the alarm system, and the ability to change its state. Inspect the state and command descriptions for valid values. |
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
### [Binary Sensor](https://www.home-assistant.io/integrations/binary_sensor.mqtt/)
| Channel ID | Type | R/W | Description |
|-----------------|--------|-----|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| sensor | Switch | RO | The current state of the sensor (on/off). See [Home Assistant documentation](https://www.home-assistant.io/integrations/binary_sensor/#device-class) for how to interpret the value for specific device classes. |
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
### [Button](https://www.home-assistant.io/integrations/button.mqtt/)
| Channel ID | Type | R/W | Description |
|-----------------|--------|-----|------------------------------------------------------------------------------|
| button | String | WO | Inspect the state description for the proper string to send (usually PRESS). |
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
### [Camera](https://www.home-assistant.io/integrations/camera.mqtt/)<br>
Base64 encoding is not supported
| Channel ID | Type | R/W | Description |
|-----------------|--------|-----|-----------------------------------------------------|
| camera | Image | RO | The latest image received. |
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
### [Climate](https://www.home-assistant.io/integrations/climate.mqtt/)
| Channel ID | Type | R/W | Description |
|---------------------|--------|-----|-------------------------------------------------------------------------------|
| action | String | RO | The current operating state of the HVAC device. |
| current-temperature | Number | RO | The current temperature |
| fan-mode | String | R/W | The desired fan speed. Inspect the state description for allowed values. |
| mode | String | R/W | The desired operating mode. Inspect the state description for allowed values. |
| swing | String | R/W | The desired swing mode. Inspect the state description for allowed values. |
| temperature | Number | R/W | The desired temperature. |
| temperature-high | Number | R/W | The desired maximum temperature. |
| temperature-low | Number | R/W | The desired minimum temperature. |
| power | Switch | WO | Use to turn the HVAC on or off, regardless of mode. |
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
### [Cover](https://www.home-assistant.io/integrations/cover.mqtt/)
| Channel ID | Type | R/W | Description |
|-----------------|----------------|-----|----------------------------------------------------------------------------------|
| cover | Rollershutter | R/W | Status and control of the cover, possibly including its current position. |
| state | String | RO | The current state of the cover, possibly including opening, closing, or stopped. |
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
### [Device Trigger](https://www.home-assistant.io/integrations/device_trigger.mqtt/)
If a device has multiple device triggers for the same subtype (the particular button), they will only show up as a single channel, and all events for that button will be delivered to that channel.
| Channel ID | Type | R/W | Description |
|----------------------------------|---------|-----|--------------------------------------------------------------------------------------|
| {the subtype from the component} | Trigger | N/A | A trigger channel that receives triggers (typically button presses) from the device. |
### [Event](https://www.home-assistant.io/integrations/event.mqtt/)
| Channel ID | Type | R/W | Description |
|-----------------|---------|------|-----------------------------------------------------------|
| event-type | Trigger | N/A | The event type (e.g. a particular scene being triggered). |
| json-attributes | Trigger | N/A | Additional attributes, as a serialized JSON string. |
### [Fan](https://www.home-assistant.io/integrations/fan.mqtt/)
| Channel ID | Type | R/W | Description |
|-----------------|---------|-----|-----------------------------------------------------------|
| switch | Switch | R/W | Only one of `switch` or `speed` will be present. |
| speed | Dimmer | R/W | Only one of `switch` or `speed` will be present. |
| preset-mode | String | R/W | Inspect the state description for valid values. |
| oscillation | Switch | R/W | If the fan itself is oscillating, in addition to blowing. |
| direction | String | R/W | `forward` or `backward` |
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
### [Light](https://www.home-assistant.io/integrations/light.mqtt/)
| Channel ID | Type | R/W | Description |
|-----------------|---------|-----|-----------------------------------------------------------------|
| switch | Switch | R/W | Only one of `switch`, `brightness`, or `color` will be present. |
| brightness | Dimmer | R/W | Only one of `switch`, `brightness`, or `color` will be present. |
| color | Color | R/W | Only one of `switch`, `brightness`, or `color` will be present. |
| color-mode | String | RO | The current color mode |
| color-temp | Number | R/W | The color temperature (in mired) |
| effect | String | R/W | Inspect the state description to see possible effects. |
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
### [Lock](https://www.home-assistant.io/integrations/lock.mqtt/)
| Channel ID | Type | R/W | Description |
|-----------------|--------|-----|-----------------------------------------------------------------------------------------------------------------------------------------------|
| lock | Switch | R/W | Lock/unlocked state. |
| state | String | R/W | Additional states may be supported such as jammed, or opening the door directly. Inspect the state and command descriptions for availability. |
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
### [Number](https://www.home-assistant.io/integrations/number.mqtt/)
| Channel ID | Type | R/W | Description |
|-----------------|--------|-----|-----------------------------------------------------|
| number | Number | R/W | |
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
### [Scene](https://www.home-assistant.io/integrations/scene.mqtt/)
| Channel ID | Type | R/W | Description |
|-----------------|--------|-----|-----------------------------------------------------------------------------------------------------------|
| scene | String | WO | Triggers a scene on the device. Inspect the state description for the proper string to send (usually ON). |
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
### [Select](https://www.home-assistant.io/integrations/select.mqtt/)
| Channel ID | Type | R/W | Description |
|-----------------|--------|-----|-------------------------------------------------------------------------------------|
| select | String | R/W | The value for the component. Inspect the state description for all possible values. |
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
### [Sensor](https://www.home-assistant.io/integrations/sensor.mqtt/)
| Channel ID | Type | R/W | Description |
|-----------------|-----------------------|-----|-----------------------------------------------------|
| sensor | Number/String/Trigger | RO | The value from the sensor. |
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
### [Switch](https://www.home-assistant.io/integrations/switch.mqtt/)
| Channel ID | Type | R/W | Description |
|-----------------|--------|-----|-----------------------------------------------------|
| switch | Switch | R/W | If the device is on or off. |
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
### [Text](https://www.home-assistant.io/integrations/text.mqtt/)
| Channel ID | Type | R/W | Description |
|-----------------|--------|-----|-----------------------------------------------------|
| text | String | R/W | The text to display on the device. |
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
### [Update](https://www.home-assistant.io/integrations/update.mqtt/)<br>
This is a special component, that will show up as additional properties on the Thing, and add a button on the Thing to initiate an OTA update.
The `json-attributes` channel for this component will always appear as part of channel group, and not be renamed to match the component itself.
| Channel ID | Type | R/W | Description |
|-----------------|--------|-----|-----------------------------------------------------|
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
### [Vacuum](https://www.home-assistant.io/integrations/vacuum.mqtt/)
| Channel ID | Type | R/W | Description |
|-----------------|--------|-----|--------------------------------------------------------------------------------------------------|
| command | String | WO | Send a command to the vacuum. Inspect the state description for allowed values. |
| fan-speed | String | R/W | Set the fan speed. Inspect the state description fro allowed values. |
| custom-command | String | WO | Send an arbitrary command to the vacuum. This may be a raw command, or JSON. |
| battery-level | Dimmer | RO | The vaccum's battery level. |
| state | String | RO | The state of the vacuum. One of `cleaning`, `docked`, `paused`, `idle`, `returning`, or `error`. |
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
### [Valve](https://www.home-assistant.io/integrations/valve.mqtt/)
| Channel ID | Type | R/W | Description |
|-----------------|---------------|-----|---------------------------------------------------------------------------------------------|
| valve | Switch/Dimmer | R/W | If the valve is on (open), or not. For a valve with position (a Dimmer), 100% is full open. |
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
- [Alarm Control Panel](https://www.home-assistant.io/integrations/alarm_control_panel.mqtt/)
- [Binary Sensor](https://www.home-assistant.io/integrations/binary_sensor.mqtt/)
- [Button](https://www.home-assistant.io/integrations/button.mqtt/)
- [Camera](https://www.home-assistant.io/integrations/camera.mqtt/)<br>
Base64 encoding is not supported.
- [Climate](https://www.home-assistant.io/integrations/climate.mqtt/)
- [Cover](https://www.home-assistant.io/integrations/cover.mqtt/)
- [Device Trigger](https://www.home-assistant.io/integrations/device_trigger.mqtt/)
- [Event](https://www.home-assistant.io/integrations/event.mqtt/)
- [Fan](https://www.home-assistant.io/integrations/fan.mqtt/)
- [Light](https://www.home-assistant.io/integrations/light.mqtt/)
- [Lock](https://www.home-assistant.io/integrations/lock.mqtt/)
- [Number](https://www.home-assistant.io/integrations/number.mqtt/)
- [Scene](https://www.home-assistant.io/integrations/scene.mqtt/)
- [Select](https://www.home-assistant.io/integrations/select.mqtt/)
- [Sensor](https://www.home-assistant.io/integrations/sensor.mqtt/)
- [Switch](https://www.home-assistant.io/integrations/switch.mqtt/)
- [Update](https://www.home-assistant.io/integrations/update.mqtt/)<br>
This is a special component, that will show up as additional properties on the Thing, and add a button on the Thing to initiate an OTA update.
- [Vacuum](https://www.home-assistant.io/integrations/vacuum.mqtt/)
## Supported Devices

View File

@ -27,6 +27,7 @@ import org.openhab.binding.mqtt.generic.AvailabilityTracker;
import org.openhab.binding.mqtt.generic.ChannelState;
import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
import org.openhab.binding.mqtt.generic.MqttChannelStateDescriptionProvider;
import org.openhab.binding.mqtt.generic.values.TextValue;
import org.openhab.binding.mqtt.generic.values.Value;
import org.openhab.binding.mqtt.homeassistant.generic.internal.MqttBindingConstants;
import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
@ -42,6 +43,7 @@ import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.binding.generic.ChannelTransformation;
import org.openhab.core.thing.type.AutoUpdatePolicy;
import org.openhab.core.thing.type.ChannelDefinition;
import org.openhab.core.thing.type.ChannelGroupDefinition;
import org.openhab.core.thing.type.ChannelGroupType;
@ -62,6 +64,7 @@ import com.hubspot.jinjava.Jinjava;
*/
@NonNullByDefault
public abstract class AbstractComponent<C extends AbstractChannelConfiguration> {
public static final String JSON_ATTRIBUTES_CHANNEL_ID = "json-attributes";
// Component location fields
protected final ComponentConfiguration componentConfiguration;
@ -152,7 +155,18 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
}
}
protected void addJsonAttributesChannel() {
if (channelConfiguration.getJsonAttributesTopic() != null) {
buildChannel(JSON_ATTRIBUTES_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(), "JSON Attributes",
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.getJsonAttributesTopic(),
channelConfiguration.getJsonAttributesTemplate())
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).isAdvanced(true).build();
}
}
protected void finalizeChannels() {
addJsonAttributesChannel();
if (!newStyleChannels) {
return;
}

View File

@ -16,7 +16,6 @@ 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.OnOffValue;
import org.openhab.binding.mqtt.generic.values.TextValue;
import org.openhab.binding.mqtt.generic.values.Value;
import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
@ -34,7 +33,6 @@ import com.google.gson.annotations.SerializedName;
@NonNullByDefault
public class BinarySensor extends AbstractComponent<BinarySensor.ChannelConfiguration> {
public static final String SENSOR_CHANNEL_ID = "sensor";
public static final String JSON_ATTRIBUTES_CHANNEL_ID = "json-attributes";
/**
* Configuration class for MQTT component
@ -59,11 +57,6 @@ public class BinarySensor extends AbstractComponent<BinarySensor.ChannelConfigur
protected String payloadOn = "ON";
@SerializedName("payload_off")
protected String payloadOff = "OFF";
@SerializedName("json_attributes_topic")
protected @Nullable String jsonAttributesTopic;
@SerializedName("json_attributes_template")
protected @Nullable String jsonAttributesTemplate;
}
public BinarySensor(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
@ -76,13 +69,6 @@ public class BinarySensor extends AbstractComponent<BinarySensor.ChannelConfigur
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
if (channelConfiguration.jsonAttributesTopic != null) {
buildChannel(JSON_ATTRIBUTES_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(), "JSON Attributes",
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.jsonAttributesTopic, channelConfiguration.jsonAttributesTemplate)
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
}
finalizeChannels();
}

View File

@ -13,14 +13,9 @@
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.ImageValue;
import org.openhab.binding.mqtt.generic.values.TextValue;
import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
import org.openhab.core.thing.type.AutoUpdatePolicy;
import com.google.gson.annotations.SerializedName;
/**
* A MQTT camera, following the https://www.home-assistant.io/components/camera.mqtt/ specification.
@ -32,7 +27,6 @@ import com.google.gson.annotations.SerializedName;
@NonNullByDefault
public class Camera extends AbstractComponent<Camera.ChannelConfiguration> {
public static final String CAMERA_CHANNEL_ID = "camera";
public static final String JSON_ATTRIBUTES_CHANNEL_ID = "json-attributes";
/**
* Configuration class for MQTT component
@ -43,11 +37,6 @@ public class Camera extends AbstractComponent<Camera.ChannelConfiguration> {
}
protected String topic = "";
@SerializedName("json_attributes_template")
protected @Nullable String jsonAttributesTemplate;
@SerializedName("json_attributes_topic")
protected @Nullable String jsonAttributesTopic;
}
public Camera(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
@ -58,13 +47,6 @@ public class Camera extends AbstractComponent<Camera.ChannelConfiguration> {
buildChannel(CAMERA_CHANNEL_ID, ComponentChannelType.IMAGE, value, getName(),
componentConfiguration.getUpdateListener()).stateTopic(channelConfiguration.topic).build();
if (channelConfiguration.jsonAttributesTopic != null) {
buildChannel(JSON_ATTRIBUTES_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(), "JSON Attributes",
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.jsonAttributesTopic, channelConfiguration.jsonAttributesTemplate)
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
}
finalizeChannels();
}
}

View File

@ -33,7 +33,6 @@ import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChanne
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.thing.type.AutoUpdatePolicy;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
@ -64,7 +63,6 @@ public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
public static final String TEMPERATURE_LOW_CH_ID = "temperature-low";
public static final String TEMPERATURE_LOW_CH_ID_DEPRECATED = "temperatureLow";
public static final String POWER_CH_ID = "power";
public static final String JSON_ATTRIBUTES_CHANNEL_ID = "json-attributes";
public enum TemperatureUnit {
@SerializedName("C")
@ -150,11 +148,6 @@ public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
protected @Nullable List<String> holdModes; // Are there default modes? Now the channel will be ignored without
// hold modes.
@SerializedName("json_attributes_template")
protected @Nullable String jsonAttributesTemplate;
@SerializedName("json_attributes_topic")
protected @Nullable String jsonAttributesTopic;
@SerializedName("mode_command_template")
protected @Nullable String modeCommandTemplate;
@SerializedName("mode_command_topic")
@ -298,12 +291,6 @@ public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
buildOptionalChannel(POWER_CH_ID, ComponentChannelType.SWITCH, new OnOffValue(), updateListener, null,
channelConfiguration.powerCommandTopic, null, null, null);
if (channelConfiguration.jsonAttributesTopic != null) {
buildChannel(JSON_ATTRIBUTES_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(), "JSON Attributes",
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.jsonAttributesTopic, channelConfiguration.jsonAttributesTemplate)
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
}
finalizeChannels();
}

View File

@ -40,7 +40,6 @@ import com.google.gson.annotations.SerializedName;
public class Cover extends AbstractComponent<Cover.ChannelConfiguration> {
public static final String COVER_CHANNEL_ID = "cover";
public static final String STATE_CHANNEL_ID = "state";
public static final String JSON_ATTRIBUTES_CHANNEL_ID = "json-attributes";
/**
* Configuration class for MQTT component
@ -84,11 +83,6 @@ public class Cover extends AbstractComponent<Cover.ChannelConfiguration> {
protected String stateOpening = "opening";
@SerializedName("state_stopped")
protected String stateStopped = "stopped";
@SerializedName("json_attributes_template")
protected @Nullable String jsonAttributesTemplate;
@SerializedName("json_attributes_topic")
protected @Nullable String jsonAttributesTopic;
}
@Nullable
@ -166,12 +160,6 @@ public class Cover extends AbstractComponent<Cover.ChannelConfiguration> {
return true;
}).withAutoUpdatePolicy(optimistic ? AutoUpdatePolicy.RECOMMEND : null).build();
if (channelConfiguration.jsonAttributesTopic != null) {
buildChannel(JSON_ATTRIBUTES_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(), "JSON Attributes",
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.jsonAttributesTopic, channelConfiguration.jsonAttributesTemplate)
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
}
finalizeChannels();
}
}

View File

@ -16,7 +16,6 @@ import java.util.ArrayList;
import java.util.List;
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.TextValue;
import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
@ -36,7 +35,6 @@ import com.google.gson.annotations.SerializedName;
@NonNullByDefault
public class Event extends AbstractComponent<Event.ChannelConfiguration> implements ChannelStateUpdateListener {
public static final String EVENT_TYPE_CHANNEL_ID = "event-type";
public static final String JSON_ATTRIBUTES_CHANNEL_ID = "json-attributes";
private static final String EVENT_TYPE_TRANFORMATION = "{{ value_json.event_type }}";
/**
@ -52,12 +50,6 @@ public class Event extends AbstractComponent<Event.ChannelConfiguration> impleme
@SerializedName("event_types")
protected List<String> eventTypes = new ArrayList();
@SerializedName("json_attributes_topic")
protected @Nullable String jsonAttributesTopic;
@SerializedName("json_attributes_template")
protected @Nullable String jsonAttributesTemplate;
}
private final HomeAssistantChannelTransformation transformation;
@ -71,7 +63,13 @@ public class Event extends AbstractComponent<Event.ChannelConfiguration> impleme
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate()).trigger(true)
.build();
if (channelConfiguration.jsonAttributesTopic != null) {
finalizeChannels();
}
// Overridden to use create it as a trigger channel
@Override
protected void addJsonAttributesChannel() {
if (channelConfiguration.getJsonAttributesTopic() != null) {
// It's unclear from the documentation if the JSON attributes value is expected
// to be the same as the main topic, and thus would always have an event_type
// attribute (and thus could possibly be shared with multiple components).
@ -81,11 +79,10 @@ public class Event extends AbstractComponent<Event.ChannelConfiguration> impleme
// the filtering below.
buildChannel(JSON_ATTRIBUTES_CHANNEL_ID, ComponentChannelType.TRIGGER, new TextValue(), getName(),
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.jsonAttributesTopic, channelConfiguration.jsonAttributesTemplate)
.trigger(true).build();
.stateTopic(channelConfiguration.getJsonAttributesTopic(),
channelConfiguration.getJsonAttributesTemplate())
.isAdvanced(true).trigger(true).build();
}
finalizeChannels();
}
@Override

View File

@ -28,7 +28,6 @@ import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChanne
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.type.AutoUpdatePolicy;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
@ -44,12 +43,12 @@ import com.google.gson.annotations.SerializedName;
*/
@NonNullByDefault
public class Fan extends AbstractComponent<Fan.ChannelConfiguration> implements ChannelStateUpdateListener {
public static final String SWITCH_CHANNEL_ID = "fan";
public static final String SWITCH_CHANNEL_ID = "switch";
public static final String SWITCH_CHANNEL_ID_DEPRECATED = "fan";
public static final String SPEED_CHANNEL_ID = "speed";
public static final String PRESET_MODE_CHANNEL_ID = "preset-mode";
public static final String OSCILLATION_CHANNEL_ID = "oscillation";
public static final String DIRECTION_CHANNEL_ID = "direction";
public static final String JSON_ATTRIBUTES_CHANNEL_ID = "json-attributes";
/**
* Configuration class for MQTT component
@ -117,10 +116,6 @@ public class Fan extends AbstractComponent<Fan.ChannelConfiguration> implements
protected int speedRangeMax = 100;
@SerializedName("speed_range_min")
protected int speedRangeMin = 1;
@SerializedName("json_attributes_template")
protected @Nullable String jsonAttributesTemplate;
@SerializedName("json_attributes_topic")
protected @Nullable String jsonAttributesTopic;
}
private final OnOffValue onOffValue;
@ -139,8 +134,8 @@ public class Fan extends AbstractComponent<Fan.ChannelConfiguration> implements
ChannelStateUpdateListener onOffListener = channelConfiguration.percentageCommandTopic == null
? componentConfiguration.getUpdateListener()
: this;
onOffChannel = buildChannel(SWITCH_CHANNEL_ID, ComponentChannelType.SWITCH, onOffValue, "On/Off State",
onOffListener)
onOffChannel = buildChannel(newStyleChannels ? SWITCH_CHANNEL_ID : SWITCH_CHANNEL_ID_DEPRECATED,
ComponentChannelType.SWITCH, onOffValue, "On/Off State", onOffListener)
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
channelConfiguration.getQos(), channelConfiguration.commandTemplate)
@ -202,13 +197,6 @@ public class Fan extends AbstractComponent<Fan.ChannelConfiguration> implements
.inferOptimistic(channelConfiguration.optimistic).build();
}
if (channelConfiguration.jsonAttributesTopic != null) {
buildChannel(JSON_ATTRIBUTES_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(), "JSON Attributes",
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.jsonAttributesTopic, channelConfiguration.jsonAttributesTemplate)
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
}
finalizeChannels();
}

View File

@ -36,7 +36,6 @@ import com.google.gson.annotations.SerializedName;
public class Lock extends AbstractComponent<Lock.ChannelConfiguration> {
public static final String LOCK_CHANNEL_ID = "lock";
public static final String STATE_CHANNEL_ID = "state";
public static final String JSON_ATTRIBUTES_CHANNEL_ID = "json-attributes";
/**
* Configuration class for MQTT component
@ -68,11 +67,6 @@ public class Lock extends AbstractComponent<Lock.ChannelConfiguration> {
protected String stateUnlocked = "UNLOCKED";
@SerializedName("state_unlocking")
protected String stateUnlocking = "UNLOCKING";
@SerializedName("json_attributes_template")
protected @Nullable String jsonAttributesTemplate;
@SerializedName("json_attributes_topic")
protected @Nullable String jsonAttributesTopic;
}
private boolean optimistic = false;
@ -128,13 +122,6 @@ public class Lock extends AbstractComponent<Lock.ChannelConfiguration> {
return true;
}).build();
if (channelConfiguration.jsonAttributesTopic != null) {
buildChannel(JSON_ATTRIBUTES_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(), "JSON Attributes",
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.jsonAttributesTopic, channelConfiguration.jsonAttributesTemplate)
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
}
finalizeChannels();
}

View File

@ -17,10 +17,8 @@ import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mqtt.generic.values.NumberValue;
import org.openhab.binding.mqtt.generic.values.TextValue;
import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
import org.openhab.core.thing.type.AutoUpdatePolicy;
import org.openhab.core.types.util.UnitUtils;
import com.google.gson.annotations.SerializedName;
@ -33,7 +31,6 @@ import com.google.gson.annotations.SerializedName;
@NonNullByDefault
public class Number extends AbstractComponent<Number.ChannelConfiguration> {
public static final String NUMBER_CHANNEL_ID = "number"; // Randomly chosen channel "ID"
public static final String JSON_ATTRIBUTES_CHANNEL_ID = "json-attributes";
/**
* Configuration class for MQTT component
@ -65,11 +62,6 @@ public class Number extends AbstractComponent<Number.ChannelConfiguration> {
protected String payloadReset = "None";
protected String mode = "auto";
@SerializedName("json_attributes_topic")
protected @Nullable String jsonAttributesTopic;
@SerializedName("json_attributes_template")
protected @Nullable String jsonAttributesTemplate;
}
public Number(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
@ -85,13 +77,6 @@ public class Number extends AbstractComponent<Number.ChannelConfiguration> {
channelConfiguration.getQos(), channelConfiguration.commandTemplate)
.inferOptimistic(channelConfiguration.optimistic).build();
if (channelConfiguration.jsonAttributesTopic != null) {
buildChannel(JSON_ATTRIBUTES_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(), "JSON Attributes",
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.jsonAttributesTopic, channelConfiguration.jsonAttributesTemplate)
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
}
finalizeChannels();
}
}

View File

@ -29,7 +29,6 @@ import com.google.gson.annotations.SerializedName;
@NonNullByDefault
public class Scene extends AbstractComponent<Scene.ChannelConfiguration> {
public static final String SCENE_CHANNEL_ID = "scene";
public static final String JSON_ATTRIBUTES_CHANNEL_ID = "json-attributes";
/**
* Configuration class for MQTT component
@ -44,11 +43,6 @@ public class Scene extends AbstractComponent<Scene.ChannelConfiguration> {
@SerializedName("payload_on")
protected String payloadOn = "ON";
@SerializedName("json_attributes_topic")
protected @Nullable String jsonAttributesTopic;
@SerializedName("json_attributes_template")
protected @Nullable String jsonAttributesTemplate;
}
public Scene(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
@ -62,13 +56,6 @@ public class Scene extends AbstractComponent<Scene.ChannelConfiguration> {
channelConfiguration.getQos())
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
if (channelConfiguration.jsonAttributesTopic != null) {
buildChannel(JSON_ATTRIBUTES_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(), "JSON Attributes",
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.jsonAttributesTopic, channelConfiguration.jsonAttributesTemplate)
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
}
finalizeChannels();
}
}

View File

@ -27,7 +27,7 @@ import com.google.gson.annotations.SerializedName;
*/
@NonNullByDefault
public class Select extends AbstractComponent<Select.ChannelConfiguration> {
public static final String SELECT_CHANNEL_ID = "select"; // Randomly chosen channel "ID"
public static final String SELECT_CHANNEL_ID = "select";
/**
* Configuration class for MQTT component
@ -47,11 +47,6 @@ public class Select extends AbstractComponent<Select.ChannelConfiguration> {
protected String stateTopic = "";
protected String[] options = new String[0];
@SerializedName("json_attributes_topic")
protected @Nullable String jsonAttributesTopic;
@SerializedName("json_attributes_template")
protected @Nullable String jsonAttributesTemplate;
}
public Select(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
@ -65,6 +60,7 @@ public class Select extends AbstractComponent<Select.ChannelConfiguration> {
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
channelConfiguration.getQos(), channelConfiguration.commandTemplate)
.inferOptimistic(channelConfiguration.optimistic).build();
finalizeChannels();
}
}

View File

@ -23,7 +23,6 @@ import org.openhab.binding.mqtt.generic.values.Value;
import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
import org.openhab.binding.mqtt.homeassistant.internal.listener.ExpireUpdateStateListener;
import org.openhab.core.thing.type.AutoUpdatePolicy;
import org.openhab.core.types.util.UnitUtils;
import com.google.gson.annotations.SerializedName;
@ -35,8 +34,7 @@ import com.google.gson.annotations.SerializedName;
*/
@NonNullByDefault
public class Sensor extends AbstractComponent<Sensor.ChannelConfiguration> {
public static final String SENSOR_CHANNEL_ID = "sensor"; // Randomly chosen channel "ID"
public static final String JSON_ATTRIBUTES_CHANNEL_ID = "json-attributes";
public static final String SENSOR_CHANNEL_ID = "sensor";
private static final Pattern TRIGGER_ICONS = Pattern.compile("^mdi:(toggle|gesture).*$");
@ -61,11 +59,6 @@ public class Sensor extends AbstractComponent<Sensor.ChannelConfiguration> {
@SerializedName("state_topic")
protected String stateTopic = "";
@SerializedName("json_attributes_topic")
protected @Nullable String jsonAttributesTopic;
@SerializedName("json_attributes_template")
protected @Nullable String jsonAttributesTemplate;
}
public Sensor(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
@ -97,12 +90,6 @@ public class Sensor extends AbstractComponent<Sensor.ChannelConfiguration> {
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())//
.trigger(trigger).build();
if (channelConfiguration.jsonAttributesTopic != null) {
buildChannel(JSON_ATTRIBUTES_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(), "JSON Attributes",
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.jsonAttributesTopic, channelConfiguration.jsonAttributesTemplate)
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
}
finalizeChannels();
}

View File

@ -27,7 +27,7 @@ import com.google.gson.annotations.SerializedName;
*/
@NonNullByDefault
public class Switch extends AbstractComponent<Switch.ChannelConfiguration> {
public static final String SWITCH_CHANNEL_ID = "switch"; // Randomly chosen channel "ID"
public static final String SWITCH_CHANNEL_ID = "switch";
/**
* Configuration class for MQTT component
@ -52,11 +52,6 @@ public class Switch extends AbstractComponent<Switch.ChannelConfiguration> {
protected String payloadOn = "ON";
@SerializedName("payload_off")
protected String payloadOff = "OFF";
@SerializedName("json_attributes_topic")
protected @Nullable String jsonAttributesTopic;
@SerializedName("json_attributes_template")
protected @Nullable String jsonAttributesTemplate;
}
public Switch(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
@ -71,6 +66,7 @@ public class Switch extends AbstractComponent<Switch.ChannelConfiguration> {
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
channelConfiguration.getQos())
.inferOptimistic(channelConfiguration.optimistic).build();
finalizeChannels();
}
}

View File

@ -172,6 +172,8 @@ public class Update extends AbstractComponent<Update.ChannelConfiguration> imple
state.title = channelConfiguration.title;
state.releaseSummary = channelConfiguration.releaseSummary;
state.releaseUrl = channelConfiguration.releaseUrl;
addJsonAttributesChannel();
}
/**

View File

@ -115,11 +115,6 @@ public class Vacuum extends AbstractComponent<Vacuum.ChannelConfiguration> {
@SerializedName("state_topic")
protected @Nullable String stateTopic;
@SerializedName("json_attributes_template")
protected @Nullable String jsonAttributesTemplate;
@SerializedName("json_attributes_topic")
protected @Nullable String jsonAttributesTopic;
}
/**
@ -183,12 +178,17 @@ public class Vacuum extends AbstractComponent<Vacuum.ChannelConfiguration> {
}
}
buildOptionalChannel(newStyleChannels ? JSON_ATTRIBUTES_CH_ID : JSON_ATTRIBUTES_CH_ID_DEPRECATED,
ComponentChannelType.STRING, new TextValue(), updateListener, null, null,
channelConfiguration.jsonAttributesTemplate, channelConfiguration.jsonAttributesTopic);
finalizeChannels();
}
// Overridden to use deprecated channel ID
@Override
protected void addJsonAttributesChannel() {
buildOptionalChannel(newStyleChannels ? JSON_ATTRIBUTES_CH_ID : JSON_ATTRIBUTES_CH_ID_DEPRECATED,
ComponentChannelType.STRING, new TextValue(), componentConfiguration.getUpdateListener(), null, null,
channelConfiguration.getJsonAttributesTemplate(), channelConfiguration.getJsonAttributesTopic());
}
@Nullable
private ComponentChannel buildOptionalChannel(String channelId, ComponentChannelType channelType, Value valueState,
ChannelStateUpdateListener channelStateUpdateListener, @Nullable String commandTemplate,

View File

@ -65,6 +65,11 @@ public abstract class AbstractChannelConfiguration {
*/
protected @Nullable List<Availability> availability;
@SerializedName("json_attributes_template")
protected @Nullable String jsonAttributesTemplate;
@SerializedName("json_attributes_topic")
protected @Nullable String jsonAttributesTopic;
@SerializedName(value = "~")
protected String parentTopic = "";
@ -197,6 +202,16 @@ public abstract class AbstractChannelConfiguration {
return availabilityMode;
}
@Nullable
public String getJsonAttributesTemplate() {
return jsonAttributesTemplate;
}
@Nullable
public String getJsonAttributesTopic() {
return jsonAttributesTopic;
}
/**
* 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.

View File

@ -72,21 +72,21 @@ public class FanTests extends AbstractComponentTests {
assertThat(component.channels.size(), is(1));
assertThat(component.getName(), is("fan"));
assertChannel(component, Fan.SWITCH_CHANNEL_ID, "zigbee2mqtt/fan/state", "zigbee2mqtt/fan/set/state",
assertChannel(component, Fan.SWITCH_CHANNEL_ID_DEPRECATED, "zigbee2mqtt/fan/state", "zigbee2mqtt/fan/set/state",
"On/Off State", OnOffValue.class, null);
publishMessage("zigbee2mqtt/fan/state", "ON_");
assertState(component, Fan.SWITCH_CHANNEL_ID, OnOffType.ON);
assertState(component, Fan.SWITCH_CHANNEL_ID_DEPRECATED, OnOffType.ON);
publishMessage("zigbee2mqtt/fan/state", "ON_");
assertState(component, Fan.SWITCH_CHANNEL_ID, OnOffType.ON);
assertState(component, Fan.SWITCH_CHANNEL_ID_DEPRECATED, OnOffType.ON);
publishMessage("zigbee2mqtt/fan/state", "OFF_");
assertState(component, Fan.SWITCH_CHANNEL_ID, OnOffType.OFF);
assertState(component, Fan.SWITCH_CHANNEL_ID_DEPRECATED, OnOffType.OFF);
publishMessage("zigbee2mqtt/fan/state", "ON_");
assertState(component, Fan.SWITCH_CHANNEL_ID, OnOffType.ON);
assertState(component, Fan.SWITCH_CHANNEL_ID_DEPRECATED, OnOffType.ON);
component.getChannel(Fan.SWITCH_CHANNEL_ID).getState().publishValue(OnOffType.OFF);
component.getChannel(Fan.SWITCH_CHANNEL_ID_DEPRECATED).getState().publishValue(OnOffType.OFF);
assertPublished("zigbee2mqtt/fan/set/state", "OFF_");
component.getChannel(Fan.SWITCH_CHANNEL_ID).getState().publishValue(OnOffType.ON);
component.getChannel(Fan.SWITCH_CHANNEL_ID_DEPRECATED).getState().publishValue(OnOffType.ON);
assertPublished("zigbee2mqtt/fan/set/state", "ON_");
}
@ -122,7 +122,7 @@ public class FanTests extends AbstractComponentTests {
assertThat(component.channels.size(), is(1));
assertThat(component.getName(), is("fan"));
assertChannel(component, Fan.SWITCH_CHANNEL_ID, "", "zigbee2mqtt/fan/set/state", "On/Off State",
assertChannel(component, Fan.SWITCH_CHANNEL_ID_DEPRECATED, "", "zigbee2mqtt/fan/set/state", "On/Off State",
OnOffValue.class, AutoUpdatePolicy.RECOMMEND);
}
@ -160,7 +160,7 @@ public class FanTests extends AbstractComponentTests {
assertThat(component.channels.size(), is(1));
assertThat(component.getName(), is("fan"));
assertChannel(component, Fan.SWITCH_CHANNEL_ID, "zigbee2mqtt/fan/state", "zigbee2mqtt/fan/set/state",
assertChannel(component, Fan.SWITCH_CHANNEL_ID_DEPRECATED, "zigbee2mqtt/fan/state", "zigbee2mqtt/fan/set/state",
"On/Off State", OnOffValue.class, AutoUpdatePolicy.RECOMMEND);
}
@ -231,7 +231,7 @@ public class FanTests extends AbstractComponentTests {
assertThat(component.channels.size(), is(1));
component.getChannel(Fan.SWITCH_CHANNEL_ID).getState().publishValue(OnOffType.OFF);
component.getChannel(Fan.SWITCH_CHANNEL_ID_DEPRECATED).getState().publishValue(OnOffType.OFF);
assertPublished("zigbee2mqtt/fan/set/state", "set to OFF_");
}

View File

@ -174,7 +174,7 @@ public class HAConfigurationTests {
assertThat(config.holdModes, is(List.of("schedule", "manual", "boost", "complex", "comfort", "eco")));
assertThat(config.holdStateTemplate, is("{{ value_json.preset }}"));
assertThat(config.holdStateTopic, is("zigbee2mqtt/th1"));
assertThat(config.jsonAttributesTopic, is("zigbee2mqtt/th1"));
assertThat(config.getJsonAttributesTopic(), is("zigbee2mqtt/th1"));
assertThat(config.maxTemp, is(new BigDecimal(35)));
assertThat(config.minTemp, is(new BigDecimal(5)));
assertThat(config.modeCommandTopic, is("zigbee2mqtt/th1/set/system_mode"));
@ -218,8 +218,8 @@ public class HAConfigurationTests {
assertThat(config.holdStateTemplate, is("s"));
assertThat(config.holdStateTopic, is("t"));
assertThat(config.holdModes, is(List.of("u1", "u2", "u3")));
assertThat(config.jsonAttributesTemplate, is("v"));
assertThat(config.jsonAttributesTopic, is("w"));
assertThat(config.getJsonAttributesTemplate(), is("v"));
assertThat(config.getJsonAttributesTopic(), is("w"));
assertThat(config.modeCommandTemplate, is("x"));
assertThat(config.modeCommandTopic, is("y"));
assertThat(config.modeStateTemplate, is("z"));

View File

@ -20,6 +20,7 @@ import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.mqtt.generic.values.OnOffValue;
import org.openhab.binding.mqtt.generic.values.TextValue;
import org.openhab.core.library.types.OnOffType;
/**
@ -63,11 +64,13 @@ public class SwitchTests extends AbstractComponentTests {
}\
""");
assertThat(component.channels.size(), is(1));
assertThat(component.channels.size(), is(2));
assertThat(component.getName(), is("th1 auto lock"));
assertChannel(component, Switch.SWITCH_CHANNEL_ID, "zigbee2mqtt/th1", "zigbee2mqtt/th1/set/auto_lock",
"th1 auto lock", OnOffValue.class);
assertChannel(component, Switch.JSON_ATTRIBUTES_CHANNEL_ID, "zigbee2mqtt/th1", "", "JSON Attributes",
TextValue.class);
publishMessage("zigbee2mqtt/th1", "{\"auto_lock\": \"MANUAL\"}");
assertState(component, Switch.SWITCH_CHANNEL_ID, OnOffType.OFF);
@ -108,8 +111,10 @@ public class SwitchTests extends AbstractComponentTests {
}\
""");
assertThat(component.channels.size(), is(1));
assertThat(component.channels.size(), is(2));
assertThat(component.getName(), is("th1 auto lock"));
assertChannel(component, Switch.JSON_ATTRIBUTES_CHANNEL_ID, "zigbee2mqtt/th1", "", "JSON Attributes",
TextValue.class);
assertChannel(component, Switch.SWITCH_CHANNEL_ID, "zigbee2mqtt/th1", "", "th1 auto lock", OnOffValue.class);
@ -148,11 +153,12 @@ public class SwitchTests extends AbstractComponentTests {
}\
""");
assertThat(component.channels.size(), is(1));
assertThat(component.channels.size(), is(2));
assertThat(component.getName(), is("th1 auto lock"));
assertChannel(component, Switch.SWITCH_CHANNEL_ID, "", "zigbee2mqtt/th1/set/auto_lock", "th1 auto lock",
OnOffValue.class);
assertChannel(component, Switch.JSON_ATTRIBUTES_CHANNEL_ID, "zigbee2mqtt/th1", "", "JSON Attributes",
TextValue.class);
component.getChannel(Switch.SWITCH_CHANNEL_ID).getState().publishValue(OnOffType.OFF);
assertPublished("zigbee2mqtt/th1/set/auto_lock", "MANUAL");

View File

@ -130,8 +130,8 @@ public class HomeAssistantThingHandlerTests extends AbstractHomeAssistantTests {
verify(thingHandler, times(1)).componentDiscovered(eq(new HaID(configTopic)), any(Switch.class));
thingHandler.delayedProcessing.forceProcessNow();
assertThat(nonSpyThingHandler.getThing().getChannels().size(), is(8));
verify(stateDescriptionProvider, atLeast(8)).setDescription(any(), any(StateDescription.class));
assertThat(nonSpyThingHandler.getThing().getChannels().size(), is(9));
verify(stateDescriptionProvider, atLeast(9)).setDescription(any(), any(StateDescription.class));
verify(channelTypeProvider, times(3)).putChannelGroupType(any());
}
@ -253,7 +253,7 @@ public class HomeAssistantThingHandlerTests extends AbstractHomeAssistantTests {
"homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config",
getResourceAsByteArray("component/configTS0601AutoLock.json"));
thingHandler.delayedProcessing.forceProcessNow();
assertThat(nonSpyThingHandler.getThing().getChannels().size(), is(8));
assertThat(nonSpyThingHandler.getThing().getChannels().size(), is(9));
verify(stateDescriptionProvider, atLeast(7)).setDescription(any(), any(StateDescription.class));
// When dispose
@ -281,13 +281,13 @@ public class HomeAssistantThingHandlerTests extends AbstractHomeAssistantTests {
"homeassistant/switch/0x847127fffe11dd6a_auto_lock_zigbee2mqtt/config",
getResourceAsByteArray("component/configTS0601AutoLock.json"));
thingHandler.delayedProcessing.forceProcessNow();
assertThat(nonSpyThingHandler.getThing().getChannels().size(), is(8));
assertThat(nonSpyThingHandler.getThing().getChannels().size(), is(9));
// When dispose
nonSpyThingHandler.handleRemoval();
// Expect channel descriptions removed, 7 for climate and 1 for switch
verify(stateDescriptionProvider, times(8)).remove(any());
// Expect channel descriptions removed, 7 for climate and 2 for switch
verify(stateDescriptionProvider, times(9)).remove(any());
// Expect channel group types removed, 1 for each component
verify(channelTypeProvider, times(2)).removeChannelGroupType(any());
}