Protocol
The device uses BLE GATT for communication, but the sensor values are not immediately available.
When the original app connects to the device, it performs an elaborate initialization, but most of it isn’t required.
BLE & GATT
The basic technologies behind the sensors communication are Bluetooth Low Energy (BLE) and GATT.
They allow the devices and the app to share data in a defined manner and define the way you can discover the devices and their services.
In general you have to know about services and characteristics to talk to a BLE device.
Adafruit’s Kevin Townsend has published a really nice introduction on the subject and if you don’t know about BLE and/or GATT you should definitely give it a look.
Services, characteristics and handles
Root service
Service UUID | Characteristic UUID | Handle | Read/Write | Description |
---|---|---|---|---|
0000fe95-0000-1000-8000-00805f9b34fb | – | – | – | used for device discovery |
Generic access
Service UUID | Characteristic UUID | Handle | Read/Write | Description |
---|---|---|---|---|
00001800-0000-1000-8000-00805f9b34fb | 00002800-0000-1000-8000-00805f9b34fb | 0x0003 | read | device name |
Data service
Service UUID | Characteristic UUID | Handle | Read/Write | Description |
---|---|---|---|---|
00001204-0000-1000-8000-00805f9b34fb | 00001a00-0000-1000-8000-00805f9b34fb | 0x0033 | write | device mode change (send command) |
00001204-0000-1000-8000-00805f9b34fb | 00001a01-0000-1000-8000-00805f9b34fb | 0x0035 | read | get real-time sensor values |
00001204-0000-1000-8000-00805f9b34fb | 00001a02-0000-1000-8000-00805f9b34fb | 0x0038 | read | get firmware version and battery level |
00001204-0000-1000-8000-00805f9b34fb | 00001a11-0000-1000-8000-00805f9b34fb | 0x003c | read | get historical sensor values |
00001204-0000-1000-8000-00805f9b34fb | 00001a10-0000-1000-8000-00805f9b34fb | 0x003e | write | |
00001204-0000-1000-8000-00805f9b34fb | 00001a12-0000-1000-8000-00805f9b34fb | 0x0041 | read | device time |
Data structure
The data is encoded on bytes in Little endian.
This means that the data is represented with the least significant byte first.
To understand multi-byte integer representation, you can consult this Wikipedia page.
Name
A read request to the 0x03
handle will return n bytes of data, for example 0x466c6f7765722063617265
corresponding to the device name.
Position | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
Value | 46 | 6c | 6f | 77 | 65 | 72 | 20 | 63 | 61 | 72 | 65 |
Bytes | Type | Value | Description |
---|---|---|---|
all | ASCII text | Flower Care | device name |
Firmware and battery
A read request to the 0x38
handle will return 7 bytes of data, for example 0x6328332e312e39
.
Position | 00 | 01 | 02 | 03 | 04 | 05 | 06 |
---|---|---|---|---|---|---|---|
Value | 63 | 28 | 33 | 2e | 31 | 2e | 39 |
Bytes | Type | Value | Description |
---|---|---|---|
00 | uint8 | 99 | battery level in % |
02-06 | ASCII text | 3.1.8 | firmware version |
The second byte (0x28
) seems to be a separator. In older firmware versions it always read 0x13
.
Both are control characters in the ASCII table.
Device time
A read request to the 0x41
handle will return 4 bytes of data, for example 0x09ef2000
.
Position | 00 | 01 | 02 | 03 |
---|---|---|---|---|
Value | 09 | ef | 20 | 00 |
Bytes | Type | Value | Description |
---|---|---|---|
00-03 | uint32 | 2158345 | seconds since device epoch (boot) |
Considering the device’s epoch as second 0, the value obtained is a delta from now from which we can determine the actual time.
We use this method while determining the datetime of historical entries.
Real-time data
In order to read the sensor values you need to change its mode.
This can be done by writing 2 bytes (0xa01f
) to the mode change handle (0x33
).
Note: For my Chinese version (Flower care), in firmware 3.1.9, the value is (0xa0 0x00
), try this value if above not works.
After writing them you can read the actual sensor values from the data handle (0x35
).
A read request will return 16 bytes of data, for example 0xea0000ab00000015b200023c00fb349b
.
Position | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Value | ea | 00 | 00 | ab | 00 | 00 | 00 | 15 | b2 | 00 | 02 | 3c | 00 | fb | 34 | 9b |
Bytes | Type | Value | Description |
---|---|---|---|
00-01 | uint16 | 234 | temperature in 0.1 °C |
02 | ? | ? | ? |
03-06 | uint32 | 171 | brightness in lux |
07 | uint8 | 21 | moisture in % |
08-09 | uint16 | 178 | conductivity in µS/cm |
10-15 | ? | ? | ? |
Historical data
The device stores historical data when not connected that can be later synchronized.
As with reading real-time sensor information, we need to change the device mode by writing 3 bytes (0xa00000
) to the history control handle (0x3e
).
Entry count
The next step is to get information about the stored history by reading from the history data handle (0x3c
).
This will return 16 bytes of data, for example 0x2b007b04ba130800c815080000000000
.
Position | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Value | 2b | 00 | 7b | 04 | ba | 13 | 08 | 00 | c8 | 15 | 08 | 00 | 00 | 00 | 00 | 00 |
Bytes | Type | Value | Description |
---|---|---|---|
00-01 | unint16 | 43 | number of stored historical records |
02-15 | ? | ? | ? |
Read entry
Next we need to read each historical entry individually.
To do so we need to calculate it’s address, write it to the history control handle (0x3e
) and then read the entry from the history data handle (0x3c
).
The address for each individual entry is computed by adding two bytes representing the entry index to 0xa1
.
Entry 0
‘s address will be 0xa10000
, entry 1
‘s address 0xa10100
, entry 16
‘s address 0xa11000
, and so on…
After writing the address of the entry to be read, be can do so by requesting the payload from the history read handle (0x3c
).
This will return 16 bytes of data, for example 0x70e72000eb00005a00000015b3000000
.
Position | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Value | 70 | e7 | 20 | 00 | eb | 00 | 00 | 5a | 00 | 00 | 00 | 15 | b3 | 00 | 00 | 00 |
Bytes | Type | Value | Description |
---|---|---|---|
00-03 | uint32 | 2156400 | timestamp, seconds since device epoch (boot) |
04-05 | uint16 | 235 | temperature in 0.1 °C |
06 | ? | ? | ? |
07-09 | uint32 | 90 | brightness in lux |
10 | ? | ? | ? |
11 | uint8 | 21 | moisture in % |
12-13 | uint16 | 179 | conductivity in µS/cm |
14-15 | ? | ? |
Clear entries
Once all entries have been read, they can be wiped from the device by marking the process as successful
.
This can be achieved by writing 3 bytes (0xa20000
) to the history control handle (0x3e
).
Blink
Writing 2 bytes (0xfdff
) to the mode change handle (0x33
) will make the device blink the top LED once.