Hacking “Laica Body Touch” weight scale with iOS

I have bought a new weight scale that works with or without a smartphone.

There is a proprietary app, called Laica Body Touch.

Problem with this app are:

  • a lot of bugs (reset data, invalid data, logout and data lost, etc…)
  • no connection with Apple’s Health application
  • proprietary, cannot make my own graph and my custom analytics

For this reason, I want to use the outcoming raw data to make my own application and track the weight in the way that I prefer.


Analysis

The app works with Bluetooth (BLE) and sends data in broadcast without any BLE connection every time that you use it.

If you have the Body Touch app, you can see data updated on the screen.


Getting data

Creating a new simple iOS application with XCode and create a new class called BluetoothManager.swift.

This class is responsible to scan all the BLE peripheral around you that sends data in broadcast.

Let’s see the code of simple BluetoothManager.swift (copy/paste):

https://gist.github.com/elpsk/81c2c0da041eea7bc366870d0e8fff09

Well, after this remember to add in your Plist the key in order to allow the Bluetooth usage of your app:

  • NSBluetoothAlwaysUsageDescription
  • NSBluetoothPeripheralUsageDescription

Start scan data

In your ViewController.swift, create the BluetoothManager instance and start scanning peripheral:

var ble: BluetoothManager?

override func viewWillAppear(_ animated: Bool) {
     super.viewWillAppear(animated)
     ble = BluetoothManager(delegate: self)
 }

override func viewDidAppear(_ animated: Bool) {
     super.viewDidAppear(animated)
     ble!.start()
 }

Next, implement the two delegates needed for the manager:

extension ViewController: BluetoothManagerDelegate {

     func didErrorReceived(manager: NSObject) {
         print(#function)
     }

     func didSensorReceived(data: Data, rssi: Int) {
         print( data )
     }

 }

For now, you can print directly, next we create a HEX parser for parsing the raw data.

Build and run! You should see a lot of Bluetooth peripheral sending data in your log. If you have the Laica, you can measure your weight and see the raw hexadecimal data.

Raw data example

{length = 14, bytes = 0x02a109ff 013a ffff80ffff2183aa}
{length = 14, bytes = 0x02a109ff 013a 028986ffff2116aa}
{length = 14, bytes = 0x02a109ff 0170 ffff80ffff21b9aa}
{length = 14, bytes = 0x02a109ff 018a ffff80ffff21d3aa}
{length = 14, bytes = 0x02a109ff 0163 ffff80ffff21acaa}
{length = 14, bytes = 0x02a109ff 011e ffff80ffff2167aa}
{length = 14, bytes = 0x02a109ff 0076 ffff80ffff21beaa}
{length = 14, bytes = 0x02a109ff 0000 ffff80ffff2148aa}
{length = 14, bytes = 0x02a109ff 0023 ffff80ffff216baa}
{length = 14, bytes = 0x02a109ff 006c ffff80ffff21b4aa}
{length = 14, bytes = 0x02a109ff 00fd ffff80ffff2145aa}

As you can see, the length of the data is 14 bytes:

0x 02 a1 09 ff 01 63 ff ff 80 ff ff 21 ac aa  = 14 bytes

Every byte contains a different value like:

  • weight
  • BMI
  • body fat
  • body water
  • etc…

Parse and extract the weight

In this tutorial I’ll show you how to get the weight, I left other parameters to you!

Create a new class, called HEXParser.swift and start adding this code.

First of all, you need a struct with the position of the byte. In this case, we need to get the weight that is at position 2 in the chunk of 4 bytes.

struct BytePositions {
     // 0 based
     let size = 4
     let weight = 2
 }
0x 02a1 09ff 00A3 0226 86ff ff21 1baa
   [0]  [1]  [2]  [3]  [4]  [5]  [6]

As you can see from the raw data the byte at position 2 is 00A3 that is in decimal 163.

Converting 163 to kg (163/10) you obtain 16,3 kg!

Adding Parser methods:

class HexParser {
     private var rawArray: [Character] = []

     // initialize with the BLE received raw data
     init( data: Data ) {
         rawArray = Array( data.hexEncodedString() )
     }

     // extract the weight in decimal
     func getWeight() -> Int {
         let cks = rawArray.chunked(into: 4)
         let hexString = String( cks[BytePositions().weight] )
         if let intValue = Int(hexString, radix: 16) {
             return intValue
         }
         return -1
     }
 }

Cool, almost completed!

Make it works

Come back in your ViewController and go in the Bluetooth delegate and paste the following code:

func didSensorReceived(data: Data, rssi: Int) {

     // initialize with raw data
     let hexParser = HexParser(data: data)

     // get weight and save data to your data-model
     let weight = hexParser.getWeight()
     if weight != -1 {
         self.weightModel.weight = Double(hexParser.getWeight()) / 10
         self.weightModel.refresh = Date()
     }

     // do what you want with the data-model, like
     // refreshing your UI, save to Health and so on...
     self.refreshModel()
 }

Here you can find also a helper extension of Data that I’ve created in order to convert the Data format info a HEX String format:

extension Data {

     struct HexEncodingOptions: OptionSet {
         let rawValue: Int
         static let upperCase = HexEncodingOptions(rawValue: 1 << 0)     }

     func hexEncodedString(options: HexEncodingOptions = []) -> String {
         let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
         return map { String(format: format, $0) }.joined()
     }    

 }

The used data model is this, very simple:

struct WeightModel {
     var weight: Double
     var refresh: Date = Date()
 }

Now you can create your own graphs and analytics or basically sends data to Health app in order to make Apple do analytics for you 🙂


TL;DR – My BEAUTIFUL application :

A simple app that save data to Apple Health and set a badge on your app with the latest weight measured.

Of course is not on App Store…

Very stupid, but the final scope is to save data to Health!


Enjoy byting!

Alberto Pasca