oliexdev / openScale

Open-source weight and body metrics tracker, with support for Bluetooth scales
GNU General Public License v3.0
1.72k stars 297 forks source link

Scale support request for Withings Body+ 3C #897

Open glacambre opened 2 years ago

glacambre commented 2 years ago

Scale name Withings Body+ 3C. The model I have is Nokia-branded (withings was temporarily aquired by Nokia), but I don't think this has an impact on the scale's internals. The official application is withings healthmate.

Step 1: Read the general reverse engineer process

Step 2: Acquiring some Bluetooth traffic

Unfortunately, this scale is shared with other persons and it looks like the logs seem to contain personal information about these other persons, so I can't share the files (I can see their names in some of the bluetooth packets).

Step 3: Discover Bluetooth services and characteristic The scale has two steps: setup and synchronization. Here are the logs taken through openScale when the scale is in "setup" mode: btsnoop_hci_openscale_init1.log And here are the logs taken through openScale when the scale has been setup with the official app: btsnoop_hci_openscale1.log

glacambre commented 2 years ago

I'm aware that you probably can't do anything without having access to captures of the data synchronization step between the official app and the scale. This is why I attempted to add support by myself, but my lack of android development and bluetooth knowledge is hindering me. So far, I managed to hack openScale to accept my scale:

diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothFactory.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothFactory.java
index 8985d008..62f0f07b 100644
--- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothFactory.java
+++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothFactory.java
@@ -136,6 +136,9 @@ public class BluetoothFactory {
         if (deviceName.equals("CH100")){
             return new BluetoothHuaweiAH100(context);
         }
+        if (deviceName.equals("Body+ 3C")){
+            return new BluetoothBodyPlus3C(context);
+        }
         return null;
     }
 }

With BluetoothBodyPlus3C.java looking like this:

package com.health.openscale.core.bluetooth;

import android.content.Context;
import android.os.Handler;

import com.health.openscale.core.OpenScale;
import com.health.openscale.core.datatypes.ScaleUser;

import java.util.UUID;

import timber.log.Timber;

public class BluetoothBodyPlus3C extends BluetoothCommunication {
    private static final UUID SERVICE_BODYPLUS3C_CUSTOM_SERVICE = BluetoothGattUuid.fromShortCode(0xfaa0);
    private static final UUID SERVICE_BODYPLUS3C_CUSTOM_RECEIVE = BluetoothGattUuid.fromShortCode(0xfaa2);

    private enum STEPS  {
        INIT,
    }

    private Context context;

    private Handler beatHandler;

    public BluetoothBodyPlus3C(Context context) {
        super(context);
        this.context = context;
        this.beatHandler = new Handler();
    }

    @Override
    public String driverName() {
        return "Withings Body+ 3C";
    }

    @Override
    protected boolean onNextStep(int stepNr) {
        STEPS step;
        try {
            step = STEPS.values()[stepNr];
        } catch (Exception e) {
            // stepNr is bigger then we have in STEPS
            return false;
        }
        switch (step) {
            case INIT:
                // wait scale wake up
                Timber.d("BODYPLUS3C::onNextStep step 0 = set notification");
                final ScaleUser selectedUser = OpenScale.getInstance().getSelectedScaleUser();
                // Setup notification
                setNotificationOn(SERVICE_BODYPLUS3C_CUSTOM_SERVICE, SERVICE_BODYPLUS3C_CUSTOM_RECEIVE);
                stopMachineState();
                break;
        }

        return true;
    }

    @Override
    public void onBluetoothNotify(UUID characteristic, byte[] value) {
        Timber.d("BODYPLUS3C::onBluetoothNotify uuid: %s", characteristic.toString());
    }
}

But I'm quite lost about what the next steps are - in particular, I have a hard time deciphering the bluetooth logs. I've found that asking wireshark to filter out everything except RFCOMM packets seem to only keep what's truly important, but I'm at a loss as to what to do exactly. My first guess would be to perform a diff between my various logs, see what parts are different between the various files and then hone in on them, trying to figure out how to tie them to known values.

Do you have any tool/recommended reading to proceed with this? How do you usually proceed?

glacambre commented 2 years ago

I used wireshark to export the captures as text and then vimdiff'd the two shortest ones I had. It looks like the first two messages exchanged by the phone and the scale are identical:

Withings_70:aa:3d -> Sony_1b:6f:9e RFCOMM   15     Rcvd UIH Channel=4 
0000  01 00                                             ..

Sony_1b:6f:9e -> Withings_70:aa:3d RFCOMM   39     Sent UIH Channel=4 UID 
0000  01 01 00 15 01 01 01 00 10 01 2a 00 06 01 01 00   ..........*.....
0010  4d 84 25 09 28 00 02 00 1b                        M.%.(....

And then, messages start differing. In the first log:

Withings_70:aa:3d -> Sony_1b:6f:9e RFCOMM   62     Rcvd UIH Channel=4 UID 
0000  01 01 00 2c 01 01 28 00 27 01 22 00 23 11 30 30   ...,..(.'.".#.00
0010  3a 32 34 3a 65 34 3a 37 30 3a 61 61 3a 33 63 10   :24:e4:70:aa:3c.
0020  2a 25 36 27 16 c2 c9 94 3c 5b 58 4c c6 c8 3c b2   *%6'....<[XL..<.

Sony_1b:6f:9e -> Withings_70:aa:3d RFCOMM   87     Sent UIH Channel=4 UID 
0000  01 01 00 45 01 01 28 00 40 01 23 00 15 14 00 eb   ...E..(.@.#.....
0010  b1 69 26 7d e2 88 a2 07 b0 bf ef b8 0f b6 4f 10   .i&}..........O.
0020  93 3f 01 22 00 23 11 30 30 3a 32 34 3a 65 34 3a   .?.".#.00:24:e4:
0030  37 30 3a 61 61 3a 33 63 10 bc 20 a6 b9 68 f1 7a   70:aa:3c.. ..h.z
0040  ed bf 0b c9 b2 c8 ce f0 ce                        .........

In the second:

Withings_70:aa:3d -> Sony_1b:6f:9e RFCOMM   62     Rcvd UIH Channel=4 UID 
0000  01 01 00 2c 01 01 28 00 27 01 22 00 23 11 30 30   ...,..(.'.".#.00
0010  3a 32 34 3a 65 34 3a 37 30 3a 61 61 3a 33 63 10   :24:e4:70:aa:3c.
0020  36 f1 2f 95 2e 85 65 5d b5 26 a2 06 93 8b 7f c5   6./...e].&......

Sony_1b:6f:9e -> Withings_70:aa:3d RFCOMM   87     Sent UIH Channel=4 UID 
0000  01 01 00 45 01 01 28 00 40 01 23 00 15 14 1a 46   ...E..(.@.#....F
0010  52 f6 d0 a4 72 2c 75 1c 2b 49 5d 58 12 da eb a3   R...r,u.+I]X....
0020  f9 95 01 22 00 23 11 30 30 3a 32 34 3a 65 34 3a   ...".#.00:24:e4:
0030  37 30 3a 61 61 3a 33 63 10 82 d0 5f 1a dd e2 e0   70:aa:3c..._....
0040  34 56 96 ae 8f 39 30 3a db                        4V...90:.

So it looks like the scale is sending its identifier to the phone along an extra payload, and the phone sends the identifier back with a different payload. But what's the relationship between the two extra payloads? Not sure yet, here are some more exchanges that might help figure that out...

Withings_70:aa:3d -> Sony_1b:6f:9e RFCOMM   62     Rcvd UIH Channel=4 UID 
0000  01 01 00 2c 01 01 28 00 27 01 22 00 23 11 30 30   ...,..(.'.".#.00
0010  3a 32 34 3a 65 34 3a 37 30 3a 61 61 3a 33 63 10   :24:e4:70:aa:3c.
0020  44 26 a8 52 7b 68 3f 6e 78 c7 fb 8d 4e ce 8c d0   D&.R{h?nx...N...

Sony_1b:6f:9e -> Withings_70:aa:3d RFCOMM   87     Sent UIH Channel=4 UID 
0000  01 01 00 45 01 01 28 00 40 01 23 00 15 14 3f 9e   ...E..(.@.#...?.
0010  aa 84 94 83 97 2b 79 c9 59 ef fd 00 f6 8e 6d a6   .....+y.Y.....m.
0020  25 16 01 22 00 23 11 30 30 3a 32 34 3a 65 34 3a   %..".#.00:24:e4:
0030  37 30 3a 61 61 3a 33 63 10 15 0e c6 05 4f ea 9b   70:aa:3c.....O..
0040  2c b3 5a 73 05 f2 e7 c8 3f                        ,.Zs....?
Withings_70:aa:3d -> Sony_1b:6f:9e RFCOMM   62     Rcvd UIH Channel=4 UID 
0000  01 01 00 2c 01 01 28 00 27 01 22 00 23 11 30 30   ...,..(.'.".#.00
0010  3a 32 34 3a 65 34 3a 37 30 3a 61 61 3a 33 63 10   :24:e4:70:aa:3c.
0020  2b 75 ef 4e 79 f5 87 f6 ee f6 3d 7b 8b ee 20 1c   +u.Ny.....={.. .

Sony_1b:6f:9e -> Withings_70:aa:3d RFCOMM   87     Sent UIH Channel=4 UID 
0000  01 01 00 45 01 01 28 00 40 01 23 00 15 14 62 4c   ...E..(.@.#...bL
0010  2e 6a 21 1d a0 c0 54 42 e5 62 36 d6 ed 6a 0b 86   .j!...TB.b6..j..
0020  c4 96 01 22 00 23 11 30 30 3a 32 34 3a 65 34 3a   ...".#.00:24:e4:
0030  37 30 3a 61 61 3a 33 63 10 6a eb 4b 68 59 87 67   70:aa:3c.j.KhY.g
0040  03 db 35 d7 32 51 d3 24 33                        ..5.2Q.$3

I think I might try to attach to the app with a debugger and see if I can make sense of what I see...