Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[mqtt] Simplify homeassistant thing types, and use AbstractStorageBasedTypeProvider #16600

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

ccutrer
Copy link
Contributor

@ccutrer ccutrer commented Apr 1, 2024

This is a rather large PR, but is built of a few commits with essentially two distinct goals. I have not tested them individually, and there are lots of interactions between the two goals, which is why I did not disentangle them.

This first goal is to simplify and optimize thing and channel types for mqtt.homeassistant. This fixes #14295. This in itself has two user visible changes:

  • Thing types have not actually changed, but thing UIDs have. Auto Discovery will no longer use the per-thing thing type in the thing ID, but will use the "base" thing type (available in the binding's XML) of mqtt:homeassistant in its ID. In practice, newly discovered things will get UIDs such as mqtt:homeassistant:mosquitto:awair-element-63611 instead of mqtt:homeassistant_awair_2Delement_2D63611:mosquitto:awair_2Delement_2D63611 (unnecessary escaping has also been removed). Existing things will not change their UID. I'm not sure if this is considered a breaking change, since existing things will not change at all. But if you were to delete the thing, and re-add it from discovery, it would end up with a different ID. This new behavior matches how mqtt.homie works.
  • Channel IDs have been simplified. First, if a component has no name (in the Home Assistant configuration), it will not generate a channel group at all. Second, the channel group name is now only the component's name, instead of including the entire thing ID in the channel group, it is now only the component name (which already has to be unique within a Home Assistant device, so not a problem there). The channel group type (which is unseen by the user) remains the fully unique id, which includes the thing id. Third, if a component only has a single channel, it also doesn't create a channel group, just a single channel for that component without a group.
    For this change, I did a bit of hoop-jumping to pass a property through from auto-discovery, so that existing things will not have their channels change, only newly discovered and created things will use this new channel type. Again, I'm not sure if this counts as a breaking change. If it does, I'm possibly inclined to not use this hinky property, and simply apply the new channel IDs to all things. An example change is from mqtt:homeassistant_zigbee2mqtt_5F0x8cf681fffe32e58e:mosquitto:zigbee2mqtt_5F0x8cf681fffe32e58e:0x8cf681fffe32e58e_5Fbattery_5Fzigbee2mqtt#sensor to mqtt:homeassistant:mosquitto:zigbee2mqtt_5F0x8cf681fffe32e58e:sensor_battery

The second goal is relatively simple - use AbstractStorageBasedTypeProvider. Because the thing and channel group types are dynamic, they don't exist in the binding's XML. But since openHAB 4.0, if a thing type isn't available at startup, it will leave the thing in an unknown state for two minutes before it initializes it. By using AbstractStorageBasedTypeProvider, the dynamic types will be persisted to JSON storage, and thus available immediately at startup, avoiding the two minute delay. This fixes #15577

Instead, use state descriptions for the varying parts, which means that
there is not an explosion of channel types for every single Home Assistant
channel, which can cause performance issues if you have a lot of
devices/channels

Signed-off-by: Cody Cutrer <cody@cutrer.us>
This has two important impacts, and one not-as-important-but-still-
beneficial impact:
 * ThingTypes are not created for Things that are never actually
   created, but only discovered, reducing bloat.
 * Avoids warnings about things types not being in the registry
 * Thing IDs end up like
   mqtt:homeassistant:broker:mydevice instead of
   mqtt:homeassistant_mydevice:broker:mydevice

Note that already-created things will maintain their original ID,
and it's only new things created from auto-discovery that will have
the new, shorter thing IDs.

Signed-off-by: Cody Cutrer <cody@cutrer.us>
This is triggered by a property only set by discovery, so will only apply
to newly created things.

Channel groups are now named after simply the component type and object_id,
if possible, and without additional encoding, instead off encoding the
unique_id. Most devices will encode the component type and device identifiers
into the unique_id, and of arbitrary format, so it is far more complicated
than necessary, when a channel group is already only within the scope of
the thing/device.

Many component types that can only ever have one channel (button, switch,
sensor, select, scene, etc.) will not be in a channel group anymore. In this
case their channel id is what their group id would have been, without the
repetitiveness of "sensor_<unique_id>#sensor".

Signed-off-by: Cody Cutrer <cody@cutrer.us>
Includes both homie and homeassistant dynamic things.
It also fixes all ordering issues in both bindings in order to
be consistent in how types are persisted:
 * For Homie, Nodes (channel groups) and Properties (channels)
   are ordered in the way they are defined in $nodes and $properties
 * For Home Assistant, Components are ordered by label. This
   includes both single channel components that are not in a channel
   group, as well as channel groups. We also ensure that on the
   Thing itself non-grouped channels consistently sort before grouped
   channels.

Signed-off-by: Cody Cutrer <cody@cutrer.us>
@ccutrer ccutrer requested a review from antroids as a code owner April 1, 2024 16:05
Signed-off-by: Cody Cutrer <cody@cutrer.us>
Signed-off-by: Cody Cutrer <cody@cutrer.us>
@lolodomo lolodomo added the enhancement An enhancement or new feature for an existing add-on label Apr 3, 2024
*/
public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class<C> clazz) {
public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class<C> clazz,
boolean newStyleChannels, boolean singleChannelComponent) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this possible to move newStyleChannels into haID?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before I spend time making changes here, I'd like feedback on how we want to address backwards compatibility:

  • as is - newly discovered things get the shorter channels, already-existing things don't. file-based things don't (which... I don't even know if file-based Home Assistant things really work, so might not matter)
  • an explicit configuration on the thing to opt in (means can be used for file-based things, or if you have to re-create your things for some reason) - but means it's an additional step, so most people won't ever know about it. and not really useful for re-creating things, since the thing uid will always be the shorter version, even if you choose the longer channel ids.
  • an explicit configuration on the thing to opt out (means can be used for file-based things, or if you have to re-create your things for some reason) - but means it will break all existing things causing issues for existing users anyway. and not really useful for re-creating things, since the thing uid will always be the shorter version, even if you choose the longer channel ids.
  • just pull off the band aid and call a breaking change - all existing things get the newer shorter ids, and no way to go back. definitely simplifies the code to not need to plumb this through, or to have to have two branches

Copy link
Contributor

@lsiepel lsiepel May 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the as-is option, i think this is elegant. One question, how long would you support the deprecated convention? One or two releases? When this comes with a clear eol date and an upgrade notice (distro repo) it is probably my favorite.

Imho opt-in/opt-out is only making it more complex and not solving any issue in the ugprade path.

The last option is clear, convenient for the developers, but disruptive to the end user.

So all in all, my vote would go to the first option.

if (newStyleChannels && this.singleChannelComponent) {
groupId = haID.component;
} else {
groupId = "";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not it be null as in the org.openhab.core.thing.ChannelUID.getGroupId?

Copy link
Contributor Author

@ccutrer ccutrer Apr 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't be null, since it's not just used as part of a channel id, but also for other uses in the thing handler that don't allow null. Perhaps when I have more time I'll check if I can change those all to allow nullable.

Comment on lines +171 to 178
protected ComponentChannel.Builder buildChannel(String channelID, ChannelTypeUID channelTypeUID, Value valueState,
String label, ChannelStateUpdateListener channelStateUpdateListener) {
if (singleChannelComponent) {
channelID = groupId;
}
return new ComponentChannel.Builder(this, channelID, channelTypeUID, valueState, label,
channelStateUpdateListener);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, there are no restrictions on which UIDs can be used as channelTypeUID, but the types are determined. Could you create a build method for each channel type. The next step would be abstract builders with factory methods specific for channel types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please elaborate here? I'm not sure what you mean about types being determined.

@lolodomo
Copy link
Contributor

lolodomo commented Apr 6, 2024

@ccutrer : did you notice the build failed ?

@ccutrer
Copy link
Contributor Author

ccutrer commented Apr 6, 2024

I did. And I've gotten to the point that I can reproduce the failures locally, but have not yet had time to troubleshoot them (or address @antroids comments).

Signed-off-by: Cody Cutrer <cody@cutrer.us>
Copy link
Contributor Author

@ccutrer ccutrer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've addressed (most) the review comments, but not looked at itests. The suggested changes have also caused (non-test) test failures - I think related to some of the foreach changes, possibly due to needing to mock different methods. I don't have time today to look into any of that, but will when I can

*/
public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class<C> clazz) {
public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class<C> clazz,
boolean newStyleChannels, boolean singleChannelComponent) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before I spend time making changes here, I'd like feedback on how we want to address backwards compatibility:

  • as is - newly discovered things get the shorter channels, already-existing things don't. file-based things don't (which... I don't even know if file-based Home Assistant things really work, so might not matter)
  • an explicit configuration on the thing to opt in (means can be used for file-based things, or if you have to re-create your things for some reason) - but means it's an additional step, so most people won't ever know about it. and not really useful for re-creating things, since the thing uid will always be the shorter version, even if you choose the longer channel ids.
  • an explicit configuration on the thing to opt out (means can be used for file-based things, or if you have to re-create your things for some reason) - but means it will break all existing things causing issues for existing users anyway. and not really useful for re-creating things, since the thing uid will always be the shorter version, even if you choose the longer channel ids.
  • just pull off the band aid and call a breaking change - all existing things get the newer shorter ids, and no way to go back. definitely simplifies the code to not need to plumb this through, or to have to have two branches

Comment on lines +171 to 178
protected ComponentChannel.Builder buildChannel(String channelID, ChannelTypeUID channelTypeUID, Value valueState,
String label, ChannelStateUpdateListener channelStateUpdateListener) {
if (singleChannelComponent) {
channelID = groupId;
}
return new ComponentChannel.Builder(this, channelID, channelTypeUID, valueState, label,
channelStateUpdateListener);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please elaborate here? I'm not sure what you mean about types being determined.

if (newStyleChannels && this.singleChannelComponent) {
groupId = haID.component;
} else {
groupId = "";
Copy link
Contributor Author

@ccutrer ccutrer Apr 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't be null, since it's not just used as part of a channel id, but also for other uses in the thing handler that don't allow null. Perhaps when I have more time I'll check if I can change those all to allow nullable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement An enhancement or new feature for an existing add-on
Projects
None yet
4 participants