Creating a Python module for the Beurer BM 65 blood pressure monitor (Part I)

So, I just bought a brand spanking new blood pressure monitor. My wife is a nurse and we have been talking about getting one for some time. After browsing the market, we settled on a Beurer BM 65. It is a very nice piece of kit and it comes with a USB plug for PC connectivity. Exciting, right? Unfortunately, the software is Windows-only. Bummer..

Beurer BM 65

Beurer BM 65

Well, then it’s obviously my duty to reverse-engineer it. Let’s get started then! A quick Google search tells me that others have had success with the Beurer PM70 (a heart rate monitor) and success with the Beurer BG64 (a diagnostic scale). They seem to use different protocols, though.

Reverse engineering the protocol

When the BM 65 is plugged in, it enumerates as 067B:2303, which is a Prolific Technology PL-2303 USB-to-serial controller. Interesting.. The problem is therefore reduced to guessing the serial protocol it uses.

Output from dmesg

Output from dmesg

On my Linux box it gets mapped to /dev/ttyUSB0 with no issues, as this chip is supported in the kernel. But how to communicate with it? We need to sniff the protocol… Beurer provides a free Windows-only tool called Health Manager for communicating with the device, as well as a subset of their other products. Luckily, it’s possible to eavesdrop on serial ports in Windows, and my gaming rig runs Windows 7. There is a SysInternals tool for this called Portmon, but it seems to work very poorly on Win7 x64. Next, I tried a tool called API Monitor v2. As the communication with the device is through a fake serial port, we should be able to sniff the relevant Windows API calls.

Lo and behold! It works! It seems that SetCommState in Kernel32.dll is used to configure the COM port (4800 baud 8N1).

4800baud

The SetCommState call where the serial connection is set up to 4800 baud 8N1.

Let’s then see if we can deduce the actual communication.. After some digging around I successfully limited the captured API calls to just the file I/O stuff in Kernel32.dll. The Health Manager tool tries all available COM ports until it gets a correct response. After that, we just have to follow the yellow brick road of WriteFile and ReadFile API calls. It only writes 1 or 2 bytes per call (depending on the command) and all reads are single byte reads.

Serial data transfer

Serial data transfer

This API Monitor tool is a bit tedious for this, so I tried Serial Port Monitor by Eltima Software instead. It is a shareware program with a 14 day trial, but that’s enough for this purpose. A serial dump of a sequence of 3 measurements looks something like this:

Captured serial communication

Captured serial communication

In table form, transferring a set of three measurements goes like this:

Sent to deviceReceived from deviceMy interpretation
0xAAPing?
0x55Pong!
0xA4Get description
"Andon Blood Pressure Meter KD001"Device description
0xA2How many measurements?
0x033 measurements!
0xA3 0x01Get measurement 1
0xAC 0x66 0x37 0x4E 0x0A 0x11 0x16 0x2A 0x0DMeasurement 1!
0xA3 0x02Get measurement 2
0xAC 0x62 0x35 0x5F 0x0A 0x0E 0x12 0x0C 0x0DMeasurement 2!
0xA3 0x03Get measurement 3
0xAC 0x64 0x3D 0x55 0x0A 0x0C 0x0E 0x09 0x0DMeasurement 3!

After the last byte, the connection is terminated. Apparently, this device is made by a company called Andon. And it seems that it only transmits data about a single user at a time. Let’s have a look at a single measurement:

Byte valueMy interpretationDescription
0xAC0b10101100Status bits? Magic number?
0x66102 + 25 = 127 mmHgSystolic blood pressure (offset by 25)
0x3755 + 25 = 80 mmHgDiastolic blood pressure (offset by 25)
0x4E78 BPMPulse
0x0A10 = OctoberMonth
0x1117Day of month
0x1622Hours
0x2A42Minutes
0x0D13 = 2013Year

And Bob’s your uncle! We have now successfully reverse engineered the protocol. Well.. Almost.. I haven’t got a clue about the first byte of each measurement. It might be a magic number, but it’s probably some status bits. Besides blood pressure and pulse, the device also registers cardiac arrhythmia. If this information is recorded and if a measurement is always 9 bytes, it would have to be stored in these bits.

I mentioned that the device seemed to be made by Andon. After a bit of digging, I found some evidence for this. The document from dabl Educational Trust says:

“Andon is an OEM manufacturer for the BM 65. Despite the different designs, the BM 65 is functionally the same as the Andon KD-5915 with added dual user, averaging and uploading features but without the voiced results.”

It seems that some of the other Andon devices also support USB. I wonder if the protocol is the same as for the BM 65?

Implementing a Python module for the Beurer BM 65

Let’s make a rudimentary data downloader in Python using our newly acquired knowledge about the protocol. I’m using Python 2.7 on a reasonably new Linux Mint installation. The code is reasonably basic, omitting any kind of error handling.

For those who don’t want to copy code from here, you can also pull a copy from GitHub.

Disclaimer:
The code is free to use, but do so at your own risk.
If you brick your device, it’s not my problem.

import sys, serial

class Measurement(object):
    def __init__(self, data):
        self.header = data[0]
        self.systolic = data[1] + 25
        self.diastolic = data[2] + 25
        self.pulse =  data[3]
        self.month = data[4]
        self.day = data[5]
        self.hours = data[6]
        self.minutes = data[7]
        self.year = data[8] + 2000
        self.time = "{0}-{1:02}-{2:02} {3:02}:{4:02}".format(self.year,
                                                             self.month,
                                                             self.day,
                                                             self.hours,
                                                             self.minutes)

    def getBytes(self):
        return [self.header,
                self.systolic - 25,
                self.diastolic - 25,
                self.pulse,
                self.month,
                self.day,
                self.hours,
                self.minutes,
                self.year - 2000]

    def __repr__(self):
        hexBytes = ['0x{0:02X}'.format(byte) for byte in self.getBytes()]
        return "Measurement([{0}])".format(', '.join(hexBytes))

    def __str__(self):
        return "\n".join(["Header byte        : 0x{0:02X}",
                          "Time               : {1}",
                          "Systolic pressure  : {2} mmHg",
                          "Diastolic pressure : {3} mmHg",
                          "Pulse              : {4} BPM"]).format(self.header,
                                                                  self.time,
                                                                  self.systolic,
                                                                  self.diastolic,
                                                                  self.pulse)

class BeurerBM65(object):
    def __init__(self, port):
        self.port = port

    def sendBytes(self, connection, byteList, responseLength = 1):
        connection.write(''.join([chr(byte) for byte in byteList]))
        response = connection.read(responseLength)
        return [ord(char) for char in response]

    def bytesToString(self, bytes):
        return "".join([chr(byte) for byte in bytes])

    def getMeasurements(self):
        ser = serial.Serial(
            port = self.port,
            baudrate = 4800,
            parity = serial.PARITY_NONE,
            stopbits = serial.STOPBITS_ONE,
            bytesize = serial.EIGHTBITS,
            timeout = 1)

        pong = self.sendBytes(ser, [0xAA])
        print "Sent ping. Expected 0x55, got {0}".format(hex(pong[0]))

        description = self.bytesToString(self.sendBytes(ser, [0xA4], 32))
        print "Requested device description. Got '{0}'".format(description)

        measurementCount = self.sendBytes(ser, [0xA2])[0]
        print "Found {0} measurement(s)...".format(measurementCount)

        for idx in range(measurementCount):
            yield Measurement(self.sendBytes(ser, [0xA3, idx + 1], 9))

        print "Done. Closing connection..."
        ser.close()

if __name__ == "__main__":
    conn = BeurerBM65(sys.argv[1])
    for idx, measurement in enumerate(conn.getMeasurements()):
        print ""
        print "MEASUREMENT {0}".format(idx + 1)
        print measurement

Using the hardware on an Arduino UNO

In this post we’ll go though what it takes to configure some Arduino libraries for the nRF24L01 modules and the DHT22 sensors. The hardware will in both cases be an Arduino UNO with the perf board shield described in my last post.

How to probe a DHT22 sensor from an Arduino

Let’s start by looking at how to talk to a DHT22. The DHT22 sensor uses a homemade 1-wire protocol for communicating with its host. The data transmission is initiated by the host by pulling the data pin low for 20 ms. Then the host pulls the data pin high for 40 ms. Finally, the sensor will transmit 40 bits by pulsing the data pin. Short pulses (26-28 microseconds) mean 0 and long pulses (70 microseconds) mean 1. Bytes 0 and 1 are the relative humidity and bytes 2 and 3 are the temperature. In both cases the first byte is the integral part while the second byte is the decimal part. The final byte is a checksum, which is simple the least significant byte of the sum of the previous 4 bytes. See the data sheet for more details on this.

DHT22 protocol

DHT22 handshake (as shown in the data sheet)

Luckily, all the hard work has been taken care of. To get started, grab a copy of Adafruit’s DHT22 library and unpack it to your Arduino library folder. Then open the Arduino IDE and open the DHT/DHTtester example. The only piece of configuration needed is which pin the sensor’s data pin is connected to. In my case it is analog input pin A5, which can also be used as digital pin 19. I simply changed #define DHTPIN 2 to #define DHTPIN 19. Upload the compiled code and open the serial monitor. Remember to set the baud rate to 9600. If all went well, you should be greeted with something like this:

DHTxx test!
Humidity: 23.90 % Temperature: 26.20 *C
Humidity: 23.90 % Temperature: 26.20 *C
Humidity: 23.90 % Temperature: 26.20 *C
Humidity: 23.90 % Temperature: 26.20 *C
Humidity: 23.90 % Temperature: 26.20 *C
Humidity: 23.90 % Temperature: 26.20 *C
Humidity: 23.90 % Temperature: 26.20 *C
Humidity: 23.90 % Temperature: 26.20 *C
Humidity: 23.80 % Temperature: 26.20 *C

The library code looks simple enough to optimize for size. We don’t need all the bells and whistles for our sensor nodes.

How to make a configure and use an nRF24L01 radio module from an Arduino

There is a great data sheet for the chip at Nordic Semiconductor’s website. From this data sheet it’s entirely possible to write a library from scratch. But for prototyping it’s better to stand on the shoulders of giants instead of re-inventing the wheel. Let’s use an existing library.

As I’ve mentioned earlier, there are already great libraries for this online. Basically, there are two worth mentioning: Maniacbug’s RF24 library and the smaller Mirf library. We will be using the former as it’s the most complete of the two. I have noticed that there are several forks of this library on GitHub, so it may be best to shop around a bit to find the best match for our purposes. Download a zip and expand the contents to your Arduino libraries folder.

We are going to test radio connectivity using the RF24/pingpair_dyn example. There are lots of settings to configure, but as a bare minimum we need to configure which pin is CE, which pin is CSN, and the role of the node (i.e. if we are the pinging part or the ponging part of the pair). Conveniently, the defaults for CE and CSN are the ones I’m using (i.e. digital pins 9 and 10). This can be changed in line 26 where it says RF24 radio(9,10);. Remember that no matter which pins you use, you must have pin 10 configured as an output for the SPI hardware to work in master mode. Else, it will run in slave mode and the code won’t work.

The default behavior for the RF24/pingpair_dyn example is to be the pinging part. Let’s just keep it that way and configure the Raspberry Pi to be the ponging part. Now try to compile and upload the code. Without a pong-partner, the pings will just time out, but at least we know something is working. Remember that this test uses 57600 baud per default when using the serial monitor. The output should look something like this:

RF24/examples/pingpair_dyn/
ROLE: Ping out
STATUS = 0x0e RX_DR=0 TX_DS=0 MAX_RT=0 RX_P_NO=7 TX_FULL=0
RX_ADDR_P0-1 = 0xf0f0f0f0e1 0xf0f0f0f0d2
RX_ADDR_P2-5 = 0xc3 0xc4 0xc5 0xc6
TX_ADDR = 0xf0f0f0f0e1
RX_PW_P0-6 = 0x20 0x20 0x00 0x00 0x00 0x00
EN_AA = 0x3f
EN_RXADDR = 0x03
RF_CH = 0x4c
RF_SETUP = 0x07
CONFIG = 0x0f
DYNPD/FEATURE = 0x3f 0x04
Data Rate = 1MBPS
Model = nRF24L01+
CRC Length = 16 bits
PA Power = PA_HIGH
Now sending length 4...Failed, response timed out.
Now sending length 6...Failed, response timed out.
Now sending length 8...Failed, response timed out.
Now sending length 10...Failed, response timed out.

If something is wrong (e.g. if the SPI interface has been configured incorrectly), the code will fail to recognize the radio. Then the output in the serial monitor will look like this:

RF24/examples/pingpair_dyn/
ROLE: Ping out
STATUS = 0xff RX_DR=1 TX_DS=1 MAX_RT=1 RX_P_NO=7 TX_FULL=1
RX_ADDR_P0-1 = 0x0000000000 0x0000000000
RX_ADDR_P2-5 = 0xff 0xff 0xff 0xff
TX_ADDR = 0xffffffffff
RX_PW_P0-6 = 0x00 0x00 0x00 0x00 0x00 0x00
EN_AA = 0xff
EN_RXADDR = 0xff
RF_CH = 0xff
RF_SETUP = 0xff
CONFIG = 0xff
DYNPD/FEATURE = 0x00 0x00
Data Rate = 1MBPS
Model = nRF24L01
CRC Length = 16 bits
PA Power = PA_HIGH
Now sending length 4...Got response size=255 value=ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
Now sending length 6...Got response size=255 value=ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
Now sending length 8...Got response size=255 value=ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
Now sending length 10...Got response size=255 value=ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ

Assuming all went well, we are now done testing the Arduino libraries. Ultimately, they will be used as inspiration for the sensor node firmware. My next post will be about getting the Raspberry Pi to talk to the radio modules. In the end we should have a working ping/pong test running.

Making prototype perf board hardware

In my last post, I described the various electronic components in my soon-to-be wireless thermo- and hygrometer. In this post, we’ll look at how to wire up some prototype perf boards.

Connecting an nRF24L01 module and a DHT22 sensor to an Arduino

An nRF24L01 radio module communicates with its host using SPI. Luckily for us, the Arduino (as well as most of the Atmel AVR chips in general) supports SPI directly in the hardware. The hardware SPI pins on the Arduino UNO are digital pins 11 (MOSI), 12 (MISO), and 13 (SCK). Furthermore, we need to wire up the radio module’s pins for CSN (Chip Select), CE (Chip Enable), VCC (3.3V), and GND. There’s also a pin called IRQ, but it seems to be used less commonly. All I/O pins are 5V tolerant, but VCC must not be above 3.6V according to the datasheet. This means that we don’t need to have a level-converter chip in order to use the radio module with an Arduino.

nRF24L01

nRF24L01

I connected the module to my Arduino board like this:

nRF24L01 pin: Arduino pin:
1 (GND) GND
2 (VCC) 3.3V
3 (CE) Digital pin 9
4 (CSN) Digital pin 10
5 (SCK) Digital pin 13
6 (MOSI) Digital pin 11
7 (MISO) Digital pin 12
8 (IRQ) Digital pin 8

Please note that the SPI pins are totally different on Arduino Leonardo boards, where they can only be accessed through the ICSP pin header. Backward compatibility FTW…

The DHT22 sensor is much simpler to hook up. It has four pins, but we only need three of them. I would have liked to use one of the remaining “traditional” digital pins on the Arduino, but due to the infamous pin header alignment bug, it’s much easier to reconfigure one of the analog input pins as a digital pin when using perf board. I have used pin A5, which is also known as digital pin 19.

DHT22 pin: Arduino pin:
1 (VCC) 5V (3.3V would probably also work)
2 (DATA) Digital pin 19
3 (N/C)
4 (GND) GND

According to the datasheet, there’s supposed to be a pull-up resistor from the data pin to VCC. In practice it seems that the weak internal pull-up in the ATMEGA chip is enough. The sensor uses a weird 1-wire protocol on the data-pin, but fortunately we have the library provided by Adafruit.

With this knowledge it is just a matter of finding a suitable piece of perf board and the various needed bits and pieces. I must say that I never thought I would find a purpose for this exact piece of scrap perf board, but in this case it’s perfect.

Arduino shield

Arduino shield

When mounted it looks like this:

Arduino with nRF24L01 and DHT22

Arduino with nRF24L01 and DHT22

Connecting an nRF24L01 module to a Raspberry PI

Now we are ready for part two of this post. Again, we are going to use the hardware SPI port provided by the hardware. All the pins we need are located in the big 26-pin P1 header. There’s a nice write-up about P1 (and the other headers) on eLinux.org. I have connected the nRF24L01 module like this:

nRF24L01 pin: Raspberry PI pin (P1 header):
1 (GND) 20 (GND)
2 (VCC) 17 (3.3V)
3 (CE) 16 (GPIO 23)
4 (CSN) 18 (GPIO 24)
5 (SCK) 23 (SCLK / GPIO 11)
6 (MOSI) 19 (MOSI / GPIO 10)
7 (MISO) 21 (MISO / GPIO 9)
8 (IRQ)

This is mostly based on the documentation for this code I found while researching how to connect the parts. It is a Raspberry Pi port of a Beagleboard port of Maniacbug’s nice RF24 library for Arduino. The author writes that he doesn’t know if it works or not. Well it doesn’t. Not right away at least. It took some work to get running, but I’ll get back to that in the next post.

My adaptor board:

nRF24L01 adaptor board for Raspberry Pi

nRF24L01 adaptor board for Raspberry Pi

And mounted on the Pi with a radio module:

Mounted adaptor board

Mounted nRF24L01 adaptor board on Raspberry Pi

That’s it! Our prototype hardware is finished. In the next post I’ll explain how to get a simple ping/pong test working.

New Project: Wirelesss thermo- and hygrometer

Let’s start this blog out by looking at a project..

So, wouldn’t it be cool to have the ultimate open source (well, mostly) indoor/outdoor thermo- and hygrometer with multiple wireless sensors and a sleek HTML5 based interface? Of course it would! I’m not the first one to make one of these. In fact, the guy who made the Arduino library for the radio modules I’m going to use has made something very similar: Low-Power Wireless Sensor Node

I often pick my projects in order to learn some new skill or technology. But in this case there’s also a potentially useful gadget in the end. The new technologies for me this time will be energy-efficient embedded design, wireless communications, backend design (perhaps even some NoSQL), and a dynamically updated HTML5 frontend. Oh, and responsive design. It must look nice on both “real” computers and tablets/phones.

Some preliminary features and specifications:

  • Raspberry PI as base station
  • nRF24L01+ based radio modules
  • DHT22 temperature & humidity sensors
  • Battery-powered Atmel ATTiny based sensor boards
  • Arduino Uno as prototype platform for the sensor boards
  • HTML5 based frontend (WebSockets, SVG graphics, …)
  • Database backend that stores all measurements (perhaps MongoDB)

Let’s look over these features one by one.

Raspberry PI as base station

RPI

Raspberry PI – Model B

Every hobby project needs either a Raspberry PI or an Arduino to score blogosphere points, amirite? Besides, it’s also offers good value for money. At Open Space Aarhus, my friendly local hackerspace, we recently did a group-buy of these. We ended up paying 275 DKK (approx. $47) including taxes and shipping for a B-model. That’s not too bad considering the Danish 25% VAT..

But why a Raspberry Pi, you might ask. Well, besides its relatively powerful CPU, good Linux support, and built-in LAN interface, it also provides hardware SPI pins in its GPIO header. This is very useful for communicating with the radio modules described below.

nRF24L01+ based radio modules

nRF24L01+ module

nRF24L01+ module

I used to think that 2.4GHz wireless modules were too expensive for a project like this. Not anymore! These bad boys cost me $1.20 apiece on Ebay. I got 10 of them, so if I don’t break any I’ll end out with 10 wireless sensor modules. The nRF24L01 chip (and cheap modules) has become quite popular among hobbyists it seems. Well, count me in. So instead of messing around with RF design and other low-level radio stuff, I can concentrate on a nice and tidy SPI based interface where all the gritty details have been taken care of. It’s even battery-friendly due to its multiple sleep modes. More on this later…

nRF24L01+ module with PA and LNA

nRF24L01+ module with PA and LNA

For the base station I’ve chosen a much more potent radio module. It uses the same chip, but with additional amplifiers and a proper antenna. It cost me more than all the other radio modules combined, but it’s an attempt at extending the range of the system. Besides the amplifiers, the board is more or less identical to the others. The pin header is exactly the same.

Of course, someone already implemented an Arduino library for these modules: Getting Started with nRF24L01+ on Arduino

It has even been ported to work with the SPI hardware on the Raspberry PI: RF24RP

It took me a bit of debugging to get the example code running on the Pi. I’ll get back to that in a later post.

DHT22 temperature & humidity sensors

AM2302

AM2302

There’s not much sense in making sensor nodes without sensors. I considered several alternatives until I noticed the DHT22 sensor type. It’s a combined temperature and humidity sensor with a very good range (0…100% RH and -40…125 degrees Celsius) and adequate precision for this purpose (+/- 2-5% RH and +/- 0.5 degrees Celcius).

I found a great deal on AM2302 sensors on Ebay. They are basically the same as DHT22. My preliminary tests indicate that they are sufficiently accurate.

Adafruit has made an Arduino library for it: DHT-sensor-library

Battery-powered Atmel ATTiny based sensor boards

In order for this project to be a success, the sensor nodes must be able to run for several months between battey changes. It might be better to pursue other architectures than Atmel AVR (e.g. the very power efficient TI MSP430 series), but the Atmel MCUs are familiar to me.

At some point I will draw up some PCBs for this. Stay tuned…

Arduino Uno as prototype platform for the sensor boards

Arduino Uno

Arduino Uno

At the early stages of the project, it’s nice to have a simple prototype platform. I’m using an Arduino for this. When the code starts to mature, I’ll switch to a custom prototype board based on an ATTiny chip.

HTML5 based frontend (WebSockets, SVG graphics, …)

Continuously updating sensor measurements provide a nice real-life data source for a dynamic HTML5 website. More on this later… 

Database backend that stores all measurements (perhaps MongoDB)

I want to store all measurements in order to make various statistics. Both simple stuff like the temperature development during the last 24 hours as well as more advanced calculations are interesting. More on this later…

Next stop: Make prototype perf board hardware

Hello world!

Hello everybody!

On this blog I’ll document my various weird projects as they progress. I’ll also create pages for my other projects. Oh, and random ramblings! Don’t forget the ramblings.

I don’t assume that I’ll ever get a huge following on this blog. That’s OK. It’s mostly for documenting my own projects. As far as possible, everything I make and post here will be open sourced and put on GitHub or similar repository sites. As we say at my friendly local hackerspace Open Space Aarhus:

BUILD WHAT YOU NEED, SHARE WHAT YOU BUILD, BE AWESOME.

Wise words indeed. Let’s get started!

/atbrask