Skip to content

Matter Switch: Configure individual electrical clusters if electrical sensor device type is not found - #3149

Open
hcarter-775 wants to merge 1 commit into
mainfrom
configure/plugs-with-generic-energy-clusters
Open

Matter Switch: Configure individual electrical clusters if electrical sensor device type is not found#3149
hcarter-775 wants to merge 1 commit into
mainfrom
configure/plugs-with-generic-energy-clusters

Conversation

@hcarter-775

@hcarter-775 hcarter-775 commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Description of Change

Electrical clusters (energy/power measurement) are often associated with the Electrical Sensor device type. We have support today for the device type and its corresponding Power Topology cluster. However, some devices do not support this device type, but do still expose these clusters. This is generally allowed, per spec, though it loses clarity for the controller what those electrical clusters actually map to. Still, this should be supported in a general way, since there are several popular devices in the field that do not appear to expose the Electrical Sensor device type for one reason or another.

It is unclear what the internals of the problem exactly are in this issue: #3147, but I believe this may be the solution.

Summary of Completed Tests

Unit tests added. On-device testing still required.

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

Test Results

   73 files    541 suites   0s ⏱️
3 246 tests 3 246 ✅ 0 💤 0 ❌
5 238 runs  5 238 ✅ 0 💤 0 ❌

Results for commit b593c00.

@github-actions

Copy link
Copy Markdown

matter-switch_coverage.xml

File Coverage
All files 92%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua 97%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua 92%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/sub_drivers/eve_energy/init.lua 91%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/event_utils.lua 52%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/init.lua 97%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/utils.lua 98%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/device_configuration.lua 94%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_garage_door/init.lua 97%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua 96%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/init.lua 90%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/capability_handlers.lua 83%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/attribute_handlers.lua 96%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/switch_handlers/event_handlers.lua 97%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua 80%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/switch_handlers/capability_handlers.lua 92%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/sub_drivers/aqara_cube/init.lua 95%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/sub_drivers/third_reality_mk1/init.lua 93%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua 93%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua 75%

Minimum allowed coverage is 90%

Generated by 🐒 cobertura-action against b593c00

@ldeora

ldeora commented Jul 28, 2026

Copy link
Copy Markdown

Thank you again for investigating this.

We now have on-device logs from a P316M-specific test build, and they clarify the failure considerably.

Confirmed P316M endpoint structure

SmartThings supplies the Edge driver with the complete Matter endpoint inventory:

all=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
OnOff server=[1, 2, 3, 4, 5, 6]
Electrical Sensor=[7, 8, 9, 10, 11, 12]
default=1

The six Electrical Sensor endpoints also return the expected AvailableEndpoints associations:

Electrical Sensor EP 7  -> OnOff EP 1
Electrical Sensor EP 8  -> OnOff EP 2
Electrical Sensor EP 9  -> OnOff EP 3
Electrical Sensor EP 10 -> OnOff EP 4
Electrical Sensor EP 11 -> OnOff EP 5
Electrical Sensor EP 12 -> OnOff EP 6

All six reads completed successfully.

This rules out several possible causes:

  • SmartThings exposing only outlet 1 to the Edge driver;
  • missing On/Off endpoint discovery;
  • missing Electrical Sensor device types;
  • missing PowerTopology;
  • invalid AvailableEndpoints data from the device;
  • and a general inability of try_create_device() to create the child devices.

The P316M follows the existing Electrical Sensor plus SET_TOPOLOGY path. It is therefore not an example of the device structure primarily addressed by the newly added fallback in this PR, where electrical measurement clusters exist without Electrical Sensor device-type endpoints.

I still think that fallback is a useful generic compatibility improvement. However, the new on-device evidence indicates that it is not the missing piece for the P316M regression.

Result of creating children before electrical profiling completes

The test build enumerated the known On/Off endpoints immediately and requested basic plug-binary children for endpoints 2–6 before waiting for the electrical topology to finish:

Requesting child for OnOff EP 2 with profile plug-binary
Requesting child for OnOff EP 3 with profile plug-binary
Requesting child for OnOff EP 4 with profile plug-binary
Requesting child for OnOff EP 5 with profile plug-binary
Requesting child for OnOff EP 6 with profile plug-binary

SmartThings successfully registered all five children.

After all six AvailableEndpoints associations had been processed, the parent and the five existing children were updated to:

plug-power-energy-powerConsumption

On/Off reports for endpoints 2–6 were then emitted on the corresponding child devices.

This confirms that:

  1. all six controllable endpoints are present in the Matter device data;
  2. the hub can create Edge children for endpoints 2–6;
  3. the electrical topology is valid;
  4. basic child devices can be created before electrical profiling completes;
  5. and the children can be enriched with their final electrical profiles afterward.

Suggested generic driver change

I suggest separating structural endpoint creation from optional profile enrichment.

The current order in DeviceConfiguration.match_profile() is effectively:

Wait for all profiling fields
        |
        v
Enumerate OnOff endpoints
        |
        v
Create child devices
        |
        v
Assign final profiles

The relevant code currently begins with:

function DeviceConfiguration.match_profile(driver, device)
  if profiling_data_still_required(device) then return end

  local default_endpoint_id = switch_utils.find_default_endpoint(device)
  local server_onoff_ep_ids = device:get_endpoints(clusters.OnOff.ID)

  if #server_onoff_ep_ids > 0 then
    ChildConfiguration.create_or_update_child_devices(
      driver,
      device,
      server_onoff_ep_ids,
      default_endpoint_id,
      SwitchDeviceConfiguration.assign_profile_for_onoff_ep
    )
  end

  -- Remaining parent-profile matching
end

Because child creation is below the global profiling gate, any unresolved profiling field prevents the driver from even attempting to create otherwise valid and independently controllable On/Off children.

A more fault-tolerant order would be:

Enumerate OnOff endpoints
        |
        v
Create basic child devices
        |
        v
Wait for electrical profiling
        |
        v
Upgrade parent and child profiles

Conceptually, the flow could be split like this:

function DeviceConfiguration.match_profile(driver, device)
  local default_endpoint_id =
    switch_utils.find_default_endpoint(device)

  local server_onoff_ep_ids =
    device:get_endpoints(clusters.OnOff.ID)

  -- Structural configuration:
  -- create independently controllable OnOff endpoints first.
  if #server_onoff_ep_ids > 0 then
    ChildConfiguration.ensure_basic_onoff_children(
      driver,
      device,
      server_onoff_ep_ids,
      default_endpoint_id
    )
  end

  -- Optional capability/profile enrichment can wait.
  if profiling_data_still_required(device) then
    return
  end

  -- Apply final profiles to the parent and existing children.
  if #server_onoff_ep_ids > 0 then
    ChildConfiguration.create_or_update_child_devices(
      driver,
      device,
      server_onoff_ep_ids,
      default_endpoint_id,
      SwitchDeviceConfiguration.assign_profile_for_onoff_ep
    )
  end

  -- Existing parent-profile matching continues here.
end

This is only pseudocode. The important design distinction is:

Endpoint structure -> create basic devices
Electrical mapping -> enrich their profiles

rather than:

Electrical mapping must complete -> create devices

SwitchDeviceConfiguration.assign_profile_for_onoff_ep() already has a binary fallback when no electrical tags are available, and create_or_update_child_devices() already updates the metadata of an existing child once the final profile is known. Most of the required profile-enrichment behavior therefore already exists.

Idempotency for asynchronous child creation

The existing child-creation function should not simply be moved above the profiling gate without an additional guard.

driver:try_create_device() is asynchronous. Until the child has been registered, switch_utils.find_child() still returns nil. Repeated lifecycle events, topology reports or calls to match_profile() could otherwise submit duplicate creation requests for the same endpoint.

The test build used a non-persistent pending set keyed by endpoint ID. Its behavior was:

existing child:
    update its profile

no child, request already pending:
    do nothing

no child, no request pending:
    call try_create_device()
    mark endpoint as pending

The pending entry was cleared when the corresponding child reached its configuration lifecycle.

Keeping this state non-persistent also permits a clean retry after a driver restart if a child-creation request is lost or rejected.

An implementation could follow this general pattern:

local pending = device:get_field(fields.PENDING_CHILD_CREATIONS) or {}

if existing_child_device then
  existing_child_device:try_update_metadata({
    profile = child_profile,
    optional_component_capabilities = optional_component_capabilities
  })
elseif not pending[ep_id] then
  pending[ep_id] = true
  device:set_field(fields.PENDING_CHILD_CREATIONS, pending)

  driver:try_create_device({
    type = "EDGE_CHILD",
    label = label_and_name,
    profile = child_profile,
    parent_device_id = device.id,
    parent_assigned_child_key = string.format("%d", ep_id),
    vendor_provided_label = label_and_name
  })
end

The exact storage location and lifecycle used to clear the pending entry can of course follow the conventions preferred by the driver.

Why early child creation appears to be the relevant change

An earlier test build restored explicit AvailableEndpoints reads but still produced only outlet 1.

The subsequent test build retained those reads and added early basic child creation. With that change:

  • all five missing children were created;
  • duplicate child requests were suppressed while registration was pending;
  • all topology associations were processed;
  • all six devices received their final power/energy profile;
  • and On/Off reports were routed to the appropriate devices.

This makes the placement and timing of child creation the strongest current explanation for the observed difference.

Suggested regression test

In addition to the single-endpoint electrical-cluster tests already added in this PR, I suggest adding a multi-endpoint fixture representing the P316M structure:

OnOff endpoints:             1, 2, 3, 4, 5, 6
Electrical Sensor endpoints: 7, 8, 9, 10, 11, 12
PowerTopology feature:       SET_TOPOLOGY
Mappings:                    7->1, 8->2, 9->3, 10->4, 11->5, 12->6
Default endpoint:            1

The tests should verify that:

  1. children for endpoints 2–6 are requested while POWER_TOPOLOGY is still unresolved;
  2. repeated lifecycle and profile-matching calls do not submit duplicate child requests;
  3. delayed topology reports do not suppress basic outlet control;
  4. later topology reports add the correct electrical tags;
  5. the existing children are updated to plug-power-energy-powerConsumption;
  6. all six On/Off reports resolve to the correct parent or child device;
  7. existing single-outlet devices retain their current behavior.

Separate electrical-value observation

The test build successfully selected the power/energy profile and subscribed to:

ElectricalPowerMeasurement.ActivePower
ElectricalEnergyMeasurement.CumulativeEnergyImported
ElectricalEnergyMeasurement.PeriodicEnergyImported

The logs then showed repeated errors originating in the generated SmartThings ElectricalEnergyMeasurement code:

bad argument #1 to 'for iterator'
(table expected, got nil)

This appears to be separate from the missing-child regression, because all five children had already been created and assigned their final profiles.

It may nevertheless explain why actual power or energy values remain blank even after the topology and profiles are correct. Testing the three electrical attributes separately would help identify the exact report or data shape that triggers the decoder error.

Summary

I think PR #3149 should remain as a generic improvement for devices that expose electrical clusters without Electrical Sensor device types.

For issue #3147, the on-device logs support an additional generic change: create basic On/Off children independently of electrical profiling, then enrich their profiles after the topology information becomes available.

That avoids a P316M-specific workaround and ensures that an incomplete optional profiling step cannot hide valid, independently controllable Matter endpoints.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants