Android app for displaying data from the Qingping CGD1 - Bluetooth LE alarm clock with sensors.
- Warning
- Features
- Technical Details
- Screenshots
- Protocol Specification
App was largely created using LLMs. I still have reviewed the code, so it's only semi-slop, but you have been warned, etc., etc.
- Initial setup screen to guide the user through finding and selecting their device
- Scans for a specific Bluetooth LE device by its MAC address
- Share a saved device with others using QR code
- Parses and displays sensor data
- Management of up to 16 device alarms
- Custom ringtones uploading
- Global alarm switch to enable or disable all device alarms at once
- Bluetooth state monitoring with automatic prompts to enable it
- Interactive real-time previews for brightness and volume settings
- Widget for displaying sensor data on the home screen
- Configurable background updates to fetch data periodically
- Settings to customize the device's MAC address, theme (light/dark/system), and language
The application is built with modern Android development technologies and targets recent Android versions.
- Target API: The application targets Android 16 (API level 36) and has a minimum requirement of Android 14 (API level 34)
- UI: Jetpack Compose for a declarative and modern UI
- Background Processing:
WorkManagerandAlarmManagerfor scheduling periodic data fetches, ensuring the widget is always up to date
This section describes the reverse-engineered Bluetooth Low Energy (BLE) protocol for the Qingping CGD1 Alarm Clock.
The device uses a custom service structure but relies on standard 128-bit base UUIDs for characteristics in this specific firmware version.
Target Service UUID: 22210000-554a-4546-5542-46534450464d (Advertised)
| Function | Characteristic UUID | Properties |
|---|---|---|
| Auth Write | 00000001-0000-1000-8000-00805f9b34fb |
Write |
| Auth Notify | 00000002-0000-1000-8000-00805f9b34fb |
Notify |
| Data Write | 0000000b-0000-1000-8000-00805f9b34fb |
Write |
| Data Notify | 0000000c-0000-1000-8000-00805f9b34fb |
Notify |
| Sensor Notify | 00000100-0000-1000-8000-00805f9b34fb |
Notify |
The device uses a two-step authentication protocol with a 16-byte random token. Once paired, the same token must be used for all future connections.
Flow:
- Connect to the device and discover services
- Enable Notifications on Auth Notify (
...0002) - Send Auth Init to Auth Write (
...0001):11 01 [Token 16B] - Wait for ACK on Auth Notify:
04 ff 01 00 02(success, proceed to step 5) - Send Auth Confirm to Auth Write:
11 02 [Token 16B] - Wait for final ACK:
04 ff 02 00 00
Device will send you an ACK even when the token is bad. Try to sync time or do other "privileged" action and check if the device will close connection with you.
Token Management:
- For new devices: Generate a random 16-byte token
- For paired devices: Use the stored token from the previous pairing
- Token must match what the device expects (first successful pairing establishes the token)
ACK Response Format: 04 ff [CmdID] [Len] [Status]
- Status
00= Success - Status
01= Failure - Status
02= Continue (for Auth Init, proceed to step 5)
After authentication, it is recommended to synchronize the time.
- Command (Auth Write):
05 09 [Timestamp 4B LE] - Response (Auth Notify):
04 ff 09 00 00(Success)
The device supports a fixed capacity of 16 alarm slots (indexed 0-15). All alarm/settings operations happen on the Data characteristics.
To create or modify an alarm:
- Command:
07 05 [ID] [Enabled] [HH] [MM] [Days] [Snooze] - ID: The alarm index (0-15)
- Enabled:
0x01= On,0x00= Off - HH, MM: Hour (0-23) and Minute (0-59)
- Days (Bitmask):
0x01= Monday0x02= Tuesday0x04= Wednesday0x08= Thursday0x10= Friday0x20= Saturday0x40= Sunday0x00= Once
- Snooze:
0x01= On,0x00= Off
To delete an alarm, overwrite it with FF values (marking it as empty/unused).
- Command:
07 05 [ID] FF FF FF FF FF
- Command:
01 06 - Response:
11 06 [Base Index] [Alarm Entry 1 (5B)] ... - Alarm Entry:
[Enabled] [HH] [MM] [Days] [Snooze]
Note: Device sends multiple packets if needed (up to 4 alarms per packet). All 16 slots are
returned, empty slots have FF FF FF FF FF values.
- ACK (after Set/Delete):
04 ff 05 00 00(Success)
Managed via a single comprehensive payload on Data Write.
-
Command: Start with
13(Set Settings) or01 02(Read Settings) -
Set Settings Payload (20 bytes):
13 01 [Vol] [Hdr1] [Hdr2] [Flags] [Timezone] [Duration] [Brightness] [NightStartH] [NightStartM] [NightEndH] [NightEndM] [TzSign] [NightEn] [Sig 4B]
| Byte | Value | Description |
|---|---|---|
| 0 | 0x13 |
Command ID |
| 1 | 0x01 / 0x02 |
Set / Read Response |
| 2 | 1-5 |
Sound Volume |
| 3-4 | 58 02 |
Fixed Header / Version (???) |
| 5 | Bitmask | Mode Flags: See the Mode Flags Breakdown table below. |
| 6 | Integer | Timezone Offset (Units of 6 minutes). Device does not handle DST automatically. |
| 7 | Seconds | Backlight Duration (0=Off) |
| 8 | Packed | Brightness (High nibble: Day/10, Low nibble: Night/10) |
| 9-10 | HH:MM | Night Start Time |
| 11-12 | HH:MM | Night End Time |
| 13 | 0/1 |
Timezone Sign (1=Positive, 0=Negative) |
| 14 | 0/1 |
Night Mode Enabled |
| 15 | - | Reserved (preserved from device response) |
| 16-19 | Sig 4B |
Ringtone signature (4 bytes). Identifies the device ringtone — see the "Known Ringtone Signatures" list below. |
This byte acts as a bitfield where individual bits control specific boolean settings.
| Bit | Value (Hex) | Description | 0 (Off/Default) | 1 (On/Active) |
|---|---|---|---|---|
| 0 | 0x01 |
Language | Chinese | English |
| 1 | 0x02 |
Time Format | 24-hour | 12-hour |
| 2 | 0x04 |
Temp Unit | Celsius | Fahrenheit |
| 3 | 0x08 |
(Reserved ?) | - | - |
| 4 | 0x10 |
Master Alarm Disable (!) | Enabled | Disabled |
| 5-7 | - | (Unused ?) | - | - |
Workaround: Disabling night mode is being done via setting 1-minute night mode (i.e.
00:00 - 00:01). Yup, it's that stupid; even official app does this.
- Command (Data Write):
02 03 [Value] - Value: Brightness level / 10 (
0-10). - Response (Data Notify):
04 ff 03 00 00(Success).
Plays a generic "beep" sound for testing volume level (not the user's selected ringtone).
- Command (Data Write):
01 04(Play at current volume) or02 04 [Vol](Play at volume1-5) - Response (Data Notify):
04 ff 04 00 00(Success)
- Target:
00000100-...(Notify) - Format:
[00] [Temp L] [Temp H] [Hum L] [Hum H] - Values: Little Endian Int16 / 100.0
- Service UUID:
0x180f, Char UUID:0x2a19 - Format: 1 byte (percentage)
- Command (Auth Write):
01 0d - Response (Auth Notify):
0b [Length] [ASCII String]
Official apps are using these 4-byte signatures to identify ringtones:
| Ringtone | Signature (Hex) |
|---|---|
| Beep | fd c3 66 a5 |
| Digital Ringtone | 09 61 bb 77 |
| Digital Ringtone 2 | ba 2c 2c 8c |
| Cuckoo | ea 2d 4c 02 |
| Telephone | 79 1b ac b3 |
| Exotic Guitar | 1d 01 9f d6 |
| Lively Piano | 6e 70 b6 59 |
| Story Piano | 8f 00 48 86 |
| Forest Piano | 26 52 25 19 |
For uploading custom ringtones, this app is using these alternating slot signatures:
| Slot | Signature (Hex) |
|---|---|
| Custom 1 | de ad de ad |
| Custom 2 | be ef be ef |
Important: Always alternate between slots when uploading new custom audio. Doesn't matter how you name it. The device may reject uploads if the target signature matches the currently active ringtone, but audio itself is different from the one you are uploading.
If you want to host your own ringtone repository, you can create a JSON manifest file. Just point to
a JSON file (e.g., https://example.com/rings/index.json), and the app will parse the manifest for
ringtone URLs
Manifest Format:
The JSON file should map hex signatures to objects containing at least a "wav" URL. The official
app uses an additional "pcm" field, but this app takes the Wave and converts it on its own.
{
"1d019fd6": {
"name": "Exotic Guitar",
"wav": "https://example.com/rings/1d019fd6.wav"
},
"6e70b659": {
"name": "Lively Piano",
"wav": "https://example.com/rings/6e70b659.wav"
},
"8f004886": {
"name": "Story Piano",
"wav": "https://example.com/rings/8f004886.wav"
},
"26522519": {
"name": "Forest Piano",
"wav": "https://example.com/rings/26522519.wav"
},
"fdc366a5": {
"name": "Beep",
"wav": "https://example.com/rings/fdc366a5.wav"
},
"ea2d4c02": {
"name": "Cuckoo",
"wav": "https://example.com/rings/ea2d4c02.wav"
},
"0961bb77": {
"name": "Digital Ringtone",
"wav": "https://example.com/rings/0961bb77.wav"
},
"ba2c2c8c": {
"name": "Digital Ringtone 2",
"wav": "https://example.com/rings/ba2c2c8c.wav"
},
"791bacb3": {
"name": "Telephone Ringtone",
"wav": "https://example.com/rings/791bacb3.wav"
}
}Key: Hex signature (without 0x prefix).
Value: Object with name (display name) and wav (full URL to WAV file).
Audio Format: 8-bit Unsigned PCM, 8000 Hz, Mono
Step 1 - Init Command (Data Write):
08 10 [Size 3B LE] [Signature 4B]
- Size: Audio length in bytes (Little Endian, 3 bytes)
- Signature: Target ringtone slot signature
Step 2 - Wait for Init ACK (Data Notify):
04 ff 10 00 [Status]
- Status
00or09= Success, proceed with upload
Step 3 - Send Audio Data:
- Packet size: 128 bytes
- Packets per block: 4 (512 bytes per block)
- First packet header: Prepend
81 08to the audio data - Wait for block ACK (
04 ff 08 ...) after every 4 packets
Step 4 - Completion:
After sending all audio data, the device will apply the new ringtone.
| Cmd | Sub | Characteristic | Description |
|---|---|---|---|
| 11 | 01 | Auth Write | Auth Init (+ 16B token) |
| 11 | 02 | Auth Write | Auth Confirm (+ 16B token) |
| 05 | 09 | Auth Write | Time Sync (+ 4B timestamp LE) |
| 01 | 0D | Auth Write | Read Firmware Version |
| 13 | 01 | Data Write | Set Settings (Volume, Brightness, etc.) |
| 01 | 02 | Data Write | Read Settings |
| 02 | 03 | Data Write | Set Immediate Brightness |
| 01 | 04 | Data Write | Preview Ringtone (current volume) |
| 02 | 04 | Data Write | Preview Ringtone (+ 1B volume) |
| 07 | 05 | Data Write | Set Alarm |
| 01 | 06 | Data Write | Read Alarms |
| 08 | 10 | Data Write | Audio Upload Init |
ACK Format (Notify characteristics): 04 ff [CmdSub] [Len] [Status]
When the device disconnects, the GATT status indicates the reason:
| Status | Meaning | Description |
|---|---|---|
| 0 | GATT_SUCCESS |
Normal disconnect (user requested) |
| 8 | GATT_CONN_TIMEOUT |
Connection timeout |
| 19 | GATT_CONN_TERMINATE_PEER |
Device terminated connection |
| 22 | GATT_CONN_TERMINATE_LOCAL |
Link lost / local host terminated |











