Introducing e32wamb: firmware for esp32-c6 based White Ambiance light
star of today’s show: proto-light running e32wamb
Introduction
This is the fifth post in the Reversing Philips Hue light driver series.
In the fourth part, I introduced a minimum firmware for the ESP32-C6.
Problem statement
While the esp32-huello-world
minimal firmware works OK, it leaves a lot to be desired implemented:
- Proper manufacturer / version info in
Basic
cluster - Proper cluster config for color-temperature light
- Proper handling of
StartUp*
values - Persistence of configured variables in flash (with delayed saves to avoid wearing out the flash prematurely)
- Scenes handling
- Status indicator (provisioning, connected (no coordinator), connected)
- Reset button
- Proper light driver with smooth(er) transitions
- Proper handling of
Level
andOnOff
options - Proper handling of
Identify
cluster (Trigger effect
)
This post introduces vastly improved firmware that fills (some of) the gaps.
One notably absent feature is support for OTA1.
Solution
To go straight for the kill: GH/wejn/e32wamb github repo has it all, under GPLv3 license.
The rest of the post goes over the architecture and some hurdles along the way. As well as some known issues with the firmware.
Architecture diagram
Issues during implementation
During the implementation, there were a few hurdles:
Proper tracking of build timestamps and versions
I mentioned this already in the How-To: timestamp and git-hash version esp32 firmware builds post.
The obvious downside is that now every idf.py build
also reconfigures
the whole project, which makes it a tad slower. But the consistent build
labels and date codes are worth it, IMO.
Figuring out “connected” status
Figuring out whether the light is connected to a coordinator was a relatively tough task.
For one, in distributed zigbee networks2, there might not be a coordinator
present at the 0x0000
short address.
There’s even How do I get the current connection state esp-zigbee-sdk issue about this open, into which I posted, too.
In the end, I chose two criteria to determine the connection status:
- either there’s a node with coordinator flag present in the neighbor table
(using
esp_zb_nwk_get_next_neighbor
api call) - or there was a recent
Read attributes
call to our light endpoint
For the latter, I asked via esp-zigbee-sdk #597
issue and was pointed to esp_zb_raw_command_handler_register()
which is indeed
useful for obtaining this data.
Intercepting OffWithEffect
data
By default, OffWithEffect
3 simply calls ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID
to set the On/Off
attribute to 0
. Insta off, no tears or apologies.
That is not wanted, so – again – I asked around
on esp-zigbee-sdk #596
and in the end figured out a relatively simple solution using (again) esp_zb_raw_command_handler_register()
.
Testing how the ledc fading works
Directly transitioning from one temperature to the other – while functional – isn’t very pleasant, I needed to implement fading.
I wasn’t exactly sure how the fading works.
Several tests on GH/wejn/esp32-ledc-fade-test later, I figured out how to drive the ledc fading. And the important fact that it blocks even in “non-blocking” mode if there’s already previous fading in progress.
Even funnier is that I managed to find an assert fail in the
ledc_fade_stop()
. That
is now fixed.
Outstanding (known) issues
There are also two known issues with the firmware:
- There is no OTA (which I alluded to in one of the sidenotes above)
- Transitions (fades) aren’t γ-corrected
Fades not gamma-corrected
The transitions (fades) not being γ-corrected is kind of a big deal.
Let’s say that the light is at 153 mired (6536 K), and you send it the
Move to color temperature
command to go to 454 mired (2203 K) in 0.4s
(the way Philips Hue App does it).
What ends up happening is that esp-zigbee-sdk
internally splits that to 5
“set temperature” calls4, spaced ~100 ms apart:
I (64085) MAIN: Light temperature change to 153
I (64085) LIGHT_DRIVER: Set to 0.0960, 1.0000, 0.0000 (o/l/t: [1, 254, 153], t: 100)
I (64195) MAIN: Light temperature change to 228
I (64195) LIGHT_DRIVER: Set to 0.7661, 1.0000, 0.5593 (o/l/t: [1, 254, 228], t: 100)
I (64305) MAIN: Light temperature change to 303
I (64305) LIGHT_DRIVER: Set to 0.7110, 0.4727, 1.0000 (o/l/t: [1, 254, 303], t: 100)
I (64415) MAIN: Light temperature change to 378
I (64415) LIGHT_DRIVER: Set to 0.3005, 0.1253, 1.0000 (o/l/t: [1, 254, 378], t: 100)
I (64525) MAIN: Light temperature change to 454
I (64525) LIGHT_DRIVER: Set to 0.0000, 0.0000, 1.0000 (o/l/t: [1, 254, 454], t: 100)
Each of those calls are executed by the light_driver
as a fade from current value
to the target.
But because this isn’t γ-corrected, and the changes can be rather huge5, the fades are actually kinda wonky.
It would be much better to split the individual fades to smaller intervals (say,
10-20 ms each) and climb the intermediate values based on data_tables
.
That is a problem for the future me6. ;)
Closing words
This was a relatively long journey – some 50+ commits so far – but now I have a reasonably well functioning firmware for the zigbee light.
Next up is turning the proto-PCB (pictured above) into a final PCB, with DC-DC converter, and all that jazz.
And that PCB should be the last part of this project…
-
Over-the-Air firmware updates. Mainly because I’m not sure how to support OTA – for 3rd party devices – from Hue bridge, so I’ll leave this for a later date. ↩
-
E.g. networks managed by Philips Hue bridge ↩
-
When you first make it work at all by adding the appropriate attributes; which is already done in the
esp32-huello-world
↩ -
ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID
callback invocations for theESP_ZB_ZCL_ATTR_COLOR_CONTROL_COLOR_TEMPERATURE_ID
attribute (of the Color Control cluster) ↩ -
See the delta for the individual channels in the
LIGHT_DRIVER
messages above ↩ -
To be handled in my copious free time… ↩