You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Support for "live update" companion DPs (e.g. DP28) in tuya-local
TL;DR: Some Tuya RGB+CCT lights require a second "live update" DP (here: DP28)
alongside the main composite state DP (DP51) for brightness changes to register
reliably — DP51 alone can leave the light stuck at a low brightness. DP28 uses
an ASCII hex format with variable, unpadded field widths that differs per
lighting mode, which tuya-local's current fixed-width format/mask system can't
generate. This RFC proposes two optional, additive extensions — a template-based
variable-width string encoder, and a "companion write" mechanism so that
updating one DP can trigger a paired write to another — and asks whether either
fits tuya-local's architecture before a full implementation/PR is attempted.
Problem
Some Tuya RGB+CCT lights (confirmed on the Calex Smart Halo,
product-id f8bws9mp29vxwqoc) do not reliably accept brightness
changes by updating only their composite state DP (DP51, a 12-byte
encoded structure containing mode, hue, saturation, RGB brightness,
white brightness and color temperature).
After extensive packet captures and protocol testing, the following
behavior was consistently observed:
During continuous slider movement, the official Tuya application
repeatedly sends updates to DP28.
When the slider is released, the application sends a final DP51
update containing the complete persistent state.
Sending only DP51 reproduces an issue where RGB brightness can become
"stuck" at low values when increasing brightness again.
Reproducing the application's sequence (DP28 updates followed by a
final DP51 commit) works reliably in both directions.
This strongly suggests that DP28 acts as a temporary "live update"
channel while DP51 represents the committed device state.
DP28 uses a payload format that cannot currently be expressed using
tuya-local's existing format/mask mechanisms because it is an ASCII
hex string whose fields have variable widths and whose layout changes
depending on the active lighting mode.
From reverse engineering, the observed formats are:
White mode:
"10" + brightness (15-digit zero-padded hex)
+ color temperature (4-digit zero-padded hex)
These formats have been reproduced consistently across multiple packet
captures and repeated protocol tests.
Additional finding: the mode field controls visible output independently of DP20
While testing the DP20/DP51 interaction, we confirmed (with DP20
explicitly verified as true via a status query beforehand) that
setting the mode byte inside DP51 to 0x04 (no white bit, no colour
bit set) turns the light visibly off, on both the physical device and
in the official app, without DP20 changing at all. Restoring the
mode byte to 0x05/0x06 immediately restores the previous hue,
saturation and brightness, with no reset observed.
This clarifies the roles of the two power-related mechanisms:
DP20 is a hard power switch (device stays reachable/queryable when false, but this is the "master off" the app's own toggle uses).
mode controls which channel (if any) is actually producing light. 0x04 ("no channel selected") is a valid, visually-off state that
does not require touching DP20 at all.
This is relevant context for the design discussion below: a
two-entity model (separate white/colour lights sharing DP51) does not
need any additional "last channel off turns off the master" logic --
clearing both channel bits already produces a visibly-off device on
its own. DP20 remains a separate, independent hard on/off that a
client is free to also expose, but no companion-write mechanism is
needed to keep it in sync with the two channel entities.
Rather than replacing the existing fixed-byte format: mechanism,
this would be an optional encoder specifically for ASCII-encoded
payloads with variable field widths.
Whenever one of the listed fields changes, tuya-local would first
generate and send the companion DP (DP28), followed immediately by the
normal DP51 update.
This affects the write path (device.py / light.py) rather than only
the configuration parser, since one Home Assistant attribute update
would intentionally produce two device writes in the correct order.
Note that this mechanism is still needed for the DP28/DP51 brightness
pairing described above -- the mode/DP20 finding above only removes
the need for a separate companion write between the channel entities
and DP20, it does not remove the need for companion_write between
DP51's brightness fields and DP28.
Expected impact
Both proposed extensions are fully optional and therefore should not
affect existing device configurations.
live_format is relatively self-contained and primarily requires
changes to the parser/encoder.
companion_write is more invasive because it changes the write flow and
requires ordered multi-DP writes while preserving existing optimistic
state handling and caching behavior.
Alternative
The protocol is already implemented successfully in a standalone custom light platform (calex_halo/), where DP28 and DP51 are generated
directly.
That solution works well for this specific device, but it bypasses many
of tuya-local's advantages such as configuration management, HACS
distribution and community maintenance.
This RFC is intended to determine whether a generic solution would be
considered suitable before investing time in a complete implementation
and pull request.
Questions
Have you encountered similar "live update" companion DPs elsewhere
among the existing device configurations that could serve as a basis
for this design?
Does the proposed YAML syntax seem compatible with the current
architecture, or would another design fit better?
Would companion DP writes be considered acceptable architecturally,
or would this functionality be expected to live in device-specific
platform code instead?
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Support for "live update" companion DPs (e.g. DP28) in tuya-local
TL;DR: Some Tuya RGB+CCT lights require a second "live update" DP (here: DP28)
alongside the main composite state DP (DP51) for brightness changes to register
reliably — DP51 alone can leave the light stuck at a low brightness. DP28 uses
an ASCII hex format with variable, unpadded field widths that differs per
lighting mode, which tuya-local's current fixed-width format/mask system can't
generate. This RFC proposes two optional, additive extensions — a template-based
variable-width string encoder, and a "companion write" mechanism so that
updating one DP can trigger a paired write to another — and asks whether either
fits tuya-local's architecture before a full implementation/PR is attempted.
Problem
Some Tuya RGB+CCT lights (confirmed on the Calex Smart Halo,
product-id
f8bws9mp29vxwqoc) do not reliably accept brightnesschanges by updating only their composite state DP (DP51, a 12-byte
encoded structure containing mode, hue, saturation, RGB brightness,
white brightness and color temperature).
After extensive packet captures and protocol testing, the following
behavior was consistently observed:
repeatedly sends updates to DP28.
update containing the complete persistent state.
"stuck" at low values when increasing brightness again.
final DP51 commit) works reliably in both directions.
This strongly suggests that DP28 acts as a temporary "live update"
channel while DP51 represents the committed device state.
DP28 uses a payload format that cannot currently be expressed using
tuya-local's existing
format/maskmechanisms because it is an ASCIIhex string whose fields have variable widths and whose layout changes
depending on the active lighting mode.
From reverse engineering, the observed formats are:
White mode:
Colour mode:
These formats have been reproduced consistently across multiple packet
captures and repeated protocol tests.
Additional finding: the mode field controls visible output independently of DP20
While testing the DP20/DP51 interaction, we confirmed (with DP20
explicitly verified as
truevia a status query beforehand) thatsetting the mode byte inside DP51 to
0x04(no white bit, no colourbit set) turns the light visibly off, on both the physical device and
in the official app, without DP20 changing at all. Restoring the
mode byte to
0x05/0x06immediately restores the previous hue,saturation and brightness, with no reset observed.
This clarifies the roles of the two power-related mechanisms:
DP20is a hard power switch (device stays reachable/queryable whenfalse, but this is the "master off" the app's own toggle uses).modecontrols which channel (if any) is actually producing light.0x04("no channel selected") is a valid, visually-off state thatdoes not require touching DP20 at all.
This is relevant context for the design discussion below: a
two-entity model (separate white/colour lights sharing DP51) does not
need any additional "last channel off turns off the master" logic --
clearing both channel bits already produces a visibly-off device on
its own.
DP20remains a separate, independent hard on/off that aclient is free to also expose, but no companion-write mechanism is
needed to keep it in sync with the two channel entities.
Two possible generic extensions
1. Template-based variable-width string encoder
Example configuration:
Rather than replacing the existing fixed-byte
format:mechanism,this would be an optional encoder specifically for ASCII-encoded
payloads with variable field widths.
2. Companion DP write support
Example:
Whenever one of the listed fields changes, tuya-local would first
generate and send the companion DP (DP28), followed immediately by the
normal DP51 update.
This affects the write path (
device.py/light.py) rather than onlythe configuration parser, since one Home Assistant attribute update
would intentionally produce two device writes in the correct order.
Note that this mechanism is still needed for the DP28/DP51 brightness
pairing described above -- the mode/DP20 finding above only removes
the need for a separate companion write between the channel entities
and DP20, it does not remove the need for
companion_writebetweenDP51's brightness fields and DP28.
Expected impact
Both proposed extensions are fully optional and therefore should not
affect existing device configurations.
live_formatis relatively self-contained and primarily requireschanges to the parser/encoder.
companion_writeis more invasive because it changes the write flow andrequires ordered multi-DP writes while preserving existing optimistic
state handling and caching behavior.
Alternative
The protocol is already implemented successfully in a standalone custom
lightplatform (calex_halo/), where DP28 and DP51 are generateddirectly.
That solution works well for this specific device, but it bypasses many
of tuya-local's advantages such as configuration management, HACS
distribution and community maintenance.
This RFC is intended to determine whether a generic solution would be
considered suitable before investing time in a complete implementation
and pull request.
Questions
Have you encountered similar "live update" companion DPs elsewhere
among the existing device configurations that could serve as a basis
for this design?
Does the proposed YAML syntax seem compatible with the current
architecture, or would another design fit better?
Would companion DP writes be considered acceptable architecturally,
or would this functionality be expected to live in device-specific
platform code instead?
All reactions