Introducing e32wamb: firmware for esp32-c6 based White Ambiance light


e32wamb proto-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:

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

G delayed_save delayed_save light_config light_config delayed_save→light_config triggers save indicator_led indicator_led led_strip led_strip indicator_led→led_strip animates RGB led light_config→delayed_save notifies about update light_driver light_driver light_config→light_driver updates main main light_config→main provides endpoint config nvs nvs light_config→nvs persists data to light_driver→light_config gets data from ledc ledc light_driver→ledc drives main→light_config updates main→light_driver triggers effects scenes scenes main→scenes uses zigbee zigbee main→zigbee configures reset_button reset_button reset_button→indicator_led (un)locks gpio gpio reset_button→gpio polls scenes→light_config reads from scenes→light_config updates scenes→zigbee updates status_indicator status_indicator status_indicator→indicator_led updates status_indicator→zigbee polls data_tables data_tables data_tables→light_driver provides channel data zigbee→main calls back ledc→gpio pwm-drives led_strip→gpio drives the WS2812
e32wamb dependencies (interactive)

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:

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, OffWithEffect3 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:

  1. There is no OTA (which I alluded to in one of the sidenotes above)
  2. 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…

  1. 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.

  2. E.g. networks managed by Philips Hue bridge

  3. When you first make it work at all by adding the appropriate attributes; which is already done in the esp32-huello-world

  4. ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID callback invocations for the ESP_ZB_ZCL_ATTR_COLOR_CONTROL_COLOR_TEMPERATURE_ID attribute (of the Color Control cluster)

  5. See the delta for the individual channels in the LIGHT_DRIVER messages above

  6. To be handled in my copious free time…