Thursday, 15 December 2016

Beurer BM55 and BF480 USB communication

Introduction


Beurer BM55 
This device is designed to measure Systolic and Diastolic pressure. Additionally it has features like pulse rate measurement and arrhythmia indicator. It also maintains the time at which the measurements are taken. This device has a capability to measure and maintain 60 readings of 2 users namely 'A' and 'B'.

Please refer below link for more details:









Beurer BF480


This device is designed to measure body weight, body fat, muscles and water content percentages. It also maintains the time at which the measurements are taken.  This device has a capability to measure and maintain 60 readings of 10 users.
Please refer below link for more details:










The collected readings for both the devices can be transferred to PC/mobile phones over the USB. This blog explains the protocol used to communicate with USB devices and provides the java code which collects the readings.


Procedure for USB communication

  1. Find the device using Product id and Vendor id
  2. Get USB connection to the device
  3. Open the USB connection
  4. Initialize the device so that it is ready to transfert the data to the requested system (PC/mobile)
  5. Get number of reading that are available in this device so that you can iterate that many times (if needed)
  6. Read the stored measurements and add them to a collection
  7. Terminate the device communication (if needed)
  8. Close the USB connection
Code is available in Git repository - Beurer BM55 and BF480 USB communication


This code has been integrated into MediPi which is an initiative started by NHS digital. MediPi is an open source software which is being piloted at Hertfordshire trust in England. I have contributed to this open source project by designing & developing Clinical front end monitoring system and made this open source software more robust.

Please refer below links for detailed information about MediPi:
Information: www.medipi.org
Source code: https://github.com/rprobinson/MediPi/tree/master/MediPiPatient

  • Beurer BM55

    Below code snippet can be used to transfer the measurements from Beurer BM55 to a java program over USB.

     public Collection<?> getMeasurements(String user) throws DeviceNotFoundException, DeviceConnectionException, SecurityException, UsbException, InterruptedException {  
             //User A or B for which readings needs to be transferred  
             BM55User readingsUser = BM55User.valueOf(user);  
             List<BM55Measurement> measurements = new ArrayList<BM55Measurement>();  
       
             //Find Beurer BM55 USB device with VENDOR_ID = (short) 0x0c45 and PRODUCT_ID = (short) 0x7406  
             UsbDevice device = getUSBDevice(VENDOR_ID, PRODUCT_ID);  
       
             //Get USB connection with interface number 0 and endpoint number -127  
             UsbPipe connectionPipe = getUSBConnection(device, 0, -127);  
             byte requestType = 33;  
             byte request = 0x09;  
             short value = 521;  
             short index = 0;  
       
             //Get usbControl with the above specified parameter. This object is needed to send data to the USB device  
             UsbControlIrp usbControl = getUSBControl(device, requestType, request, value, index);  
             try {  
       
                 //Open the USB communication  
                 connectionPipe.open();  
       
                 //prepare the device to communicate over the USB by writing {0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} byte array to device.  
                 //Read the data available on read port after writing data to the USB device.  
                 initialiseDevice(device, usbControl, connectionPipe);  
       
                 //Get the number of readings available in the device by writing {0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} byte array to device.  
                 //Read the data available on read port after writing data to the USB device. This read data represents number of readings available with the device irrespective of user A & B (It will return total readings of A+B).  
                 int numberOfReadings = getNumberOfReadings(device, usbControl, connectionPipe);  
       
                 BM55Measurement measurement;  
                 byte[] data;  
       
                 //Iterate for the numberOfReadings returned  
                 for(int readingsCounter = 1; readingsCounter < numberOfReadings; readingsCounter++) {  
                     //To get each reading write {0xA3, (byte) readingsCounter, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} byte to device.  
                     writeDataToInterface(device, usbControl, new byte[] {(byte) 0xA3, (byte) readingsCounter}, BM55USBService.DEFAULT_BYTE_ARRAY_LENGTH_8, BM55USBService.PADDING_BYTE_0xF4);  
       
                     //Read the data available on read port after writing above data to the USB device. This read data represents the byte array of {readingCounter} measurement stored in the device.  
                     data = readData(connectionPipe, 8);  
       
                     //Pass the received byte array to BM55Measurement constructor which will decode the byte array to different attributes.  
                     measurement = new BM55Measurement(data);  
       
                     //Add this measurement to the list only if the user passed to this method matches with decoded BM55Measurement user attribute.  
                     if(measurement.getUser().equals(readingsUser)) {  
                         measurements.add(new BM55Measurement(data));  
                     }  
                 }  
       
                 //Once all the measurements are captured, terminate the device communication by writing {0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} byte array to device.  
                 terminateDeviceCommunication(device, usbControl, connectionPipe);  
             } finally {  
                 //Close the USB device communication  
                 if(connectionPipe != null && connectionPipe.isOpen()) {  
                     try {  
                         connectionPipe.close();  
                         connectionPipe.getUsbEndpoint().getUsbInterface().release();  
                     } catch(UsbException e) {  
                         //Do nothing  
                     }  
                 }  
             }  
             return measurements;  
         }  
    

  • BM55Measurement constructor:

     public BM55Measurement(final byte[] reading) {  
             //Systolic pressure is byte[0] + 25  
             this.systolicPressure = BytesManipulator.getUnsignedInteger((byte) (reading[0] + 25));  
       
             //Diastolic pressure is byte[0] + 25  
             this.diastolicPressure = BytesManipulator.getUnsignedInteger((byte) (reading[1] + 25));  
       
             //Pulse rate is byte[2]  
             this.pulseRate = BytesManipulator.getUnsignedInteger(reading[2]);  
       
             int day = 0;  
             int month = 0;  
             int year = 0;  
             int hour = 0;  
             int minute = 0;  
       
             //if byte[3] < 0 then the resting indicator is on and the measured month would be byte[3] + 128  
             if(reading[3] < 0) {  
                 this.restingIndicator = true;  
                 month = reading[3] + 128;  
             } else {  
                 month = reading[3];  
             }  
       
             //if byte[4] < 0 then B is the user for which the measurement was taken and the measured day would be byte[4] + 128  
             //else the measurement taken was for user A and measured day would be byte[4]  
             if(reading[4] < 0) {  
                 this.user = BM55User.B;  
                 day = reading[4] + 128;  
             } else {  
                 this.user = BM55User.A;  
                 day = reading[4];  
             }  
       
             //Hour and minutes are in simple representation of byte[5] and byte[6] respectively  
             hour = reading[5];  
             minute = reading[6];  
       
             //if byte[7] < 0 then the arrhythmia indicator is on and the measured year would be byte[7] + 2000 + 128  
             //else the arrhythmia indicator is off and the measured year would be byte[7] + 2000  
             if(reading[7] < 0) {  
                 this.arrhythmia = true;  
                 year = reading[7] + 128 + 2000;  
             } else {  
                 year = reading[7] + 2000;  
             }  
       
             //Convert the yyyy-mm-dd HH:mm to ZonedDateTime or you can convert this to simple java.util.Date or java.sql.Timestamp  
             try {  
                 ZonedDateTime zdt = LocalDateTime.of(year, month, day, hour, minute).atZone(ZoneId.of("Z"));  
                 this.measuredTime = zdt.toInstant();  
       
             } catch(final Exception e) {  
                 e.printStackTrace();  
             }  
         }  
    
  • Beurer BF480

  • Below code snippet can be used to transfer the measurements from Beurer BF480 to a java program over USB.

     public Collection<?> getMeasurements(String user) throws DeviceNotFoundException, DeviceConnectionException, SecurityException, UsbException {  
             //User number (1 to 10) for which readings needs to be transferred  
             int userNumber = Integer.valueOf(user);  
       
             //This variable will be used to identify starting point in a transposed array for the given user  
             int readingStartByteNumber = (userNumber - 1) * 6;  
       
             //Find Beurer BM55 USB device with VENDOR_ID = (short) 0x04d9 and PRODUCT_ID = (short) 0x8010  
             List<BF480Measurement> measurements = new ArrayList<BF480Measurement>();  
             UsbDevice device = getUSBDevice(VENDOR_ID, PRODUCT_ID);  
       
             //Get USB connection with interface number 0 and endpoint number -127  
             UsbPipe connectionPipe = getUSBConnection(device, 0, -127);  
             byte requestType = 33;  
             byte request = 0x09;  
             short value = 521;  
             short index = 0;  
       
             //Get usbControl with the above specified parameter. This object is needed to send data to the USB device  
             UsbControlIrp usbControl = getUSBControl(device, requestType, request, value, index);  
             try {  
       
                 //Open the USB communication  
                 connectionPipe.open();  
       
                 //prepare the device to communicate over the USB by writing {0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} byte array to device.  
                 //Read the data available on read port after writing data to the USB device.  
                 initialiseDevice(device, usbControl, connectionPipe);  
       
                 //No need to get number of readings as maximum number of readings for this device are 64  
       
                 //Read the data from USB device by iterating 64 times and collect the readings in 128 byte array  
                 //Read 128 x 64 data from serial interface  
                 byte[][] rawReadings = new byte[BF480USBService.MAX_NUMBER_OF_READINGS][BF480USBService.BYTE_ARRAY_LENGTH_128];  
                 for(int readingsCounter = 0; readingsCounter < BF480USBService.MAX_NUMBER_OF_READINGS; readingsCounter++) {  
                     rawReadings[readingsCounter] = readData(connectionPipe, 128);  
                 }  
       
                 //Each value is represented with 2 adjacent bytes. Convert 2 adjacent bytes to one integer. e.g. [W0, W0, W1, W1,...W59,W59] will be converted to [W0,W1,... W59]  
                 //Convert 128 x 64 data to 64 x 64  
                 int[][] readings = new int[BF480USBService.MAX_NUMBER_OF_READINGS][BF480USBService.BYTE_ARRAY_LENGTH_128 / 2];  
                 for(int readingsCounter = 0; readingsCounter < BF480USBService.MAX_NUMBER_OF_READINGS; readingsCounter++) {  
                     readings[readingsCounter] = BytesManipulator.convertBytesToIntegers(rawReadings[readingsCounter]);  
                 }  
       
                 //Transpose the data matrix to retrieve each patients information  
                 int[][] userReadings = BytesManipulator.transpose(readings);  
       
       
                 //convert all 64 readings to an object by iterating over rows of the matrix  
                 for(int readingsCounter = 0; readingsCounter < BF480USBService.MAX_NUMBER_OF_READINGS; readingsCounter++) {  
                     if(userReadings[readingsCounter][readingStartByteNumber + 4] == 0) {  
                         //break when it is indicated that there are no more readings available  
                         break;  
                     }  
                     //Pass the transposed byte array along with starting position for the give user to BF480Measurement constructor which will decode the byte array to different parameters of selected measurement.  
                     measurements.add(new BF480Measurement(userReadings[readingsCounter], readingStartByteNumber));  
                 }  
                 //No need to terminate the device communication for BF480  
       
                 //Sort the readings according to measured time  
                 Collections.sort(measurements);  
             } finally {  
                 //Close the USB device communication  
                 if(connectionPipe != null && connectionPipe.isOpen()) {  
                     try {  
                         connectionPipe.close();  
                         connectionPipe.getUsbEndpoint().getUsbInterface().release();  
                     } catch(UsbException e) {  
                         //Do nothing  
                     }  
                 }  
             }  
             return measurements;  
         }  
    

    Data received over USB from beurer BF480 with byte array matrix representation

    BF480Measurement constructor:

         public BF480Measurement(final int[] reading, final int readingStartByteNumber) {  
       
             //Representation of transposed row passed to this constructor  
             //startingbyte=0 |startingbyte=6 |        |startingbyte=54  
             //[        User 1        |         User 2        |               |        User 10        ]  
             //[W0, B0, w0, M0, D0, T0, W0, B0, w0, M0, D0, T0, . . …………………….., W0, B0, w0, M0, D0, T0]  
       
             //Measurement timestamp conversion :START  
             final int date = reading[readingStartByteNumber + 4];  
             final int time = reading[readingStartByteNumber + 5];  
       
             //Calculate the year by right shifting date by 9 + 1920  
             final int year = 1920 + (date >> 9);  
       
             //Calculate the month by right shifting date by 5 & 0xf  
             final int month = date >> 5 & 0xf;  
       
             //Calculate the day by date & 0x1f  
             final int day = date & 0x1f;  
       
             //Calculate the hour by right shifting time by 8  
             final int hour = time >> 8;  
       
             //Calculate the minute by time & 0xff  
             final int minute = time & 0xff;  
       
             //Convert the yyyy-mm-dd HH:mm to ZonedDateTime or you can convert this to simple java.util.Date or java.sql.Timestamp  
             try {  
                 ZonedDateTime zdt = LocalDateTime.of(year, month, day, hour, minute).atZone(ZoneId.of("Z"));  
                 this.measuredTime = zdt.toInstant();  
             } catch(final Exception e) {  
                 e.printStackTrace();  
             }  
       
             //Measurement timestamp conversion :END  
       
             //Assign double values to weight, body fat %, water % and muscle %  
             this.weight = Double.valueOf(reading[readingStartByteNumber + 0]) / 10;  
             this.bodyFat = Double.valueOf(reading[readingStartByteNumber + 1]) / 10;  
             this.water = Double.valueOf(reading[readingStartByteNumber + 2]) / 10;  
             this.muscles = Double.valueOf(reading[readingStartByteNumber + 3]) / 10;  
         }