koenvervloesem / bluetooth-clocks

Set and get the time on various Bluetooth Low Energy clocks
https://bluetooth-clocks.readthedocs.io
MIT License
18 stars 3 forks source link

Add support for Qingping Cleargrass CGD1 #23

Open ov1d1u opened 10 months ago

ov1d1u commented 10 months ago

Thank you for your project, I started something similar in the past but without ever finalizing it.

This is the clock I'm talking about: https://www.amazon.com/Bluetooth-Temperature-Ringtones-Adjustable-Backlight/dp/B08NX6BM6X

These are the services and characteristics of the clock:

- Device Name [N R] (0x2A00)
- Appearance [R] (0x2A01)
- Peripheral Preferred Connection Parameters [R] (0x2A04)
Generic Attribute (0x1801)
- Service Changed [I] (0x2A05)
   Client Characteristic Configuration (0x2902)
Device Information (0x180A)
- PnP ID [R] (0x2A50)
Battery Service (0x180F)
- Battery Level [N R] (0x2A19)
   Client Characteristic Configuration (0x2902)
Unknown Service (00010203-0405-0607-0809-0a0b0c0d1912)
- Unknown Characteristic [R WNR] (00010203-0405-0607-0809-0a0b0c0d2b12)
   Characteristic User Description (0x2901)
Unknown Service (00010203-0405-0607-0809-0a0b0c0d2b20)
- Unknown Characteristic [R WNR] (00010203-0405-0607-0809-0a0b0c0d2b21)
   Characteristic User Description (0x2901)
Unknown Service (0000fe95-0000-1000-8000-00805f9b34fb)
- Unknown Characteristic [R] (00000004-0000-1000-8000-00805f9b34fb)
   Characteristic User Description (0x2901)
- Unknown Characteristic [N WNR] (00000010-0000-1000-8000-00805f9b34fb)
   Characteristic User Description (0x2901)
   Client Characteristic Configuration (0x2902)
- Unknown Characteristic [N W] (00000017-0000-1000-8000-00805f9b34fb)
   Characteristic User Description (0x2901)
   Client Characteristic Configuration (0x2902)
- Unknown Characteristic [N WNR] (00000018-0000-1000-8000-00805f9b34fb)
   Characteristic User Description (0x2901)
   Client Characteristic Configuration (0x2902)
- Unknown Characteristic [N WNR] (00000019-0000-1000-8000-00805f9b34fb)
   Characteristic User Description (0x2901)
   Client Characteristic Configuration (0x2902)
   Unknown Descriptor (00002800-0000-1000-8000-00805f9b34fb)
- Unknown Characteristic [WNR] (00000101-0065-6c62-2e74-6f696d2e696d)
Unknown Service (22210000-554a-4546-5542-46534450464d)
- Unknown Characteristic [W] (00000001-0000-1000-8000-00805f9b34fb)
   Characteristic User Description (0x2901)
- Unknown Characteristic [N] (00000002-0000-1000-8000-00805f9b34fb)
   Characteristic User Description (0x2901)
   Client Characteristic Configuration (0x2902)
- Unknown Characteristic [W] (0000000b-0000-1000-8000-00805f9b34fb)
   Characteristic User Description (0x2901)
- Unknown Characteristic [N] (0000000c-0000-1000-8000-00805f9b34fb)
   Characteristic User Description (0x2901)
   Client Characteristic Configuration (0x2902)
- Unknown Characteristic [N] (00000100-0000-1000-8000-00805f9b34fb)
   Characteristic User Description (0x2901)
   Client Characteristic Configuration (0x2902)
- Unknown Characteristic [R] (0000000d-0000-1000-8000-00805f9b34fb)
   Characteristic User Description (0x2901)

It looks like the service UUID and characteristic UUID matches the ones on BT Clock Lite, although I'm not sure if 00000001-0000-1000-8000-00805f9b34fb is also the characteristic for the time on CGD1, too. I tried to force it to be recognizable in bluetooth-clocks by modifying the code in devices/qingping.py, to the point where the script connected and even wrote data successfully on the device's 00000001-0000-1000-8000-00805f9b34fb characteristic, but nothing happens on the clock (apart from the Bluetooth symbol flashing, as a result of the connection).

I suppose that communication with the clock is encrypted with the BLE key or the token I'm getting by using Xiaomi-cloud-tokens-extractor:

   NAME:     Alarm clock
   ID:       blt.4.195fhgnfggc00
   BLE KEY:  dcbd1d12c64ba3f92793307bbda19ff0
   MAC:      58:2D:34:55:XX:XX
   IP:       79.112.12.XXX
   TOKEN:    92a100ab3d15e366dd825967
   MODEL:    cgllc.clock.dove

but I'm not sure what the encryption algorithm could be.

Have you looked into this product yet? I'm waiting to root my spare Redmi phone (I have to wait for a few days to be able to unlock the bootloader) so I can btsnoop the official app, but if you got any other hint until then, I could try it to see if I get any result.

ov1d1u commented 10 months ago

Surprise, an update to Android 14 re-enabled btsnoop-ing with Wireshark again on my S23! This means I was able to gather a pcap file from Wireshark with the packets sent by the Xiaomi Home app to the clock (see qingping.pcapng.zip).

It looks like the app writes 05090bc94065 two times on the 0000001-0000-1000-8000-00805f9b34fb characteristic, a value which is not far away from what your script writes (050984e74065). Writing the value recorded by Wireshark doesn't set the time back to the time of capture, so maybe there's some sequence which needs to be written on the clock so it accepts setting the time...

ov1d1u commented 10 months ago

Got some updates (sorry for spamming). I tried to snoop the BT communication with the Qingping+ app (com.cleargrass.app.air) and this app seems to be less verbose than Xiaomo Home when talking to the clock. I tried to replay the data being written on the 0001 characteristic and I have successfully changed the time on the clock!

The data I wrote was 11027c14aec1c4c0be574ac49dab345af86a then 0509b0dc4065 and the time on the clock changed to 12:53 2032-10-31 (I'm on GMT+2). I just have to figure out what is with that 11027c14aec1c4c0be574ac49dab345af86a and I think we can also add support for this device.

Capture file: qingping-app.pcapng.gz

koenvervloesem commented 10 months ago

Thanks for looking into this! I actually have the same clock, and I tried to figure out the synchronization. I recognized like you that the app writes the time in the same format as for the BT Clock Lite, but I didn't try the data packet the app sent before the time, and without this packet the clock didn't synchronize.

With the results of your investigation, I looked again at the communication between the Qingping+ app and my clock. In my situation, the first command the app sends to the clock is 1102c1d12138900e146a581b80230426aca1, which at least begins with the same two bytes as yours:

11027c14aec1c4c0be574ac49dab345af86a
1102c1d12138900e146a581b80230426aca1

When I send my command and then the same time synchronization command as for the BT Clock Lite, the time synchronizes, like in your case. It looks like it sends the same 18-byte command each time before sending the time synchronization command.

This is progress!

When I send your command and then the time synchronization command, the time doesn't synchronize.

So this seems like some device-specific key you first need to send to the device before being able to synchronize the time.

1102 is the same for our two commands, so I suppose the 32 remaining characters, or 16 bytes, are a 128-bit key. Or what do you think? So now the question is whether we can find this device-specific key without sniffing the BLE communication?

ov1d1u commented 10 months ago

Great finding with the common 1102! I was hoping this is just 1102 + BLE KEY from above, as the rest of the string and BLE KEY have the same length, but no luck.

So I began to sniff Qingping+ app's traffic over internet and guess what: the key is found in the response for https://qingplus.cleargrass.com/pair/list?, at the link_token key:

{
    "data": {
        "devices": [{
            "version": "1.0.1_0126",
            "link_token": "7c14aec1c4c0be574ac49dab345af86a",
            "state": {
                "empty": 1
            },
            "name": "Bedroom Alarm Clock",
            "id": 809689,
            "rank": 9999,
            "widget_rank": 9999,
            "push_status": 0,
            "pair_type": 0,
            "widget_status": 0,
            "mac": "582D34558A87",
            "model": "Dove",
            "product": {
                "category": "Dove",
                "model": "Dove"
            }
        }],
        "has_alert": false,
        "mqtt": {
                         ....
        },
        "entries": ["snow", "goose-homekit", "goose", "dove", "sparrow", "hodor", "magpie", "duck2", "mall", "parrot", "dany", "chicken"],
        "gateway_entries": ["goose", "dove", "hodor", "magpie", "duck2", "parrot", "chicken"],
        "use_hotfix": false,
        "feedback_unread": false
    },
    "code": 0
}

Obtaining this programmatically is another challenge, as it seems there's a dynamic header named app-timestamp which is the current timestamp and a header named app-sign which, I guess, its changing related to the other headers.

Probably there's a similar way to get this key from the Xiaomi Home app (or using Xiaomi-cloud-tokens-extractor), but at a first glance I couldn't find the key anywhere else.

koenvervloesem commented 10 months ago

I found a solution. We can set up our own key.

I removed the alarm clock from the Qingping+ app and added it again while sniffing the BLE traffic.

The app sent this sequence of commands to the 0001 characteristic:

1101059d2ae2c46779f074b062d440f130e7
1102059d2ae2c46779f074b062d440f130e7
0509b32d4265

The next time I opened the app, it didn't send the first command. So, it looks like the command beginning with 1101 writes a new key to the device, which is then used by the app afterwards with the 1102 command to authenticate.

I implemented this logic in a test script to write a new key to the alarm clock:

Then another test scripts takes a key from the user and:

This successfully synchronizes the time. So I'm confident that I can now add support for the CGD1. I'll add a command to set a new key, as well as an option to provide the necessary key for synchronizing the time.

I noticed though that the time is off one hour. This is probably a bug related to the time zone or daylight savings time. I'll investigate this further.

ov1d1u commented 10 months ago

Well... this is surprising. I was expecting this to be some sort of burned-in key or a key negotiated during the pairing process. I tested on my side and I can confirm that writing 1101 + key and then 1102 + key on the characteristic lets me use that key for the future requests. It looks like it's necessary to immediately do the 1102 + key after the 1101 call, otherwise further time syncs will not work.

Here (Romania) the time is off by two hours with your script. I suppose the app writes the timezone on the device and every timestamp after that it's treated as local time, because if I send a UTC timestamp, the time appears correctly on the clock.

koenvervloesem commented 10 months ago

Still investigating the time discrepancy.

After the time synchronization command, the Qingping+ app sends the following commands to the 0010 characteristic:

030f0a00
010d

Are these the same for you?

ov1d1u commented 10 months ago

I think you mean on the 0001 characteristic, right? If yes, I'm seeing this write operations on it after time syncing:

030f1400 010d

I tried with your values, but either I'm doing something wrong or it doesn't seem to change anything.

koenvervloesem commented 10 months ago

Sorry, I meant 0001 yes.

Ok, I'm not sure what the difference between your 14 and my 0a is (20 vs. 10 in decimal), or whether it is relevant at all. Those commands don't seem to be necessary anyway to set the time.

ov1d1u commented 10 months ago

It's on 000b. If you write this:

1301055802110a0411160008000001008f004886 it will change the timezone to Portugal. If you write this:

130105580211140411160008000101008f004886 it will change the timezone to Romania.

There's only two bytes different, 0a vs 14 at the 7th character and 00 vs 01 at the 14th one.

I think 00 and 01 means if it's - or + the GMT, 0a and 14 seems to mean some kind of offset. If I'm setting 0b instead of 0a it adds up 6 minutes on the clock.

edit: I'm so stupid, it's so obvious. So the value at offset 6 is a tenth of an hour. If you want to add two hours, you set HEX 14 (20 in DEC) which means 6 * 20 = 120 minutes.

koenvervloesem commented 10 months ago

The thing is, I'm in Brussels timezone (GMT+1), not Portugal timezone (GMT).

I haven't seen the Qingping+ app writing to the 000b characteristic in my case.

ov1d1u commented 10 months ago

Try to change the timezone in your phone settings and reinstall the app, that's how I triggered writing on 000b

ov1d1u commented 10 months ago

If you go with writing on 000b, I'd like to point out that this also contains some other settings, like the general switch for the alarms. If you just dump the bytes trying to adjust the timezone settings, you risk cancelling or turning on an user's alarms. For example, on my clock this is how I turn off all the alarms:

130105580211140411160008000101008f004886

and this is how I turn on all the alarms:

130105580201140411160008000101008f004886

If might want to get the current settings, and you do this by writing 0102 on 000b and reading the returned value on 000c.

amigcamel commented 7 months ago

Hi, thank you for all the hard work. Is there any update on this?