Skip to content

Instantly share code, notes, and snippets.

@conoro
Forked from pwnall/yunmai_protocol.txt
Created March 28, 2017 14:38
Show Gist options
  • Save conoro/f0c1d96c450a8f5cce70e2846c3686c4 to your computer and use it in GitHub Desktop.
Save conoro/f0c1d96c450a8f5cce70e2846c3686c4 to your computer and use it in GitHub Desktop.
Yunmai smart scale (M1301, M1302, M1303) Bluetooth LE protocol notes
Yunmai smart scale (M1301, M1302, M1303) Bluetooth LE protocol notes
Commands are written to GATT attribute 0xffe9 of service 0xffe5. Responses come
as value change notifications for GATT attribute 0xffe4 of service 0xffe0. These
are 16-bit Bluetooth LE UUIDs, so nnnn is 0000nnnn-0000-1000-8000-00805F9B34FB.
-----
Packet Structure
0d - packet start
1e - scale software version (only in responses)
nn - total packet length (includes packet start and CHK)
nn - command / response opcode
data
CHK - XOR of all bytes except for frame start
Example command: 0d 05 13 00 16
Example response: 0d 1e 05 05 1e
-------
Response
BE = big endian (most numbers are big endian)
LE = little endian (the weight stored in a profile is little endian)
00: 0d - packet start
01: 1e - scale software version
02: total packet length (includes packet start and CHK)
03: response type
03: 01 - unfinished weighing
04-07: date (unix time, seconds) - BE uint32
08-09: weight - BE uint16 times 0.01
10: CHK
03: 02 - finished weighing
04: 00 - historical info
05-08: date (unix time, seconds) - BE uint32
09-12: recognized userID - BE uint32
13-14: weight - BE uint16 times 0.01
15-16: resistance - BE uint 16
17-18: fat percentage - BE uint16 times 0.01
19: CHK
03: 06 - result to user operation
04: operation type - USER_ADD_OR_UPDATE: 1 | USER_ADD_OR_QUERY: 3 | USER_DELETE: 2
05-08: userID - 4 bytes, BE
09: height - in cm
10: sex - 1 for male
11: age - in years
12: waist line - default 85 (0x55)
13: bust - default 90 (0x5a)
14-15: basisWeight - default 0, set to previously received weight - LE uint16 times 0.01
16: display unit - 1 metric
03: 17 - device time
04-07:
08: CHK
-----
Command
00: 0d - packet start
01: total packet length (includes packet start and CHK)
02: command
02: 11 - set time
03-07: date (unix time, seconds) - BE uint32
08: fractional second
09: CHK
02: 17 - read time
03: CHK
02: 10 - user operation
03: operation type - USER_ADD_OR_UPDATE: 1 | USER_ADD_OR_QUERY: 3 | USER_DELETE: 2
04-07: userID - 4 bytes, BE
08: height - in cm
09: sex - 1 for male, 2 for female
10: age - in years
11: waist line - default 85 (0x55)
12: bust - default 90 (0x5a)
13-14: basisWeight - default 0, set to previously received weight
15: display unit - 1 for metric, 2 for imperial
16: body type - always 3
17: CHK
@vitoresende
Copy link

Hi @conoro, that'a a great and helpful document.
I'm facing a problem cause I don't have many experience with BLE and GATT protocol.
When I read the attribute value 0xffe4 of service 0xffe0 I got something like: DR8LAV6YdUMAAOU=.
How can I convert it to something like: 0d:1f:14:02:00:59:c6:15:2f:23:d0:b6:32:26:95:00:a5:08:f2:37?
Thanks for your help

@conoro
Copy link
Author

conoro commented Apr 16, 2020

@vitoresende I'm afraid I didn't write this document. It's a fork from https://gist.github.com/pwnall/4ec3cc3d18affa062dd5596f1b4308c9

They may be able to help you.

But if you use the NRF Connect App on Android or iOS, it should show you the values in the format that you want.

@vitoresende
Copy link

vitoresende commented Apr 16, 2020

Thank you @conoro for you answer. Actually, what I've got was a base64 string and here is how I solved this situation for someone who wants some help on it. Thanks again.

@ValdikSS
Copy link

Date synchronization (11 - set time) is incorrect: there is no fractional seconds, there is a time zone.
And the 8th byte (byte[7]) is set to 0x14 for some reason.
This is at least true for protocol 0x1f in Yunmai M1501 ( 0d 1F 05 05 1F), the document above describes 0x1e protocol.

public final byte[] sendDate(int unixinp) {
    Calendar cal = Calendar.getInstance(TimeZone.getDefault());
    int zone_dst_offset = (cal.get(15) / 1000) + (cal.get(16) / 1000); // 15 = ZONE_OFFSET
                                                                        // 16 = DST_OFFSET
    byte[] bArr = new byte[13];
    byte[] unixtime_arr = HexStringUtils.inttobytearr(unixinp, 4);
    byte[] const20 = HexStringUtils.inttobytearr((int) ScaleBleDataHelper.const20, 4);
    byte[] tzdata_arr = HexStringUtils.inttobytearr(Math.abs(zone_dst_offset), 4);
    bArr[0] = TType.const13;
    bArr[1] = HexStringUtils.getbyte(13);
    bArr[2] = 17; // 0x11
    bArr[3] = unixtime_arr[0];
    bArr[4] = unixtime_arr[1];
    bArr[5] = unixtime_arr[2];
    bArr[6] = unixtime_arr[3];
    bArr[7] = const20[3]; // 0x14
    if (zone_dst_offset < 0) {
        bArr[8] = C5194n.byte_min_value; // -127
    } else {
        bArr[8] = 0;
    }
    bArr[9] = tzdata_arr[1];
    bArr[10] = tzdata_arr[2];
    bArr[11] = tzdata_arr[3];
    bArr[12] = HexStringUtils.m16022g(_ArraysJvm.m4967c1(bArr, 0, 12));
    return bArr;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment