MCPcopy Index your code
hub / github.com/8none1/zengge_lednetwf

github.com/8none1/zengge_lednetwf @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
24 symbols 58 edges 6 files 4 documented · 17%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Important news

This repo is now retired. All new reverse engineering and support work is being done in the Home Assistant integration which you can find here: https://github.com/8none1/lednetwf_ble

Zengge LEDnet WF Bluetooth LE reverse engineering

Might also be known as: - Zengge LEDnetWF - YBCRG-RGBWW - Magic Hue - Bluetooth full colors selfie ring light

There are other ZENGGE devices with similar names. For example, there are small black USB Bluetooth LED controllers bundled with light strips, which can be used with any WS2812B lights with 3-pin connector. They show up as "LEDnetWF0200A3" plus the last six digits of their MAC. While the app uses different commands than those below, they will still accept some of them (namely on/off, HSL colors and symphony, but with only 100 effects).

Home Assistant Integration

We've got a pretty decent integration going, and it's getting updated fairly regularly. Contributions very welcome. https://github.com/8none1/lednetwf_ble

Background

I bought one of these neat looking RGB WW ring lamp things off Ali Express:

image

It has a Bluetooth LE controller and I want to be able to control it myself from code, not from within the app. It also has an mini remote control which seems to be RF not IR, despite looking like an IR. The app is called "Zengge" but also seems to be branded Magic Hue in a few places.

I'm going to try and reverse engineer the BLE protocol and see if I can make it work. It is probably worth trying to decompile the Android app as well and see if any insights can be gained there. The option of last resort is to replace the entire controller with something more friendly, like WLED on an ESP8266.

image

If you're interested in helping out, please get in touch. If you know of someone who has already worked this out let me know.

Latest news

2024-03-31

I got a new version of this controller which has a later firmware version. There are some small changes in the protocol between these versions, and I'm working them out. This also requires support for changing the LED colour ordering, chip type, number of LEDs etc. This has been worked out and decoded below.

Also I have spent some time looking at the advertising packets which they send. This contains information about the power state, current colour etc without any kind of pairing required. The data is just broadcast. I'm working on integrating this in to the Home Assistant integration as well.

In summary, this is still an active project and contributions are welcome both here to document the protocol and on the Home Assistant integration listed above.

2022-11-06

Most of the features are now supported. With the decoded protocols you can: - Set single static colours - Select from any of the built in modes and alter the speed and brightness - Create your own pixel by pixel patterns, called "smear" in the app.

Here's a demo of stepping through those features:

https://user-images.githubusercontent.com/6552931/200186465-590ff263-4d1e-49c9-acda-37d8344e04e2.mp4

There are still some things that need doing: - How does it describe the number of LEDs it has? (I have some theories already about this, just need to test them). Theory tested, it was wrong. I think they need you to tell them.

2022-11-05

I can command this thing from Python!

2022-11-04

It turns out that once you've connected to the device, set the MTU and enabled notifications then the packet counter and checksum ARE COMPLETELY IGNORED. Yup.
You can read the whole horrific story in the scratch notes file. A very rough, unfinished, and at this point abandoned attempt to reverse engineering the protocol exists in the file encoder.py. It rather looks like we don't need any of that at the moment. Tomorrow I will craft some bash scripts to really shake down what we can do with this discovery.

Process

I used HCI logging from the debug menu in Android to capture the packets going from the app to the device. It must be enabled before enabling Bluetooth. I then copied those to my main machine using adb pull sdcard/btsnoop_hci.log <local filename>. There are many logs captured in the btsnoop_logs directory if you want to have a look. Depending on your phone, there might be easier ways to retrieve the log. For example, Xiaomi users can just dial *#*#284#*#* to make copies of the two most recent logs spawn in the user-accessible files (the exact location depends on the device). I also decompiled the Zengee app in to Smali code and made an attempt to reverse engineer the "encryption" and checksum routines. However, I abandoned this work when I discovered that you don't need to concern yourself with things like making sure the checksums are correct, because the device doesn't care.

Some tips for doing this sort of HCI log reverse engineering: - Walking through the app pressing each button multiple times makes it much easier to spot where you moved on from one feature to the next. - Keep the number of multiple presses the same throughout your logging. I pressed each thing five times. - Wireshark can import the log files directly - Setting a Wireshark filter of (btatt or btgatt) && btatt.opcode.method==0x12 allows you to filter only writes from Android to the device. - Open a packet up in the bottom pane, clicking through to Bluetooth Attribute Protocol -> Value, right click and choose "Apply as column". This will allow you to see the bytes being sent to the device - Export write packets to a new file (or overwrite): Select all, export specified packets, export as pcapng Selected packets only. - Install tshark and: tshark -r <filename.pcapng -T fields -e btatt.value - That will dump out only the bytes with all the other BT headers removed. Much easier than copy and pasting from Wireshark - Assume that the original developers of the protocol were in a rush. If it seems the obvious way to do something, it probably is. - Try using some simple replay tests using gatttool to send the bytes you found with tshark. Can you make it do things by just sending the same packet again? e.g. switch the device to green and send a red packet, does it work? If so, then things like packet counters are likely ignored. Do this early, you will save a lot of time if you don't have to reversed engineer encryption and checksums.

Ok, on to the actual information.

Protocol

Header

These are common to all payloads and many (if not all) Zengge devices. - The first byte is used for per-command flags. 0x40 means that the command is fragmented due to length. The flag is set for every fragment. You need at least 56 to 57 LEDs (depending on the device) for this to happen. As this ring light only has 48 LEDs, commands will never need to be fragmented. - After this comes the counter which increments after every command (so it's the same for all fragments of one command). It starts at 1 and can roll over. It is generally ignored but used to tell which fragments belong together. - The next byte is per-fragment flags. 0x80 means that this is the last (or only) fragment. - This is the fragment counter. It starts with 0 for the first or only fragment of a command. - Now comes the only word (big endian) in this header. It contains to the total payload of all fragments' length, not counting the following byte and the checksum. This word is only present in the first or only fragment. - The next byte represents the number of bytes to the end of the payload including the last checksum byte. For single-fragment commands, this is therefore one more than the previous word. - Except for some initialisation commands, the next byte is always 0x0b. It is only present in the first or only fragment. - The last byte is a checksum. It is simply the sum of all bytes after the header, not counting the checksum itself. It is generally ignored. Some commands even omit it. This means that the header (without the checksum) of the first fragment is 8 bytes long, but only 5 for follow-up fragments.

Power control

Example bytes ON: 00 04 80 00 00 0d 0e 0b 3b 23 00 00 00 00 00 00 00 32 00 00 90

Example bytes OFF: 00 5b 80 00 00 0d 0e 0b 3b 24 00 00 00 00 00 00 00 32 00 00 91

# checksum is the last byte of the packet seems to be sum of bytes from the `3b` not inc. checksum -----v
# unknown -------------------------------------------------------------------------------v------------v |
# Likely to be colour information per HSV/WW packets ---------------------v-----------v  |            | |
# on = 0x23 off = 0x24 ------------------------------------------------v  |           |  |            | |
# unknown -------------- ----------------------------------------v--v  |  |           |  |            | |
# length of packet from here to the end including checksum ---v  |  |  |  |           |  |            | |
# one less than the length ------- ------------------------v  |  |  |  |  |           |  |            | |
# standard header --------------------------------v--v--v  |  |  |  |  |  |           |  |            | |
# counter ----------------------------------v--v  |  |  |  |  |  |  |  |  |           |  |            | |
                                            00 04 80 00 00 0d 0e 0b 3b 23 00 00 00 00 00 00 00 32 00 00 90

RGB colour handling

Ring Light / Firmware 0x53

The device expects basic static colour information in HSV format. The value for the Hue element is divided by two to fit in to a single byte. Saturation and Value are percentages from 0 to 100 (0x64). White colours are represented by colour temperature percentage from 0x0 to 0x64 from warm to cool. Warm (0x0) is only the warm white LED, cool (0x64) is only the white LED and then a mixture between the two. Brightness is a percentage.

I assume that HSV colours and white colours are mutually exclusive, but I haven't tried to do both at the same time.

# checksum ---------------------------------------------------------------------------------------------v
# White temperature and brightness ------------------------------------------------v--v                 |
# HSV colour data --------------------------------------------------------v--v--v  |  |                 |
# ?                      ----------------------------------------v        |     |  |  |                 |
# length of packet from here to the end including checksum ---v  |        |     |  |  |                 |
# length of packet from here without the checksum? --------v  |  |        |     |  |  |                 |
# standard header --------------------------------v--v--v  |  |  |        |     |  |  |                 |
# counter ----------------------------------v--v  |  |  |  |  |  |        |     |  |  |                 |
                                            00 05 80 00 00 0d 0e 0b 3b a1 00 64 64 00 00 00 00 00 00 00 00   # bytes 10, 11, 12
                                            00 10 80 00 00 0d 0e 0b 3b b1 00 00 00 1b 36 00 00 00 00 00 3d   # bytes 13 & 14 

Strip Lights / Firmware 0x56

These lights have some "static" effects which are configured through these packets as well. They are not static though, they move! Mode 2 is actually static.

checksum ------------------------------------------------------------------------------------v
00 f0 ---------------------------------------------------------------------------------v---v |
Direction 0 -> 1 -------------------------------------------------------------------v  |   | |
speed ---------------------------------------------------------------------------v  |  |   | |
background colour ------------------------------------------------------v------v |  |  |   | |
R,G,B ---------------------------------------------------------v------v |      | |  |  |   | |
Fixed mode 1 -> 7 ------------------------------------------v  |      | |      | |  |  |   | |
41 ------------------------------------------------------v  |  |      | |      | |  |  |   | |
0b ---------------------------------------------------v  |  |  |      | |      | |  |  |   | |
length ------------------------------------------v--v |  |  |  |      | |      | |  |  |   | |
header -----------------------------------v----v |  | |  |  |  |      | |      | |  |  |   | |
counter -----------------------------v--v |    | |  | || || || |      | || || || || || || || ||
                                     0005 800000 0d0e 0b 41 02 ff 00 00 00 00 00 32 00 00 f0 64
                                     0 1  2 3 4  5 6  7  8  9  10 11 12 13 14 15 16 17 18 19 20
                                 001c 800000 0d0e 0b 41 09 ff ff ff 00 00 00 64 00 00 f0 9b
                                 001b 800000 0d0e 0b 41 09 ff ff ff 00 00 00 64 01 00 f0 9c

Symphony / Rmb

This is what the app calls modes / effects.

Ring Light / Firmware 0x53

There are a number (113 in the app) of effects. They are be numbered serially from 0x01 to 0x71. These packets do not have a checksum it seems, and they use a different format to the RGB and white colour setting payloads. Nevertheless they are fairly easy to understand. They take the form of:

```

Brightness 1 - 0x64 --------------------------------------------v

Speed 1 - 0x64 ----------------------------------------------v |

effect number (1 - 0x71) ---------------------------------v | |

? --------------------------------------------------v--v | | |

length of packets from here to the end ----------v | | | | |

length of packets minus 1 --------------------v | | | | | |

Standard header ---------------------v--v--v | | | | | | |

counter -----------------------v--v | | | | | | | | | |

Core symbols most depended-on inside this repo

Shape

Function 24

Languages

Python100%

Modules by API surface

ledwf_controller.py17 symbols
encoder.py6 symbols
service_discovery.py1 symbols

For agents

$ claude mcp add zengge_lednetwf \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page