Help with: ESPHome M5Stack byte switch leds

Some time ago I bought a M5Stack byte switch and hooked it up to M5Stack NanoC6 using the i2c port. Hooked it up to Home Assistant using ESPHome. Pretty neat! After some fooling around I got the switches working, but whatever I do I cant seem to get the leds working. In desperation I turned to AI (I know. The shame is mine to bear :disappointed_face:) and with this I have managed to get one of them working... sometimes.
Has any of you managed to get them all working consistently?

I’ve done a lot of other M5 devices in ESPHome, but don’t have a ByteSwitch. If you post your code, we might be able to help. –Preceded by backticks line, and followed by backticks line. Backticks line is a set of three backtick characters (just to left of ‘1’ key).

There’s generally ESPHome example code online somewhere for most M5 devices, either by M5Stack or other people, but I couldn’t find any for ByteSwitch devices.

Here is the code for just one of the leds. I apologize for the quality. I am close to the end of my skills.

output:
  - platform: template
    id: dummy_out0
    type: float
    write_action:
      - lambda: 'return;'

light:
  - platform: monochromatic
    name: "ByteSwitch LED 0"
    id: led0
    output: dummy_out0
    on_turn_on:
      then:
        - lambda: |-
            uint8_t mode_payload[3] = { 0x40, 0x00, 0xFF };
            auto err_mode = id(bus_i2c).write(0x46, mode_payload, 3, true);
            if (err_mode != 0) {
              ESP_LOGW("byteswitch", "Mode write failed (code %d)", static_cast<int>(err_mode));
            }
            uint8_t colour_payload[4] = { 0x40, 0xFF, 0xFF, 0xFF };
            auto err_col = id(bus_i2c).write(0x46, colour_payload, 4, true);
            if (err_col != 0) {
              ESP_LOGW("byteswitch", "LED0 colour write failed (code %d)", static_cast<int>(err_col));
            }

    # Optional: turn off the LED when the light is turned off
    on_turn_off:
      then:
        - lambda: |-
            uint8_t mode_payload[3] = { 0x00, 0x00, 0x00};
            id(bus_i2c).write(0x46, mode_payload, 3, true);

EDIT: Sorry after looking at the datasheet for the M5Stack byte switch you can't do this unfortunately. The WS2812C data pin is not directly exposed to the ESP. It goes through a STM32G031G8U6 microcontroller.

They are WS2812C RGB LEDs, don't use the monochromatic light, use this light component:

Then split it into 9 individual lights using this:

Light Partition - ESPHome - Smart Home Made Simple

I arrived at the same thing and I found this diagram in their documentation. However I cant decode it. The LEDs appear multiple times and I cant figure out what i'm supposed to write and to where :frowning:

Edit: Uploads full I2C protocol

So after banging my head against the proverbial wall for a long time I came up with this.
The code is for a single LED and can be replicated to the other 8 LEDs by changing the address written to.

Full code:

i2c:
  sda: GPIO2
  scl: GPIO1
  scan: true
  id: bus_i2c

output:
  - platform: template
    id: dummy_out0
    type: float
    write_action:
      - lambda: 'return;'

- platform: monochromatic
    name: "ByteSwitch LED 0"
    id: led0
    output: dummy_out0
    on_turn_on:
      then:
        - lambda: |-
            const uint8_t write_buf[4] = {0x40, 0x00, 0x00, 0xFF};
            auto err = id(bus_i2c).write(0x46, write_buf, sizeof(write_buf));
            if (err != 0) {
              ESP_LOGW("byteswitch", "LED set failed (code %d)",static_cast<int>(err));
            }
    on_turn_off:
      then:
        - lambda: |-
            uint8_t off_payload[4] = {0x40, 0x00, 0x00, 0x00};
            id(bus_i2c).write(0x46, off_payload, sizeof(brightness_payload), true);

Explination:
In the "i2c"-block the physical pins are defined.
The "output"-block is as it states some dummy stuff needed for the monochromatic platform that does the actual manipulation of the LED.
The "platform: monochromatic"-block is where it gets interesting. We start with some boilerplate stuff to set up the switch (this will be a toggle light on or light off in Home Assistant) and then we have a lambda expression.
Side note: This is actually inline C++ code which does the talking to the LED. From what I gather you could move this part to a separate file and just reference it here but that just seems like introducing the file-system into an already messy situation.
It is the two first lines of the code that does the work. First we create a buffer which contains a i2c-call which consists of two parts:

First the address of the LED that we want to manipulate. This is found in the table that I included in the above post. Here it seems only the addresses 0x20-0x40 are relevant. In this case I'm manipulating the LED8(address 0x40) which is the LED by the i2c connector.

The second part of the buffer is a set of bytes representing the hexadecimal representation of the colors we want to set. In this case we set the LED to red(0x00, 0x00, 0xFF). Note that the color-channels are in BGR-order (Blue, Green, Red)

Another side note: You can freely choose which LED to set by changing the address, for instance: LED3 is 0x2C as registers/cells controlling that LED starts at that address. The last digit in the address is show as the second row of the table in the other post.
If you wanted to just write to the green channel of LED3 you would write to 0x2D. Just remember to only write a single byte instead of the full three. As you might write into other LEDs.
Alternatively if you wanted to set LED0-LED3 to white at the same time you could just create a large buffer and write it to 0x20. Something like this: "{0x20, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF}". Note the padding in the form of 0x00 sent in between each set of BGR. (remember to change the size of the buffer) white_buf[4] -> write_buf[16]

Once we have created this buffer we wrap it in another buffer which handles the call to the switch array over the i2c connection. This outer buffer is fairly easy as it is just the address of the switch array microcontroller. This is defined in the second row of the table from my earlier post. Once all this is done the buffer-wrap is send using id(bus_i2c.write). The remaining part of the lambda expression is just to help with debugging.

I hope all this makes sense. If not feel free to reply and I shall do my very best to answer.