-
Notifications
You must be signed in to change notification settings - Fork 101
Add XNCP coordinator LED support #710
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
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,7 @@ class XncpCommandId(t.enum16): | |
| SET_ROUTE_TABLE_ENTRY_REQ = 0x0006 | ||
| GET_ROUTE_TABLE_ENTRY_REQ = 0x0007 | ||
| GET_TX_POWER_INFO_REQ = 0x0008 | ||
| SET_LED_STATE_REQ = 0x0F00 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider a brief comment here explaining the Also a future-proofing thought: the firmware handler accepts both 3-byte (8-bit/ch) and 6-byte (16-bit/ch, big-endian) payload formats, but bellows only emits the 3-byte form. If 16-bit precision ever matters for ZBT-2 LED color smoothness, you'd want either a second payload class ( |
||
|
|
||
| GET_SUPPORTED_FEATURES_RSP = GET_SUPPORTED_FEATURES_REQ | 0x8000 | ||
| SET_SOURCE_ROUTE_RSP = SET_SOURCE_ROUTE_REQ | 0x8000 | ||
|
|
@@ -53,6 +54,7 @@ class XncpCommandId(t.enum16): | |
| SET_ROUTE_TABLE_ENTRY_RSP = SET_ROUTE_TABLE_ENTRY_REQ | 0x8000 | ||
| GET_ROUTE_TABLE_ENTRY_RSP = GET_ROUTE_TABLE_ENTRY_REQ | 0x8000 | ||
| GET_TX_POWER_INFO_RSP = GET_TX_POWER_INFO_REQ | 0x8000 | ||
| SET_LED_STATE_RSP = SET_LED_STATE_REQ | 0x8000 | ||
|
|
||
| UNKNOWN = 0xFFFF | ||
|
|
||
|
|
@@ -123,6 +125,9 @@ class FirmwareFeatures(t.bitmap32): | |
| # Recommended and maximum TX power can be queried by country code | ||
| TX_POWER_INFO = 1 << 7 | ||
|
|
||
| # The firmware exposes adapter LED control | ||
| LED_CONTROL = 1 << 31 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed against firmware: The gap between bit 7 ( |
||
|
|
||
|
|
||
| class XncpCommandPayload(t.Struct): | ||
| pass | ||
|
|
@@ -233,6 +238,18 @@ class GetTxPowerInfoRsp(XncpCommandPayload): | |
| max_power_dbm: t.int8s | ||
|
|
||
|
|
||
| @register_command(XncpCommandId.SET_LED_STATE_REQ) | ||
| class SetLedStateReq(XncpCommandPayload): | ||
| red: t.uint8_t | ||
| green: t.uint8_t | ||
| blue: t.uint8_t | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Payload shape verified against firmware The firmware also has a 6-byte branch for 16-bit-per-channel color. Out of scope for this PR, but worth a |
||
|
|
||
|
|
||
| @register_command(XncpCommandId.SET_LED_STATE_RSP) | ||
| class SetLedStateRsp(XncpCommandPayload): | ||
| pass | ||
|
|
||
|
|
||
| @register_command(XncpCommandId.UNKNOWN) | ||
| class Unknown(XncpCommandPayload): | ||
| pass | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -314,3 +314,23 @@ async def test_xncp_get_tx_power_info(ezsp_f: EZSP) -> None: | |
| ).serialize() | ||
| ) | ||
| ] | ||
|
|
||
|
|
||
| async def test_xncp_set_led_state(ezsp_f: EZSP) -> None: | ||
| """Test XNCP set_led_state.""" | ||
| ezsp_f._mock_commands["customFrame"] = customFrame = AsyncMock( | ||
| return_value=[ | ||
| t.EmberStatus.SUCCESS, | ||
| xncp.XncpCommand.from_payload(xncp.SetLedStateRsp()).serialize(), | ||
| ] | ||
| ) | ||
|
|
||
| await ezsp_f.xncp_set_led_state(red=1, green=2, blue=3) | ||
|
|
||
| assert customFrame.mock_calls == [ | ||
| call( | ||
| xncp.XncpCommand.from_payload( | ||
| xncp.SetLedStateReq(red=1, green=2, blue=3) | ||
| ).serialize() | ||
| ) | ||
| ] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test is fine and matches the established pattern in this file. Two optional additions worth considering for follow-up:
Both are nice-to-have, not project-pattern-required. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ZHA pull
zigpy/zha#715ends up calling this directly viaapplication_controller._ezsp.xncp_set_led_state(...)and gating onapplication_controller._ezsp._xncp_features. That's the first time a feature bit is being consumed from outsidebellows.zigbee.application-- the existingMEMBER_OF_ALL_GROUPS/MANUAL_SOURCE_ROUTE/RESTORE_ROUTE_TABLEcallers all live inside bellows.Consider exposing a public method on
bellows.zigbee.application.ControllerApplication(e.g.async def set_coordinator_led_state(self, red, green, blue) -> Nonethat checksFirmwareFeatures.LED_CONTROL in self._ezsp._xncp_featuresand raises a typedCoordinatorFeatureNotSupported-- or returns a sentinel -- otherwise). That gives ZHA a stable public surface to call instead of poking_ezspdirectly, and you avoid the implicit-API problem the next time someone refactorsEZSP._xncp_featuresinto something else.No-op if you prefer keeping the consumer inside bellows long-term, but the ZHA PR body explicitly raises this question, so worth answering in this PR's review.