[ST Edge] Is event modified inside emit_component_event?

Hi,

Is seems device:emit_component_event (and also component:emit_event) modify the passed event.

For the following code:

    local event = st_capabilities.button.supportedButtonValues(supported)
    for _, component in pairs(device.profile.components) do
        if device:supports_capability(st_capabilities.button, component.id) then
            -- component:emit_event( event )
            device:emit_component_event(component,event)
        end
    end

I get the following:

2022-04-14T20:25:53.755161482+00:00 INFO Zigbee Button Remote Driver [YG]  <ZigbeeDevice: d7e16b5d-77fe-4812-b476-6c9c9abd844a [0x49DC] (6 button remote)> emitting event: {"attribute_id":"supportedButtonValues","capability_id":"button","component_id":"button6","state":{"value":["pushed","held"]}}
2022-04-14T20:25:53.775900482+00:00 INFO Zigbee Button Remote Driver [YG]  <ZigbeeDevice: d7e16b5d-77fe-4812-b476-6c9c9abd844a [0x49DC] (6 button remote)> emitting event: {"attribute_id":"supportedButtonValues","capability_id":"button","component_id":"button1","state":{"value":{}}}
2022-04-14T20:25:53.797442482+00:00 INFO Zigbee Button Remote Driver [YG]  <ZigbeeDevice: d7e16b5d-77fe-4812-b476-6c9c9abd844a [0x49DC] (6 button remote)> emitting event: {"attribute_id":"supportedButtonValues","capability_id":"button","component_id":"button5","state":{"value":{}}}
2022-04-14T20:25:53.818411815+00:00 INFO Zigbee Button Remote Driver [YG]  <ZigbeeDevice: d7e16b5d-77fe-4812-b476-6c9c9abd844a [0x49DC] (6 button remote)> emitting event: {"attribute_id":"supportedButtonValues","capability_id":"button","component_id":"button2","state":{"value":{}}}
2022-04-14T20:25:53.839763148+00:00 INFO Zigbee Button Remote Driver [YG]  <ZigbeeDevice: d7e16b5d-77fe-4812-b476-6c9c9abd844a [0x49DC] (6 button remote)> emitting event: {"attribute_id":"supportedButtonValues","capability_id":"button","component_id":"button3","state":{"value":{}}}
2022-04-14T20:25:53.860387815+00:00 INFO Zigbee Button Remote Driver [YG]  <ZigbeeDevice: d7e16b5d-77fe-4812-b476-6c9c9abd844a [0x49DC] (6 button remote)> emitting event: {"attribute_id":"supportedButtonValues","capability_id":"button","component_id":"button4","state":{"value":{}}}

If I deep copy the event before emitting, everything works as expected

device:emit_component_event(component,st_utils.deep_copy(event))

I think before the last hub update, deep copy wasn’t required.
Is it a new requirement that once the event is emitted, it can no longer be used?

I think other multicomponent devices (like switches) might be affected by this change.

Thanks

P.S.
I was looking into lua_libs-api_v0x40 code and couldn’t find any modification of the passed event

What’s the content of the variable “supported”?
Does it also happen if you send the command directly like below?

device:emit_component_event(component,st_capabilities.button.supportedButtonValues({"pushed", "held", "double", "pushed_3x"})

Hi @nayelyz

Thank you for your reply.

In this example, the value is {“pushed”,“held”}.
Please take a look at the first line in the log, the hub receives the correct value.
Following lines contain empty array.

Naturally, this doesn’t happen when sending array directly.
However, I’d like to allow user to choose what values would be shown in capability, so I’m building the array dynamically from enabled preferences.
Regards,
Yakov

I believe 0x42 is available for download now.

I find myself as curious to know the answer as you must be.

Presumably if event is assigned in the loop things also work? So the unexpected behaviour is when it is the exact same event table being used and not just the same content?

1 Like

This is interesting, I wanted to get more info to make tests in the same conditions as, @ygerlovin. It is strange how it executes correctly the first time but not the next ones.
It could be how the table is created as you’re receiving the values from a preference, a while ago, I saw different behavior based on how the table was defined but not when sending an event…
@ygerlovin are you constructing the table like shown in these docs? If so, which one?

If this happens only for cases where the table parameter of the event is created dynamically, I think it won’t affect the drivers released by SmartThings as this kind of property (supportedValues - see this sample) is not defined by the user but the manufacturer. I’ll verify this with the team anyway.

I’ll make more tests to see other possible scenarios.

@nayelyz

I have to use utils.deep_copy for it to work, otherwise it will be empty too.

Below is the example code and log.

local config = {}
config.supportedDevices = {"tasmota-fan", "tasmota-generic-switch", "tasmota-metering-switch", "tasmota-dimmer-switch", "tasmota-light-cct", "tasmota-light-rgbw", "tasmota-light-rgb", "tasmota-ir-bridge", "tasmota-rf-bridge"}

print("DEBUG (1): config.supportedDevices: " .. utils.stringify_table(config.supportedDevices))
local supportedDevices = {}
supportedDevices = config.supportedDevices
print("DEBUG (2): supportedDevices: " .. utils.stringify_table(supportedDevices))
device:emit_event(cap_tasmotadevice.supportedDevices({value=supportedDevices}))
print("DEBUG (3): supportedDevices: " .. utils.stringify_table(supportedDevices))
print("DEBUG (4): config.supportedDevices: " .. utils.stringify_table(config.supportedDevices))
device:emit_event(cap_tasmotadevice.supportedDevices({value=supportedDevices}))
2022-04-19T02:35:01.036806623+00:00 PRINT Tasmota Edge (Beta)  DEBUG (1): config.supportedDevices: {"tasmota-fan", "tasmota-generic-switch", "tasmota-metering-switch", "tasmota-dimmer-switch", "tasmota-light-cct", "tasmota-light-rgbw", "tasmota-light-rgb", "tasmota-ir-bridge", "tasmota-rf-bridge"}
2022-04-19T02:35:01.040793915+00:00 PRINT Tasmota Edge (Beta)  DEBUG (2): supportedDevices: {"tasmota-fan", "tasmota-generic-switch", "tasmota-metering-switch", "tasmota-dimmer-switch", "tasmota-light-cct", "tasmota-light-rgbw", "tasmota-light-rgb", "tasmota-ir-bridge", "tasmota-rf-bridge"}
2022-04-19T02:35:01.054700873+00:00 INFO Tasmota Edge (Beta)  <Device: 5afd08ff-96f5-4b41-8683-c545c46c2934 (Tasmota Edge (Beta))> emitting event: {"attribute_id":"supportedDevices","capability_id":"voicehouse43588.tasmotaDevice5","component_id":"main","state":{"value":["tasmota-fan","tasmota-generic-switch","tasmota-metering-switch","tasmota-dimmer-switch","tasmota-light-cct","tasmota-light-rgbw","tasmota-light-rgb","tasmota-ir-bridge","tasmota-rf-bridge"]}}

2022-04-19T02:35:01.085911248+00:00 PRINT Tasmota Edge (Beta)  DEBUG (3): supportedDevices: {1="tasmota-fan", 2="tasmota-generic-switch", 3="tasmota-metering-switch", 4="tasmota-dimmer-switch", 5="tasmota-light-cct", 6="tasmota-light-rgbw", 7="tasmota-light-rgb", 8="tasmota-ir-bridge", 9="tasmota-rf-bridge"}
2022-04-19T02:35:01.089862040+00:00 PRINT Tasmota Edge (Beta)  DEBUG (4): config.supportedDevices: {1="tasmota-fan", 2="tasmota-generic-switch", 3="tasmota-metering-switch", 4="tasmota-dimmer-switch", 5="tasmota-light-cct", 6="tasmota-light-rgbw", 7="tasmota-light-rgb", 8="tasmota-ir-bridge", 9="tasmota-rf-bridge"}
2022-04-19T02:35:01.103981248+00:00 INFO Tasmota Edge (Beta)  <Device: 5afd08ff-96f5-4b41-8683-c545c46c2934 (Tasmota Edge (Beta))> emitting event: {"attribute_id":"supportedDevices","capability_id":"voicehouse43588.tasmotaDevice5","component_id":"main","state":{"value":{}}}
1 Like

Given what @hongtat has demonstrated, it looks like that could be a poor presumption.

1 Like

Based on @hongtat 's comment it looks like assigning event inside the loop wouldn’t help either

Hi @nayelyz

I construct the table like this:

function ButtonUtils.init_buttons(device, supported_actions)

    local supported = supported_actions or { "pushed" }

    for key, value in pairs(device.preferences) do
        if value then
            local v = key:match("(%a+)ButtonActionEnable")
            if v then
                table.insert(supported,v)
            end
        end
    end
    local event = st_capabilities.button.supportedButtonValues(supported)
    for _, component in pairs(device.profile.components) do
        if device:supports_capability(st_capabilities.button, component.id) then
            device:emit_component_event(component, st_utils.deep_copy(event))
        end
    end
end

This function is used for both single and multi component buttons.
Whether button profile has action preference enabled, it will be added to a list of supported actions.
Without deep copy

st_utils.deep_copy(event)

it doesn’t work as expected.
Thanks

@hongtat
Thank you, this is very useful insight.
So it seems the payload inside the event is modified.

Thank you all for the extra info.
I was able to replicate the issue and I already created a report. I’ll let you know once I get more info from the team.

1 Like

Following up on this, the team mentioned this is a known issue and it will be fixed in a future release.
There’s no ETA yet but the workaround is to use deep_copy(), so you all are on the right path.
For example:

device.profile.components["componentID"]:emit_event(capabilities.button.supportedButtonValues(utils.deep_copy(supportedMode)))
1 Like

@nayelyz Cool, thank you!

Could you please elaborate regarding the root cause of this issue?
Does it happen only when emitting events or it is more broad issue, related to garbage collection and should be anticipated for other library calls?
Thanks

It is only when we emit multiple capability events re-using a common table (due to the internal process that handles the events).
If we send a table in each event (see the sample below), is like creating different ones so, those events are not affected.

device:emit_component_event(component1ID, capabilities.button.supportedButtonValues({"pushed", "held"}))
device:emit_component_event(component2ID, capabilities.button.supportedButtonValues({"pushed", "held"}))

The team will take care of the fix, in case an additional action is needed, they will let us know. That’s why they haven’t changed the stock driver of zigbee-multi-button either.

Thank you, @nayelyz .

Perhaps, my question wasn’t clear. Please let me rephrase.
Do we understand what is causing modification of the internal table (inside the event), passed to emit_event function?

Yes, it is modified because of how the event is processed internally, aside from updating the capability value, there are other actions applied to the event. For example, to be available in device.state_cache.
Sorry, I don’t have the exact details but I know the team has identified the issue and will work on the fix.

1 Like