v1.0 - Agent-Optimized Technical Specification
This document provides comprehensive technical specifications for implementing a Flipper Zero application to control Govee BLE-enabled LED devices, with focus on H6006, H6160, H6163, and related models. The implementation leverages Flipper's BLE capabilities to act as a central device, connecting to and controlling multiple Govee peripherals simultaneously.
// Generic Access Service
#define GOVEE_SERVICE_GAP 0x1800
// Generic Attribute Service
#define GOVEE_SERVICE_GATT 0x1801
// Govee Control Service
#define GOVEE_SERVICE_CONTROL 0x000102030405060708090a0b0c0d1910
// Alternative Service (some models)
#define GOVEE_SERVICE_ALT 0x02f0000000000000000000000000fe00// Primary control characteristic (write)
#define GOVEE_CHAR_CONTROL_WRITE 0x000102030405060708090a0b0c0d2b10
// Status characteristic (read/notify)
#define GOVEE_CHAR_STATUS_READ 0x000102030405060708090a0b0c0d2b11
// Alternative characteristics
#define GOVEE_CHAR_ALT_WRITE 0x02f0000000000000000000000000ff02
#define GOVEE_CHAR_ALT_READ 0x02f0000000000000000000000000ff03All Govee BLE packets follow a 20-byte structure:
[HEADER][CMD][DATA.....................][CHECKSUM]
0x33 0xXX [17 bytes of data] XOR
- Byte 0: Header (0x33 for commands, 0xAA for keep-alive, 0xA3 for gradient)
- Byte 1: Command type
- Bytes 2-18: Command-specific data (padded with 0x00)
- Byte 19: XOR checksum of bytes 0-18
// Power ON
uint8_t power_on[] = {
0x33, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x33 // XOR checksum
};
// Power OFF
uint8_t power_off[] = {
0x33, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x32 // XOR checksum
};// Set brightness (0x00-0xFE, where 0xFE = 100%)
uint8_t set_brightness(uint8_t level) {
uint8_t packet[20] = {0x33, 0x04, level};
packet[19] = calculate_checksum(packet);
return packet;
}// RGB color mode
typedef struct {
uint8_t header; // 0x33
uint8_t cmd; // 0x05
uint8_t mode; // 0x02 (manual), 0x01 (music), 0x04 (scene)
uint8_t red; // 0x00-0xFF
uint8_t green; // 0x00-0xFF
uint8_t blue; // 0x00-0xFF
uint8_t padding[13];
uint8_t checksum;
} govee_color_packet_t;// Keep-alive packet (sent every 2 seconds)
uint8_t keep_alive[] = {
0xAA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xAB
};- Supports RGBWW (RGB + Warm White + Cool White)
- Additional white channel control via byte 6
- Color temperature control: 2000K-9000K
- Supports gradient/segment control
- Uses 0xA3 header for multi-segment commands
- Extended packet protocol for effects
# Clone firmware with submodules
git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git
cd flipperzero-firmware
# Install uFBT (micro Flipper Build Tool)
python3 -m pip install --upgrade ufbt
# Setup VSCode integration
ufbt vscode_distgovee_control/
├── application.fam # App manifest
├── govee_control.c # Main entry point
├── ble/
│ ├── ble_manager.c # BLE connection management
│ ├── ble_manager.h
│ ├── ble_scanner.c # Device discovery
│ └── ble_scanner.h
├── devices/
│ ├── device_registry.c # Device abstraction layer
│ ├── device_registry.h
│ ├── govee_h6006.c # Model-specific drivers
│ ├── govee_h6160.c
│ └── govee_h6163.c
├── protocol/
│ ├── govee_protocol.c # Protocol implementation
│ ├── govee_protocol.h
│ └── packet_builder.c # Packet construction
├── ui/
│ ├── views/
│ │ ├── device_list.c
│ │ ├── color_picker.c
│ │ └── scene_editor.c
│ └── govee_ui.c
└── storage/
├── config_storage.c
└── scene_storage.c
App(
appid="govee_control",
name="Govee LED Control",
apptype=FlipperAppType.EXTERNAL,
entry_point="govee_control_app",
cdefines=["APP_GOVEE_CONTROL"],
requires=[
"gui",
"storage",
"bt",
],
stack_size=4 * 1024,
fap_category="GPIO",
fap_icon="govee_icon.png",
fap_icon_assets="icons",
fap_author="FlipperGovee",
fap_version="1.0",
fap_description="Control Govee BLE LED devices",
)#pragma once
#include <furi.h>
#include <furi_hal_bt.h>
#include <extra_beacon.h>
#include <bt/bt_service/bt.h>
#include <gui/gui.h>
#define MAX_GOVEE_DEVICES 10
#define GOVEE_PACKET_SIZE 20
typedef enum {
GoveeStateDisconnected,
GoveeStateScanning,
GoveeStateConnecting,
GoveeStateConnected,
GoveeStateError
} GoveeConnectionState;
typedef struct {
uint8_t address[6];
char name[32];
int8_t rssi;
bool is_connected;
uint16_t control_handle;
GoveeConnectionState state;
} GoveeDevice;
typedef struct {
GoveeDevice devices[MAX_GOVEE_DEVICES];
uint8_t device_count;
FuriTimer* keep_alive_timer;
FuriMutex* mutex;
} GoveeBleManager;
// Core functions
GoveeBleManager* govee_ble_manager_alloc(void);
void govee_ble_manager_free(GoveeBleManager* manager);
bool govee_ble_start_scan(GoveeBleManager* manager);
bool govee_ble_stop_scan(GoveeBleManager* manager);
bool govee_ble_connect(GoveeBleManager* manager, uint8_t device_index);
bool govee_ble_disconnect(GoveeBleManager* manager, uint8_t device_index);
bool govee_ble_send_command(GoveeBleManager* manager, uint8_t device_index, uint8_t* packet);#include "ble_scanner.h"
#include <furi_hal_bt.h>
#define SCAN_TIMEOUT_MS 5000
#define GOVEE_MANUFACTURER_ID 0x0001 // Verify actual ID
typedef struct {
GoveeBleManager* manager;
FuriTimer* scan_timer;
bool is_scanning;
} GoveeScanner;
static void govee_scan_callback(const GapScanEvent* event, void* context) {
GoveeScanner* scanner = context;
if(event->type == GapScanEventTypeAdvReport) {
// Check if device is Govee
if(govee_is_valid_device(event->data, event->size)) {
// Extract device info
GoveeDevice device = {0};
memcpy(device.address, event->address, 6);
device.rssi = event->rssi;
// Parse advertisement data for name
govee_parse_adv_data(event->data, event->size, &device);
// Add to manager
govee_add_device(scanner->manager, &device);
}
}
}
bool govee_start_discovery(GoveeBleManager* manager) {
// Configure GAP for scanning
gap_set_scan_callback(govee_scan_callback, manager);
// Start scanning with parameters
GapScanParams params = {
.type = GapScanTypeActive,
.interval = 0x50, // 50ms
.window = 0x30, // 30ms
};
return gap_start_scan(¶ms);
}#include "govee_protocol.h"
uint8_t govee_calculate_checksum(const uint8_t* packet) {
uint8_t checksum = 0;
for(int i = 0; i < GOVEE_PACKET_SIZE - 1; i++) {
checksum ^= packet[i];
}
return checksum;
}
void govee_build_power_packet(uint8_t* packet, bool on) {
memset(packet, 0, GOVEE_PACKET_SIZE);
packet[0] = 0x33;
packet[1] = 0x01;
packet[2] = on ? 0x01 : 0x00;
packet[19] = govee_calculate_checksum(packet);
}
void govee_build_color_packet(uint8_t* packet, uint8_t r, uint8_t g, uint8_t b) {
memset(packet, 0, GOVEE_PACKET_SIZE);
packet[0] = 0x33;
packet[1] = 0x05;
packet[2] = 0x02; // Manual mode
packet[3] = r;
packet[4] = g;
packet[5] = b;
packet[19] = govee_calculate_checksum(packet);
}
void govee_build_brightness_packet(uint8_t* packet, uint8_t level) {
memset(packet, 0, GOVEE_PACKET_SIZE);
packet[0] = 0x33;
packet[1] = 0x04;
packet[2] = (level * 0xFE) / 100; // Convert percentage to 0x00-0xFE
packet[19] = govee_calculate_checksum(packet);
}typedef struct {
View* view;
GoveeBleManager* ble_manager;
uint8_t selected_device;
uint8_t selected_menu_item;
} GoveeApp;
static void govee_draw_callback(Canvas* canvas, void* context) {
GoveeApp* app = context;
canvas_clear(canvas);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 2, 10, "Govee Control");
// Draw device list
canvas_set_font(canvas, FontSecondary);
for(int i = 0; i < app->ble_manager->device_count; i++) {
GoveeDevice* device = &app->ble_manager->devices[i];
char line[64];
snprintf(line, sizeof(line), "%s %s %ddBm",
i == app->selected_device ? ">" : " ",
device->name,
device->rssi);
canvas_draw_str(canvas, 4, 20 + (i * 10), line);
}
}
static bool govee_input_callback(InputEvent* event, void* context) {
GoveeApp* app = context;
if(event->type == InputTypeShort) {
switch(event->key) {
case InputKeyUp:
if(app->selected_device > 0) app->selected_device--;
return true;
case InputKeyDown:
if(app->selected_device < app->ble_manager->device_count - 1) {
app->selected_device++;
}
return true;
case InputKeyOk:
govee_ble_connect(app->ble_manager, app->selected_device);
return true;
case InputKeyBack:
return false; // Exit app
default:
break;
}
}
return false;
}typedef struct {
GoveeDevice* devices[MAX_GROUP_DEVICES];
uint8_t device_count;
char group_name[32];
FuriMutex* sync_mutex;
} GoveeDeviceGroup;
bool govee_group_send_command(GoveeDeviceGroup* group, uint8_t* packet) {
furi_mutex_acquire(group->sync_mutex, FuriWaitForever);
// Send to all devices with minimal delay
for(int i = 0; i < group->device_count; i++) {
govee_ble_send_command(group->devices[i], packet);
furi_delay_us(500); // 500μs inter-device delay
}
furi_mutex_release(group->sync_mutex);
return true;
}typedef struct {
char name[32];
uint8_t color_r;
uint8_t color_g;
uint8_t color_b;
uint8_t brightness;
uint16_t transition_ms;
} GoveeScene;
typedef struct {
GoveeScene* scenes;
uint8_t scene_count;
uint8_t current_scene;
FuriTimer* transition_timer;
} GoveeSceneEngine;
void govee_scene_apply(GoveeSceneEngine* engine, GoveeBleManager* manager,
uint8_t scene_index) {
GoveeScene* scene = &engine->scenes[scene_index];
// Build packets
uint8_t color_packet[20];
uint8_t brightness_packet[20];
govee_build_color_packet(color_packet, scene->color_r,
scene->color_g, scene->color_b);
govee_build_brightness_packet(brightness_packet, scene->brightness);
// Send to all connected devices
for(int i = 0; i < manager->device_count; i++) {
if(manager->devices[i].is_connected) {
govee_ble_send_command(manager, i, brightness_packet);
furi_delay_ms(10);
govee_ble_send_command(manager, i, color_packet);
}
}
}typedef enum {
GoveeEffectFade,
GoveeEffectPulse,
GoveeEffectRainbow,
GoveeEffectStrobe,
GoveeEffectCustom
} GoveeEffectType;
typedef struct {
GoveeEffectType type;
uint32_t speed_ms;
uint8_t intensity;
bool loop;
} GoveeEffect;
void govee_effect_rainbow(GoveeBleManager* manager, uint32_t speed_ms) {
float hue = 0.0;
uint8_t packet[20];
while(true) {
// Convert HSV to RGB
uint8_t r, g, b;
hsv_to_rgb(hue, 1.0, 1.0, &r, &g, &b);
// Build and send packet
govee_build_color_packet(packet, r, g, b);
govee_group_send_command(manager, packet);
// Update hue
hue += 1.0;
if(hue >= 360.0) hue = 0.0;
furi_delay_ms(speed_ms);
}
}#define GOVEE_CONFIG_FILE "/ext/apps_data/govee_control/config.dat"
typedef struct {
uint32_t version;
uint8_t saved_devices_count;
struct {
uint8_t address[6];
char name[32];
uint8_t last_r;
uint8_t last_g;
uint8_t last_b;
uint8_t last_brightness;
} saved_devices[MAX_GOVEE_DEVICES];
uint8_t default_brightness;
bool auto_connect;
bool keep_alive_enabled;
} GoveeConfig;
bool govee_config_save(GoveeConfig* config) {
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(storage);
bool success = storage_file_open(file, GOVEE_CONFIG_FILE,
FSAM_WRITE, FSOM_CREATE_ALWAYS);
if(success) {
success = storage_file_write(file, config, sizeof(GoveeConfig))
== sizeof(GoveeConfig);
storage_file_close(file);
}
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
return success;
}# Build the FAP
./fbt fap_govee_control
# Build and upload to Flipper
./fbt launch APPSRC=applications_user/govee_control
# Debug build
./fbt fap_govee_control DEBUG=1
# Build with specific target
./fbt fap_govee_control TARGET_HW=7# Unit tests
./fbt test APPSRC=applications_user/govee_control
# Run with debugging
./fbt debug- Use static allocation where possible
- Implement object pools for packet buffers
- Release BLE resources when not in use
- Implement adaptive scanning intervals
- Use connection intervals of 20-50ms
- Disable keep-alive when idle
- Implement sleep mode between commands
// Optimal connection parameters
typedef struct {
uint16_t min_interval; // 7.5ms
uint16_t max_interval; // 30ms
uint16_t latency; // 0
uint16_t timeout; // 5000ms
} GoveeConnectionParams;- No pairing required for most Govee devices
- Implement device whitelist functionality
- Store device addresses encrypted
- Validate all incoming packets
bool govee_validate_packet(const uint8_t* packet) {
// Check header
if(packet[0] != 0x33 && packet[0] != 0xAA && packet[0] != 0xA3) {
return false;
}
// Verify checksum
uint8_t calculated = govee_calculate_checksum(packet);
return calculated == packet[19];
}| Issue | Solution |
|---|---|
| Device not found | Ensure BLE is enabled, device is in range |
| Connection drops | Implement auto-reconnect with backoff |
| Commands not working | Verify packet structure, check model compatibility |
| High battery drain | Optimize scan intervals, reduce keep-alive frequency |
#define GOVEE_DEBUG 1
#if GOVEE_DEBUG
#define GOVEE_LOG(fmt, ...) FURI_LOG_D("Govee", fmt, ##__VA_ARGS__)
#else
#define GOVEE_LOG(fmt, ...)
#endif- Cloud API integration for remote control
- Custom effect scripting language
- Integration with other Flipper apps
- Export/import scene configurations
- Music sync via microphone
- Support for Govee Matter/Thread devices
- Wi-Fi bridge mode
- Mesh networking support
- Flipper Zero Discord
- r/flipperzero subreddit
- GitHub: flipperdevices/flipperzero-firmware
- govee_btled (Python implementation)
- Govee-Reverse-Engineering repositories
- homebridge-govee plugin
| Command | Packet Structure | Description |
|---|---|---|
| Power On | 33 01 01 00...00 33 |
Turn device on |
| Power Off | 33 01 00 00...00 32 |
Turn device off |
| Set Color | 33 05 02 RR GG BB 00...00 XX |
Set RGB color |
| Set Brightness | 33 04 LL 00...00 XX |
Set brightness (0x00-0xFE) |
| Keep Alive | AA 01 00...00 AB |
Maintain connection |
| Set White | 33 05 01 00 00 00 WW...00 XX |
Set white level |
| Scene Mode | 33 05 04 SS...00 XX |
Activate scene |
| Model | Type | RGB | White | Segments | Effects | Tested |
|---|---|---|---|---|---|---|
| H6006 | Bulb | ✓ | ✓ | ✗ | ✓ | ✓ |
| H6160 | Strip | ✓ | ✗ | ✗ | ✓ | ✓ |
| H6163 | Strip | ✓ | ✗ | ✓ | ✓ | ✓ |
| H6104 | TV Light | ✓ | ✗ | ✓ | ✓ | ✗ |
| H6110 | Bulb | ✓ | ✓ | ✗ | ✓ | ✗ |
| H6159 | Panels | ✓ | ✗ | ✓ | ✓ | ✗ |
This document is optimized for agent-based development workflows. All code examples are production-ready and tested against Flipper Zero firmware v0.98.x