Skip to content

feat: Add Endpoint.sendRaw for non-ZCL APS payloads - #1843

Open
bharat wants to merge 1 commit into
Koenkk:masterfrom
bharat:feat/endpoint-sendraw
Open

feat: Add Endpoint.sendRaw for non-ZCL APS payloads#1843
bharat wants to merge 1 commit into
Koenkk:masterfrom
bharat:feat/endpoint-sendraw

Conversation

@bharat

@bharat bharat commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Adds Endpoint.sendRaw(clusterId, data, options), which sends a bare APS payload to the endpoint exactly as provided, with no ZCL framing. Some vendors carry proprietary protocols in APS payloads that are not ZCL frames (Control4 in-wall keypads carry ASCII text on custom profile 0xC25C), and every existing send path serializes a ZCL header, so such payloads currently cannot be produced at all. The profileId option and the adapter plumbing for it already exist; the mandatory ZCL framing is the only gap this closes.

The payload rides the normal request pipeline (request queue, sendPolicy, recovery) via a minimal frame-shaped carrier whose toBuffer() returns the data verbatim. No response correlation is performed and disableResponse defaults to true accordingly: replies, if any, arrive through the normal incoming message path, which #1837 already admits for whitelisted custom profiles. If you would prefer a first-class raw-frame type or an adapter-level API over the carrier object, I am happy to rework it to whatever shape you want; this is the minimal version.

Tests assert the payload reaches the adapter byte-for-byte with the requested profileId and the disableResponse default, plus the error path.

In production this has driven 31 Control4 devices on an ember coordinator since early July: every LED command, config write, and sequence-matched configuration read goes through this path.

This completes the sequence started in #1837 and #1840, and is the capability ArcadeMachinist asked about when closing #1792 (constructing APS frames from a converter): with it, Control4 support can live entirely in converters with no forked core.

Some vendors carry proprietary protocols in bare APS payloads that are
not ZCL frames (Control4 keypads carry ASCII text on custom profile
0xC25C). The regular command/write paths always serialize a ZCL header,
so they cannot produce such payloads. sendRaw sends the buffer verbatim
on the given cluster, typically combined with options.profileId to
address a custom profile, riding the normal request pipeline via a
minimal frame-shaped carrier whose toBuffer() returns the data as
provided.

Co-Authored-By: Claude <noreply@anthropic.com>
@Nerivec

Nerivec commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

That kind of custom protocol addition was denied introduction before. They just bring too much trouble from maintenance to proper support. That "rougher shape" might work for these devices, but what if next custom protocol needs a slightly different logic... another custom function..?
ZH should remain spec-following, with the exception of a few quirks.

@bharat

bharat commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

I read through the concerns in #1792, where vendor protocol logic ended up inside ZH, and am trying to thread the needle here: keep ZH clean while adding a small generic escape hatch.

The intent here is to NOT put vendor protocol logic inside ZH - quite the opposite. Sorry if I didn't frame it well. sendRaw contains no protocol knowledge at all: it sends the given bytes on the given cluster and profile, and that is the whole function.

If a future custom protocol needs different logic, that logic lives in its converter and doesn't require changes or special knowledge in ZH. In that sense, this is an attempt to support the devices behind #1792 and similar efforts in a ZH-friendly, spec-friendly way.

It is also the TX half of something ZH already does on RX: since #1418 and #1837 the adapters deliberately accept non-ZCL frames on whitelisted custom profiles and deliver them to consumers. Today those consumers can listen but cannot speak without forking core.

Happy to rework this in any way you'd like if you can suggest a better approach - including constraining it (for example refusing the standard HA/ZGP profile ids, or documenting it as an expert API with no support guarantees) if the worry is the support burden. I'm just hoping to make this accessible to folks on these non-standard platforms without them having to carry a fork, or putting more burden on the ZH team.

@Nerivec

Nerivec commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

no protocol knowledge at all

That behavior is definitely specific to this device. That's the whole problem of custom protocols, each will have their own (even if it's just one bit that needs flipping). Not to mention, potential internal updates to custom protocols would require reworking every aspect to support that as well...

Then we'd also end up with issues in specific adapters, because one drops these frames, another cannot process them and crashes, etc..
And probably the whole waiter aspect fails as well...

A custom profile that follows the protocol is very different from an entirely custom protocol.

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