Beurer BG64

This scale can measure your weight and percentage of different body components. The control panel is recording 12 variables:

  1. weight kg
  2. body fat %
  3. water %
  4. muscles %
  5. bones kg
  6. date
  7. upper body fat % (not for BG64)
  8. lower body fat % (not for BG64)
  9. upper body muscles % (not for BG64)
  10. lower body muscles % (not for BG64)
  11. BMR
  12. AMR

All values are 16 bit words.
Up to 10 users can store thier data for 32 measurements.
Beacause date has no time only one measurement per day is possible.

The following python program produces a CSV file with all variables available on BG64 for the first user (can be specified as a command parameter):

#!/usr/bin/env python

"""
ID 04d9:8010 Holtek Semiconductor, Inc. 
"""
import usb.core
import usb.util as util
from array import array
import sys
from optparse import OptionParser, make_option
import pickle
from struct import unpack

dev = None

VID = 0x04d9
PID = 0x8010

# define HID constants

REQ_HID_GET_REPORT   = 0x01 
REQ_HID_GET_IDLE     = 0x02 
REQ_HID_GET_PROTOCOL = 0x03 

REQ_HID_SET_REPORT   = 0x09 
REQ_HID_SET_IDLE     = 0x0A 
REQ_HID_SET_PROTOCOL = 0x0B 

HID_REPORT_TYPE_INPUT   = 1<<8
HID_REPORT_TYPE_OUTPUT  = 2<<8
HID_REPORT_TYPE_FEATURE = 3<<8

def openDev():
    global dev
    dev = usb.core.find(idVendor=VID, idProduct=PID)
    if dev is None:
        raise ValueError('Device not found')

    if dev.is_kernel_driver_active(0):
        dev.detach_kernel_driver(0)
    dev.set_configuration() # do reset and set active conf. Must be done before claim.
    util.claim_interface(dev, None)

def setReport(reportId, data):
    r = dev.ctrl_transfer(
        util.build_request_type(util.CTRL_OUT, util.CTRL_TYPE_CLASS, util.CTRL_RECIPIENT_INTERFACE), # bmRequestType,
        REQ_HID_SET_REPORT,
        HID_REPORT_TYPE_OUTPUT | reportId,
        0,                 # feature_interface_num
        data,              # data
        3000               # timeout (1000 was too small for C_FREE)
    )
    return r

def read():
    openDev()
    try:
        dev.read(0x81,64) #flush 
    except usb.core.USBError:
        pass
    
    setReport(9, array('B',(0x10,0,0,0,0,0,0,0)))

    # Every user can have upto 12 variables
    # and additional 8*64 for user data
    r = []
    for i in xrange(120+8):
        x = dev.read(0x81,64)
        if not x:
            print >> sys.stderr, "Read failed"
            exit(1)
        if x[0]==0xff and (i<120):
            print >> sys.stderr,"Invalid data",x
            exit(1)
        r.append(x)    

    # Each variable can have upto 32 values
    frmt = "!" + "H"*32
    s = []
    for i in xrange(120) :
        x = unpack(frmt, r[i])
        
        # Variables 5 .. 10 are not available on my scale
        if i>5 and i<10:
            err = [item for item in x if item!=0]
        else:
            err = [item for item in x if item==0xffff]
        if len(err):
            print >> sys.stderr, "INVALID:", err
            exit(1)

        s.append(x)
    return s

def printUser(s, user):
    # Transpose from 12x32 to 32x12 and format

    j = 12*user
    for i in xrange(32):
        x = s[j+5][i] # date
        if not x:
           break
        print "{:d}-{:02d}-{:02d}T07:00:00 {:.1f} {:.1f} {:.1f} {:.1f} {:.1f} {:d} {:d}".format(
           1920+(x>>9), x>>5&0xf, x&0x1f,
           s[j+0][i]/10., s[j+1][i]/10., s[j+2][i]/10., s[j+3][i]/10., s[j+4][i]/10., s[j+10][i], s[j+11][i])

def main():

    option_list = [
        make_option("-c", "--cached", action="store_true", dest="cached"),
        #make_option("-w", "--write",action="store",      dest="output_file"),
        make_option('-u', '--user', action='store',      dest='user'),
    ]

    parser = OptionParser(option_list=option_list, usage='Usage: %prog [options]')
    options, args = parser.parse_args()

    if options.cached:
        with open("dump.pickle") as f: s = pickle.load(f)
    else:
        s = read()
        with open("dump.pickle","w") as f: pickle.dump(s,f)

    user = 0
    if options.user:
        user=int(options.user)-1

    printUser(s, user)


if __name__ == '__main__':
    main()


To call it enter at command prompt:

./beurer.py > user1.csv
21 comments
  1. Miloš said:

    This was very helpful.
    I just upgraded EasyFit on Windows and it stopped working. Couldn’t find the old version.

  2. Chris said:

    Thanks for your script!
    By adapting the line

    HID_REPORT_TYPE_OUTPUT | reportId, to:
    0x0300,

    I got my BF100 working, excellent thank you!

  3. Hans said:

    Thanks for your script. I try to adapt it to a Sanitas BGF 48 USB scale (having exactly the same Holtek ID 04d9:8010), but could not figure out the commands and data structure of the scale. Could you give me hint how you succeded to read them out? Thanks in advance.

  4. Nicolas said:

    to make it work for older python version i needed to add field names to get rid of the “ValueError: zero length field name in format” error:

    line 103-5:
    print “{0:d}-{1:02d}-{2:02d}T07:00:00 {3:.1f} {4:.1f} {5:.1f} {6:.1f} {7:.1f} {8:d} {9:d}”.format(1920+(x>>9), x>>5&0xf, x&0x1f,s[j+0][i]/10., s[j+1][i]/10., s[j+2][i]/10., s[j+3][i]/10., s[j+4][i]/10., s[j+10][i], s[j+11][i])

  5. Nicolas said:

    In order to be able to get multiple readings per day i’m running your script on a machine that’s on all the time, the beurer is connected using a switchable usb hub which you toggle on after weighing, wait for the usb end message and switch it off again. The script calls a slightly modified version of your python routine to add a timestamp and another routine to extract the most recent records only. Thanks for making this very easy, you’ve done all the hard work!

    • Stuart said:

      Hi Nicholas, can you give me any pointers please? I’m trying to run this script on a Mac and not having much luck! I have assumed that I need PyUsb and libusb as well as Python. Which version of python are you using for example?

    • Jiri168861b said:

      could you please make your modiffied script public available 🙂 i would like to use it with my sanitas sbf 48 usb 🙂 thank you

  6. I got the Sanitas SBF 48 working with some modifications to your script (thx for that!), it has 64 slots instead of 32 and it also saves date+time. But it does not calculate bones/AMR/BMR. Technically that’s all the differences to the Beurer BG 64.

    However, one last question (i don’t want to do 64 measurements): What happens after 64 measurements? The manual says it should say “full”. Do I have to delete every single value? Can this be done via usb?
    Sanitas software “Healthcare” does not seem to delete any entries in the scale itself, but I did not test what happens if all measurement slots for one person are used.

    Any experience on your beurer BG 64? Hopefully you already did 32 measurements 😉

    • Simple reading seems to clear the “Full” flag.

  7. Stuart said:

    Thanks for posting this script! I’m trying to access the Beurer BG 64 on my Mac and hoped this was the answer to my problem but i can’t get it to run properly in Terminal. Has anyone got any suggestions please? I have zero experience with Python i’m afraid, was just hoping to copy and paste to Terminal!

    Got this error message:
    Traceback (most recent call last):
    File “beurer64.py”, line 6, in
    import usb.core
    ImportError: No module named usb.core

  8. Scientist said:

    !?Sanitas SBF48 – Does anybody have already evaluated explicite commands ?

    Does this scale answer to “0x10,0,0,0,0,0,0,0”-command as the Beurer BG64 ?
    Don’t have a Windows system right now, to do the usbsnoop myself.
    Thanks for this prepared script and anybody,who may advise me, to use with Sanitas scale.

  9. Scientist said:

    Sanitas SBF 48: As mentioned from geekparadise: only Data-BLOB-Format differs:
    so scripts needs some adaptions:
    for i in xrange(6*10): x = dev.read(0x81,128) # weight kg , body fat % , water % , muscles % , date , time

    for i in xrange(6*10) : x = unpack(frmt, r[i])

    # Transpose from 10×64 to 64×10 and format

    j = 6*user
    for i in xrange(64):
    x = s[j+4][i] # date

    print “{:d}-{:02d}-{:02d}Zeit{:d}:{:d} {:.1f} {:.1f} {:.1f} {:.1f}”.format( 1920+(x>>9), x>>5&0xf, x&0x1f, s[j+5][i]>>8 &0xff,s[j+5][i] &0xff, s[j+0][i]/10., s[j+1][i]/10., s[j+2][i]/10., s[j+3][i]/10.)

    • Jörg said:

      I tried your suggested adaptations for Sanitas SBF48 but the script exits as invalid data is read. Can you explain, why your dev.read(0x81,128) has 128 as second parameter?
      Needs the frmt = “!” + “H”*64 to be adopted to 64 instead of 32 as well?
      Can you post your running file for SBF48 please?

      • Scientist said:

        You should try to understand a little bit, what is going on:
        Sure: there is an obsolete (Can’t find a situation – this needs to be checked ) hard exit in the original script: “#if x[0]==0xff and (i<120): … ; exit(1)" – remove/comment out and go on or set (i<60)
        "why your dev.read(0x81,128) has 128 as second parameter?"
        Beurer: 8KB-BLOB (120+8) * 64 Bytes ~ 32 16bit-Integers, Sanitas: 8KB-BLOB 60(+4)*128 (!64 Slots)
        ok: missed to mention frmt = "!" + "H"*64 -adaption in Line 78 – sorry
        By the way: Discussion on how to read the BLOB was already here: http://www.mikrocontroller.net/topic/352939 "Sanitas USB-Protokoll"

        One other point is, there is a V-usb-Signal from the Holtek USB-Serial-Modul ( Yes, it's somethink alike PL2302 and FTDI with internally having Wires to Serial (TxD/RxD,RTS/CTS) , which the Base-Scale-Microcontroller provides – but the USB is just HID-driver detected in this case, no real USB-Serial Driver – though there are 3 Jumpers on the Modul-PCB, which may change this ). Would have been nice, to control this V-usb directly (possibly to rewire to RTS or so) – while it's valid, the scale is in PC-Mode, no Weight-Operation possible.
        So you are on a workaround like Nicolas mentioned.

        @admin: Don't got the requested email on Comment !?

    • Jiri168861b said:

      after all mentioned modifications … script only returns Invalid data array(‘B’, [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0])

  10. Jiri168861b said:

    error on line 42 of your script … returns exception: File “/usr/local/lib/python2.7/dist-packages/usb/backend/libusb1.py”, line 811, in claim_interface
    _check(self.lib.libusb_claim_interface(dev_handle.handle, intf))
    ctypes.ArgumentError: argument 2: : wrong type

    could you give me any advice ? Thank you.

    • Jiri168861b said:

      util.claim_interface(dev, 0) instead of util.claim_interface(dev, None) propably solves that error …

Leave a reply to Jiri168861b Cancel reply