Skip to content

WWST: add Zemismart KES606 four-gang relay/scene switch - #3239

Draft
zemismart-dev wants to merge 6 commits into
SmartThingsCommunity:mainfrom
zemismart-dev:codex/kes606-four-gang-certification
Draft

zemismart-dev wants to merge 6 commits into
SmartThingsCommunity:mainfrom
zemismart-dev:codex/kes606-four-gang-certification

Conversation

@zemismart-dev

@zemismart-dev zemismart-dev commented Sep 17, 2026 •

Copy link
Copy Markdown

Type of Change

  • WWST Certification Request
    • I have reviewed the README.md file
    • I have reviewed the CODE_OF_CONDUCT.md file
    • I have signed the CLA
    • I plan on entering a WWST Certification Request or have entered a request through the WWST Certification console at developer.smartthings.com
  • Bug fix
  • New feature
  • Refactor

Checklist

  • I have performed a self-review of my code
  • I have commented my code in hard-to-understand areas
  • I have verified my changes by testing with a device or have communicated a plan for testing
  • I am adding new behavior, such as adding a sub-driver, and have added and run new unit tests to cover the new behavior

Description of Change

Add exact-fingerprint support for the Zemismart KES-606US-L4 (_TZ3000_hurauima / TS0726) to Zigbee Switch, with four independent relay/scene children.

Following maintainer feedback, the parent now exposes switch, button and refresh. Parent on/off targets only confirmed relay-mode gangs; scene-mode or pending-transition gangs are skipped. Its switch is on when any eligible relay reports on, and off only when every eligible relay is known off (or no relay-mode gang remains). Command acknowledgements and unknown reports do not invent aggregate state. A real scene-button command is forwarded to the appropriate child and the parent's aggregate pushed event; initialization and relay reports never synthesize presses.

Requested modes are kept separate from protocol-confirmed modes. A persisted pending transition blocks control and prevents a premature aggregate off until D020 readback resolves the actual mode. Rejected changes and late asynchronous child profile updates are reconciled. Existing parent profiles migrate to the new switch/button profile with readback after completion.

Existing exact endpoint mapping, countdown, indicator and global/per-gang power-on settings are retained. The dedicated DefaultResponse handler keeps generic native handling from bypassing child/aggregate routing. Legacy ts0601 profile names remain for continuity; no other fingerprint or custom capability is added.

Current review changes and validation

Commit 09a46554713f6c093d1fdda2a333ce5a10856103 simplifies the exact KES606 matcher as requested and nests the handler under tuya-multi, using both the lazy-load registration module and the full init table. The Tuya entry point consults the physical parent's endpoints for KES606 children, while generic Tuya children retain their previous root routing to avoid extra configuration traffic. Sparse SDK endpoint maps and single-endpoint behavior are covered.

  • Official SDK API 19: 140/140 focused tests passed; API 21: 140/140 passed, on genuine Lua 5.3 via Lupa 2.8.
  • Coverage: KES606 127, Tuya 8 and existing Tuya multi-switch 5. Twelve additional regressions verify nested dispatch, parent/eight relay-scene child routing, malformed child keys, generic child behavior, sparse endpoint maps and single-endpoint configuration.
  • Luacheck 1.2.0 analysis API: 8 modified Lua files, zero warnings/errors/fatals. Independent source review, build-only packaging and git diff --check passed. SDK files unchanged.
  • Broad zigbee-switch runs were compared against a clean 192595d baseline using the same local SDK/runtime. Both revisions have the same failing case sets:1 API 19 failure and 62 API 21 failures involving existing SDK/capability argument validation and local Lua math.pow availability. No new broad failures were introduced. These full local runs are not claimed as all-green; the final two added sparse/single-endpoint tests were subsequently run in both focused suites.
  • The three profile YAML files and their preference semantics are unchanged. Ninety current shared definitions were audited. Power/indicator/countdown definitions lack equivalent choices/ranges; the closest mode Boolean changes the saved preference key/type and Scene default. Inline definitions are retained pending agreement on a compatible shared enum or supported migration path. A code comment explains tracked mode versus asynchronous child-profile capabilities.

The October 15 routing revision has been built and tested locally, but has not replaced the Hub package or been physically retested. The hardware evidence below is explicitly for 192595d and its October 13 package. Hardware validation of the new routing revision remains a separate step before release.

Hardware and certification evidence for the October 13 baseline

New package 2026-10-13T07:18:46.468510907 is installed and selected by a clean pairing of the physical sample, with exactly one parent and four gang children. The initial generic-driver takeover retained three old generic endpoint children; the test family was explicitly removed and cleanly re-paired rather than treating that topology as accepted.

  • Targeted Operational Test 66VJTV2QBH: 33 passed, 0 failed, 18 excluded, 0 skipped/errors. The previously failing parent automation-presentation check now passes, as do parent and all four child on/off pairs. Operator confirmation: parent controls all four together, independent child routing is correct, final loads off. Excluded items are lifecycle, preference pledges and the parent scene-button case; this is not a new Full Integration Test.
  • With L4 in confirmed Scene mode, a six-step parent/child aggregate regression passed. Parent commands targeted endpoints1-3 only; L4 remained unchanged. Turning L1 off while L2 stayed on kept the parent on, then the final L2 off updated the parent off. Protocol, cloud state and physical operator confirmation agreed, final all-off.
  • Mixed-mode formal retest 47NKZ9SQDD: 28 passed,0 failed,14 excluded,0 skipped/errors. With L1Switch and L2-L4Scene, parent switch and aggregate button, L2/L3/L4 physical scene events, and L1 relay commands all passed. Each real0xFD press produced both its child and parent pushed event. Targeted L1scene report 7YSI12M2VD subsequently passed 20cases with0failures (19excluded), completing formal real-press coverage of all four children on the same package. All four modes were then restored to Switch: actual D020readback0, switch/refresh profiles and saved preferences verified. Final parent on/off targeted all four endpoints; protocol/cloud state and onsite confirmation agreed, final all loads off. No Hub reboot was needed.
  • Session 94I3MFKV7D was canceled (10passed,1failed,13skipped,15excluded): the Test Suite's parent button initialization requires parent switch On even when switch test cases are excluded. With every gang in Scene, the parent correctly has no relay targets and remains off, so initialization failed. This report remains preserved; a relay-mode gang must remain available for this initialization, while individual child scene cases can be tested separately. No synthetic switch/button state was added to bypass this requirement.

The hardware-tested 192595d baseline passed all six upstream Driver Tests jobs and CLA.

The WWST application was submitted on September17 and remains under review. Historical full test 6MUUP3G0YH (46passed/1parent-presentation failure) belongs to the September17 package 2026-09-17T04:56:18.385495812; it is not evidence for the new parent capability behavior. The earlier scene report X90YMMGWXX belongs to the previous03:24 candidate. The later formal scene attempt XQA0I9YQF8 remains17passed/5failed/13excluded; continuous-log physical verification did not relabel it as passing. Earlier physical countdown/indicator/power-cycle evidence remains version-specific supplemental evidence.

The original cloud-state stall cleared after an approved Hub reboot; its root cause was not established. The later September17 mode-return regression passed. Current October 13 hardware verification will be recorded separately.

This remains a draft for maintainer review of the routing follow-up and shared-preference compatibility explanation. No certification approval or parent-presentation exception is assumed.

@CLAassistant

CLAassistant commented Sep 17, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@github-actions

Copy link
Copy Markdown

Duplicate profile check: Passed - no duplicate profiles detected.

@github-actions

Copy link
Copy Markdown

Profile category check: ✅ Passed - all profiles have a category defined.

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

zigbee-switch_coverage.xml

File Coverage
All files 92% ✅
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/init.lua 92% ✅
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/lazy_load_subdriver.lua 57% ❌
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/preferences.lua 97% ✅
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/inovelli/init.lua 86% ❌
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/aqara/init.lua 92% ✅
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/inovelli/vzm32-sn/init.lua 95% ✅
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/firstled-io/init.lua 91% ✅
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/rgbw-bulb/init.lua 87% ❌
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/init.lua 82% ❌
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua 96% ✅
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/zll-dimmer/init.lua 81% ❌
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/configurations/init.lua 98% ✅
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/stateless_handlers/init.lua 44% ❌
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua 92% ✅
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/frient-IO/init.lua 88% ❌
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/frient-IO/unbind_request.lua 71% ❌
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/inovelli/vzm30-sn/init.lua 95% ✅
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/aqara/version/init.lua 94% ✅
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/white-color-temp-bulb/duragreen/init.lua 66% ❌
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/frient/init.lua 94% ✅
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/zemismart-kes606/init.lua 96% ✅
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/ge-link-bulb/init.lua 81% ❌
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/color_temp_range_handlers/init.lua 71% ❌
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/tuya-multi/can_handle.lua 91% ✅
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/zigbee-dimming-light/osram-iqbr30/init.lua 77% ❌
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/zll-polling/init.lua 81% ❌
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/ikea-xy-color-bulb/init.lua 57% ❌

Minimum allowed coverage is 90%

Generated by 🐒 cobertura-action against e1c0c3c

@github-actions

Copy link
Copy Markdown

Test Results

   73 files  ± 0    542 suites  +1   0s ⏱️ ±0s
3 359 tests +94  2 914 ✅  - 351  0 💤 ±0  0 ❌ ±0  445 🔥 +445 
5 348 runs  +94  4 759 ✅  - 495  0 💤 ±0  0 ❌ ±0  589 🔥 +589 

For more details on these errors, see this check.

Results for commit e1c0c3c. ± Comparison against base commit 87c61ad.

@cjswedes cjswedes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A refresh-only parent with four independently switchable relay/scene children. Both the existing implementation and candidate tests pass the child automation-presentation checks, while the parent fails the generic requirement for an automation action or condition. We are requesting review of that design; no exception approval is assumed.

I am not sure what to do about this; it may need an exception. My only idea is that the parent could have a switch and button capability on its main component:

  • button is pressed whenever a child device button is pressed (this is inline with functionality of other zigbee button devices)
  • switch controls all child devices that are in the non-scene mode, and if 1 child is on then the parent is on, otherwise it is off.

Comment on lines +7 to +19
if device == nil then return false end
local parent = device
if device.network_type == device_lib.NETWORK_TYPE_CHILD then
local gang = tonumber((device.parent_assigned_child_key or ""):match("^gang([1-4])$"))
if gang == nil then return false end
parent = device:get_parent_device()
elseif device.network_type ~= device_lib.NETWORK_TYPE_ZIGBEE then
return false
end
if parent ~= nil and parent:get_manufacturer() == "_TZ3000_hurauima"
and parent:get_model() == "TS0726" then
return true, require("zemismart-kes606")
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this can_handle is quite confusing:

  1. The get_manufacturer and get_model commands when called on a zigbee child device are overwritten to lookup the mfr/model on the parent device.
  2. The checking of if the parent_assigned_child_key parses correctly should not be done in this function. If that fails here, there are no other subdrivers/handlers where it will succeed, so we should just let it fail in this subdriver so that any errors (which shouldnt happen) are actually associated with the code that created the child devices
Suggested change
if device == nil then return false end
local parent = device
if device.network_type == device_lib.NETWORK_TYPE_CHILD then
local gang = tonumber((device.parent_assigned_child_key or ""):match("^gang([1-4])$"))
if gang == nil then return false end
parent = device:get_parent_device()
elseif device.network_type ~= device_lib.NETWORK_TYPE_ZIGBEE then
return false
end
if parent ~= nil and parent:get_manufacturer() == "_TZ3000_hurauima"
and parent:get_model() == "TS0726" then
return true, require("zemismart-kes606")
end
if device ~= nil and device:get_manufacturer() == "_TZ3000_hurauima"
and device:get_model() == "TS0726" then
return true, require("zemismart-kes606")
end
return false

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Applied in 09a4655. The KES606 matcher now uses the exact manufacturer/model check you suggested, relying on the SDK's child-to-parent lookup. Child-key validation no longer affects subdriver selection; malformed keys remain within the KES606 handler and are rejected by its gang-specific command/preference guards. Added regression coverage for those cases.

-- The composite relay/scene device has its own configure and refresh handlers.
if device:get_manufacturer() == "_TZ3000_hurauima" and device:get_model() == "TS0726" then
return false
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Rather than excluding like this, we should have the zemismart-kes606 subdriver be a nested subdriver of this Tuya multi subdriver. That will prevent it from using the handlers here that are overridden by the new zemismart subdriver.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Applied in 09a4655. KES606 is now nested under tuya-multi; the root entry and exact-fingerprint exclusion were removed. tuya-multi/sub_drivers.lua and the full init table register the same hierarchy for lazy loading.

KES606 children use the physical parent's endpoint map at the Tuya boundary. Other Tuya children keep their previous root routing, avoiding additional generic configure/refresh/magic-spell traffic. Tests cover nested parent/child dispatch and lifecycle behavior, generic Tuya parent/child behavior, sparse endpoint maps and single-endpoint configuration. Focused suites pass 140/140 on API 19 and API 21. This routing follow-up has not yet been installed on the physical test Hub; the October 13 physical reports remain tied to 192595d.

local function onoff_report(_, device, value, zb_rx)
local gang = endpoint_from_rx(zb_rx)
local parent = parent_from_device(device)
if gang == nil or parent == nil or gang_mode(parent, gang) == "scene" then return end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you clarify why gang_mode (the persisted field) and child:supports_capability are both consulted in some paths (onoff_report) but only the field is consulted in others (relay_command)?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The two checks serve different purposes. Within gang_mode, child capabilities are only a fallback when no tracked mode is available. The tracked mode is updated from device readback and governs whether an endpoint is eligible for relay control or scene handling. Pending transitions suppress routing until D020 confirms the actual mode.

Handler-level capability checks separately prevent emitting an event into an incompatible child profile while its cloud update is asynchronous. Relay commands therefore use the confirmed mode/pending guard; child switch-event emission additionally requires a switch-capable profile. A valid relay report still updates the parent cache during a profile transition, and profile completion triggers another read. I added a source comment to make the fallback distinction explicit.

categories:
- name: RemoteController
preferences:
- title: Indicator Light

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Regarding your review request:

The embedded preference definitions, or the approved shared preferences that should replace them without changing their meaning.

We would prefer you use the shared preferences where possible

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I audited the current standard, stse and certifiedpreferences catalogs (90 definitions). The three-state power-on and indicator settings and the 0–43200-second toggle countdown do not have equivalent shared definitions. For example, restorePowerState is Boolean; powerOffMemory adds unsupported reverse behavior; turnOffDelay lacks zero cancellation, the full range and delayed-on behavior.

stse.changeToWirelessSwitch matches the basic mode function, but replacing our existing switchMode enum also changes the saved key/type and introduces a false default for existing Scene children. I did not find a documented alias or supported per-device preference-value migration in the current profile/SDK interfaces. Ignoring the new default would leave the displayed setting inconsistent; treating it as an ordinary change risks switching a Scene gang back to relay mode.

I have therefore retained the inline definitions and saved settings in this revision. Could you advise whether there is a preferred compatible shared enum or supported migration mechanism for this case? The catalog/schema audit is complete, but I am not claiming a shared-preference conversion.

@zemismart-dev

Copy link
Copy Markdown
Author

Thanks @cjswedes. The parent switch/button suggestion was implemented in 192595d, and the physical failed-case retests are complete:

  • 66VJTV2QBH:33 passed, 0 failed — former parent presentation failure plus parent/four-child relay control.
  • 47NKZ9SQDD:28 passed, 0 failed — parent aggregate pushed, L2/L3/L4 real scene presses, and mixed-mode switch checks.
  • 7YSI12M2VD:20 passed, 0 failed — L1 real scene press, completing all four scene channels.

These are targeted operational reports with documented exclusions, not a new Full Integration Test. A separate mixed-mode regression confirmed master commands skip Scene gangs and aggregate state follows eligible relay reports. All four gangs were restored to Switch; the final parent on/off regression and operator confirmation ended with all loads off. The submitted certification report's notes now reference the new evidence.

I have also pushed 09a4655 for the matcher/nested-dispatch feedback and replied to all four inline comments. Focused API 19/API 21 suites each pass 140/140; lint and independent review passed. Full local zigbee-switch runs have the same pre-existing failing-case sets as the clean 192595d baseline; they are not reported as all-green. This new routing revision is locally validated only; the physical reports above remain associated with 192595d and package 2026-10-13T07:18:46.468510907.

The shared-preference comment has a compatibility explanation and a request for guidance on safe mode-preference migration. No certification approval is assumed.

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.

3 participants