From the hass repo and cross-referencing with my reverse engineering, the H7015 seems to be most similar to H7021
The BLE device name is of the form Govee_MODEL_MAC2, where MODEL is the model name (eg H7015) and MAC2 is the last 2 bytes of the MAC address.
The first byte of any message sent to the SEND characteristic is the main command followed by its payload. All messages sent or received are zero-padded to 19 bytes followed by a single XOR checksum byte without exception.
The Govee firmware uses a register-based design.
aa
reads from a register, which may include sub-register addressing. If the given register isn't a sub-register, all remaining bytes are ignored. For valid register addresses, it responds on the RECV characteristic with an ACK (aa
+ register address) followed by the contents of the (sub)register.
33
writes to a register. Some registers are specially mapped to have "pseudo-registers" which can't be addressed by aa
but have special handling when written. Some are also normalized to a range, and successful writes are not a guarantee that the firmware recognizes those values.1 For valid register addresses (regardless of validation), it responds on the RECV characteristic with an ACK (33
+ register address). The contents are not included and must be read if needed.
a3
is a multi-packet write command used to send scene parameters. All packets are prefixed by a3
and their packet number. The first packet has the prefix followed by 01 pp 02
where pp
is the number of packets including the start and end packets. The rest of the first packet is the first 14 bytes of data. Subsequent packets only have the prefix and 17 bytes of data. The last packet has a packet number of ff
and the remaining data, zero-padded. If successful, the firmware will respond with an ACK of a302
. Some edge cases:
- The data is 14 byte or less, unknown. It's possible scene param encoding ensures this isn't possible.
(len(data) - 14) % 17 == 0
, unknown. Does the final packet have the last 17 bytes, or is it entirely 0s? It's possible the firmware doesn't care either way.
a5
audio mode: unknown specification. TODO.
This is the map of registers I've found so far. The register address is in hex, and the contents are in hex unless otherwise noted. Some of this is from the snoop log while others are from fuzzing commands like r/00-ff
to see what responds.
01
on/off state (also used for keepalive)01
is "on", all other values are "off", normalized to00
04
dimmer (in percent)- Note: 0% is not off, it's just very dim.
05
mode04
scene- All of these were found through poking registers, they may be invalid states of the firmware.
0100
- rotates between blue, white, and purple
0300
hue rotate0400
sky blue0500
reddish orange0700
???- slightly red-orange with param brightness (5 sec)
- suddenly does hue rotation at max brightness for (3 sec)
0800
blinking- blinks with hue rotation
0900
candlelight- flickers between soft red-orange and yellow-orange
0f00
freezeframe- stops any animation, param ignored
0a00
soft pulsate once per hue, rotates- param = brightness
1000
quick discrete hue rotation1500
seizure flash between blue and white15ff
twinkles between soft pink and blue1600
chaser rotating between blue and pur3ple hues2300
bright slow pulsating between blue and white, reminds me of ice3f00
illumination4000
chaser with rotating hues4100
chaser with red and blue on right, purple on left, toward centerxxxx
- Whatever is currently in the color buffer. This allows the addition of extra custom scenes by loading their data via scene params and referring to them in-app by the otherwise undefined scene id.
05
"mic mode"- Write
330505 01
to enable - All subsequent writes use
00
followed by 2 bytes - From the app, either both bytes are the same value or one is
00
00xx
causes dimming using the full00-ff
range instead of the usual percentage
- Write
15
segments- acts like a write-only pseudo-register
- subsequent reads of
05
show only15
- writes start with a command byte:
01 rrggbb ccccrrggbb sstt
if cccc == 0
:- first rgb = color
else
:- first rgb =
ffffff
cccc
= BIG-endian color temperature, 2000-8800 Krrggbb
= equivalent rgb color
- first rgb =
02 bb sstt
bb
= brightness (%)- note: per-segment brightness works in addition to
04
dimmer
sstt
= little-endian segment selection bitmap
06
= "312e30302e3134" = "1.00.14"- readonly firmware version
07
"version"?02
= "881a353239d3ea86" = MAC +ea86
? extra bytes uncertain.- extra bytes may relate to the product model, but I don't know how H7015 would be encoded here.
03
= eg "3.01.01", HW version (in app)04
= eg "1.00.14", FW version again06
= eg "881a353239d3" = MAC address in little-endian order (reversed)
0e
= restart (write to restart, may retain as restart reason?)01
= power removed
0f
= "01" unknown, writing valid value is retained while invalid is ignored01
,02
are valid, no observable effect
11
= sleeping settings- Turning on sets dimmer to initial brightness and decreases over duration
xx
on/off (00=off, 01=on)bb
initial brightness (%)mm
duration (minutes, max 240)mm
duration (minutes, duplicated?)
12
= wake-up settingsxx
on/off (00
=off,01
=on)bb
final brightness (percent)hh
wake-up hourmm
wake-up minuterr
repeat bitmask- LSB = Monday, days in-order, MSB = immediate?
dd
duration (minutes)
23
= alarm settingsxx
alarm index (00-03
)yy
alarm state (00
=off,80
=on) - more particularly, the MSB other bits unused.0000
??
setting flag? set to80
sometimes
40
= "001e" ignores writes41
= power-off memory- "If you enable this function, after the device is powered off and on again, it will be restored to the state before the power off."
00
= off01
= on- Retains written value
00:ff
a3
= ""- 1 byte
00:01
, unknown effect
- 1 byte
a5
color buffer?- All sub-registers are 12 bytes long, almost always 3 units of 1 brightness (%) byte with 3 rgb bytes.
xx010101
is used as an "undefined" color and fills the unused subregisters- The buffer isn't typically cleared when the mode changes, relevant bytes are just overwritten.
00
sub-register seems to be special-use00
=?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? tttt
tttt
= little-endian color temperature in mode15
cccc != 0
- Segment block
01
segments 1-302
segments 4-603
segments 7-904
segments 10-1205
segments 13-15
- scene param colors?
a531+4
up toa59b+0
seem to be color data
- scene param brightness?
a59b+4
up to (unknown) seem to be brightness data, where every 16 bytes is a single brightness byte.
ee
= ""r ff
="" => read (subregister?) "pauses" animation, suggests FW bug.- write no effect, always ""
ef
= "000101"- always "000101" no matter what is written
ff
= ""- WARNING: Poking around in this register has caused my device to softlock and require a power cycle. It's possible it was never intended to be used. These notes are incomplete.
01
= resume02
= pause, if scene set the animation plays as fast as possible (seems to crash, doesn't respond to commands )03
= pulses red05
= set to this when animation is playing? writing this pauses animationxx
= pause animation
curl "https://app2.govee.com/appsku/v1/light-effect-libraries?sku=H7015" -H 'AppVersion: 5.6.01' -s > H7015.json
From this thread allows downloading scene data dumps from the govee REST API, which is probably how the hass repo got their data.
Footnotes
-
For example, the power register (
01
) is normalized to00
for off and01
for on. Writing02
will turn it off as if you had written00
. Values outside this range are not preserved. ↩