Trybotics Logo

Pulse Oximeter Data Capture With Raspberry Pi

DESCRIPTION

My daughter has some health issues that requires her to be hooked up to a pulse oximeter at night that monitors her oxygen level and heart rate. We have night nurses so we can sleep, but sometimes we wake up at night and want to "check her numbers" without going into her bedroom and possibly waking her up (our daughter is usually a very light sleeper). The pulseox does have (very loud) alarms that go off according to limits we set, but there are subtle trends that we may watch for according to what type of day she had. Again, the standard way to check these in the middle of the night would be to walk into her room, look at the current numbers and listen to how she is breathing, and then ask the nurse about any "non-alarm" trends she may have noticed. I knew there had to be a better way to do this.

The simplest approach seemed to just put a baby monitor with video in front of the pulseox device and then we could bring up the camera on our phone to see the numbers. Besides the cost and quality issues of most nightime baby monitors, there did not seem to be a location in the room to put the baby monitor that would not either block the nurse's view of the numbers or be too far away from our daughter for the audio to pick up well. We also already own an audio baby monitor that works very well, so it seems a waste to buy another one just to add the remote video to see the current numbers and not even tell us average/trend information.

The device that the home health company provides us is a Masimo RAD-8. It has a serial/RS232 port on the back of it. I was happy to find that the data coming off the port is not protected, so getting the data from the pulseox device to a remote display seemed possible (similar to how they do it at a hospital's nurse station). I chose to use a Raspberry Pi to capture the data, process it, and present it on a web server where we could then load a web page with our phone. The main reasons for choosing this device were low cost, small size, and it has an established community in case I ran into any problems.

Description:

  1. Masimo RAD-8 Pulse Oximeter
  2. Raspberry Pi Model B+ with power adapter
  3. Wifi dongle (optional)
  4. RPi case(optional)
  5. 8GB MicroSD (you can find these with Raspbian pre-installed)
  6. USB->Serial/RS232 Adapter Cable (I had one already from an old exercise bike, but here is a similar one on Amazon for ~$20. Note that the chipset in mine is in the FTDI D2XX family. This seems to be a popular chipset and it does work on RPi, but other cables/chipsets may not)
  7. RS232 extension (optional)

You can buy kits online that package #2-5 above for $50-$60. If you don't have the #6 like I did and want the #7 extension cable, this entire project from scratch will cost about $90. I am not including the cost of the actual pulse oximeter.

Description:

On the Raspberry Pi:

Optional software used:

All of this software is free. I won't go into detail on how to configure all the layers as there are install guides from each link above. Most were installed simply using the "sudo apt-get install " command, as the default respositories in Raspbian were able to find them.

The zip file attached to this step contains 2 files

  • poxs.php is the script for the background process that collect data and inserts into the database
  • gpulse.php is the web page code that retrieves from the database and displays output

Attachments

Description:

First I need to see the format of raw data coming out of the machine. I hooked up the usb->serial cable from the RPi to the back of the pulseox machine, put the sensor on my finger and turned on it.

I needed to know the name of the usb->serial adapter device on the RPi so I could watch it for incoming data. I found this with the following commands:

pi@raspberrypi ~ $ lsusb
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp.
Bus 001 Device 004: ID 148f:5370 Ralink Technology, Corp. RT5370 Wireless Adapter
Bus 001 Device 005: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC

The last line was obviously my device and now I know the type of chip in it and who made it. There was a device listed in the /dev directory called ttyUSB0, so this was probably the right one. To confirm, I checked the output from dmesg just after connecting the cable:

pi@raspberrypi ~ $ dmesg | grep FTD
[    3.711017] usb 1-1.5: Manufacturer: FTDI
[    3.718342] usb 1-1.5: SerialNumber: FTDDCQ7W
[    9.429993] usbserial: USB Serial support registered for FTDI USB Serial Device
[    9.929322] ftdi_sio 1-1.5:1.0: FTDI USB Serial Device converter detected
[   10.722481] usb 1-1.5: FTDI USB Serial Device converter now attached to ttyUSB0

Every terminal device has specific settings it expects, such as connect speed, bits per character, etc. I found an Operator's Manual for this pulseox through a Google search that gave me the info I needed to setup using the stty command:

  • Baud Rate: 9600
  • Bits per character: 8
  • Parity: None
  • Bits: 1 start, 1 stop
  • Handshaking: None

Even if I didn't find these settings from a web search, they are fairly common (8-N-1) settings for a device like this, so some settings can be left default or guessed in lieu of a spec, without impact to the simple code for this project. The command needed to setup according to above:

stty -F /dev/ttyUSB0 9600 cs8 -parenb -cstopb -crtscts

The last thing to do is use the cat command to check for output:

cat /dev/ttyUSB0

12/30/14 22:40:35 SN=0000057681 SPO2=096% BPM=106 PI=01.68% SPCO=--.-% SPMET=--.-% DESAT=-- PIDELTA=+-- ALARM=0000 EXC=000800

Great, so the data looks pretty simple to parse, feed into a database, and present on a web page. Besides the date/time, the only pieces that I want right now are percent oxygen saturation (spo2) and heart rate (bpm). If anyone knows the purpose of the other fields, I have some academic curiousity but don't need them for this project.

Description:

SQLite is a very simple to use database. From the command line I executed the following:

sqlite3 pulseox.db "CREATE TABLE pulseox(stampdate text, stamptime text, spo2 text, bpm text, id integer primary key)"

This will create a file called pulseox.db in the current directory that contains a single table as defined above. You will notice that I am being pretty lazy using text datatypes. I could parse the text as it comes off the serial port and convert it to a specific type, but my php and javascript code for selecting and displaying the data doesn't care much about datatypes, so I decided to leave the table definition as simple as possible. I may change it later if it causes a problem.

To test the database and the id column, I issued the following insert/select command:

sqlite3 pulseoxdb "INSERT into pulseox(stampdate, stamptime, spo2, bpm) values('12/30/14','08:00:00','SPO2=100%','BPM=100');SELECT * from pulseox"

12/30/14|08:00:00|SPO2=100%|BPM=100|1

Now that the database is setup, I can move on to parsing realtime data and inserting it into this table.

Description:

I am running a shell script as a background process that opens the serial device and continually checks for data. When it reads a line, it will chop it up (from the raw data, a single space is the delimiter) and insert a row into the database. I will be using PHP as the scripting language and PDO as the api extension to connect to the database. Below are the just the key parts of code - the entire file is called poxs.php and is in the zip file attached to the "Software" step above.

$ser = fopen("/dev/ttyUSB0","r");

$dbh=new PDO('sqlite:/var/www/pulseox.db');
$query=$dbh->prepare("INSERT into pulseox (stampdate, stamptime, spo2, bpm) VALUES ( :param0, :param1, :param2, :param3);");

while (!feof($ser)) {
  $buffer = fgets($ser);
  $din = explode(" ", $buffer);
  $query->bindParam(':param0', $din[0]); # stampdate - 12/30/14
  $query->bindParam(':param1', $din[1]); # stamptime - 08:00:00
  $query->bindParam(':param2', $din[3]); # spo2 - SPO2=100%
  $query->bindParam(':param3', $din[4]); # bpm - BPM=123
  $query->execute();
}

For brevity, I have removed error detection from the code above, such as checking if the serial device could not be opened, or checking for various database errors.

As each line is read, the "explode" command chops the line into each piece of data. Then each piece of data is bound to the correct location in the database SQL statement before executing the SQL.

Description:

I want to display the last reported oxygen level and heart rate on a web page. First I put some PHP code into a simple web page to retrieve the data. In the next step I will improve the presentation:

$dbh = new PDO('sqlite:/var/www/pulseox.db');
$query=$dbh->prepare("SELECT stampdate, stamptime, spo2, bpm from pulseox order by id desc limit 1");
$query->execute();
$result=$query->fetch();

echo "$result[0] $result[1] $result[2] $result[3]";

So far, we only get the following result:

01/03/15 07:33:18 SPO2=094% BPM=090

I also want to show on the web page the average oxygen level and heart rate over the last hour. The following code is how I get those 2 values:

$query=$dbh->prepare("SELECT round(avg(substr(bpm,5,3)),1), round(avg(substr(spo2,6,3)),1) from pulseox where bpm != 'BPM=---' and spo2 != 'SPO2=---%' and id > ((select max(id) from pulseox) - 3600)");
$query->execute();
$result=$query->fetch();

$avg_bpm = $result[0];
$avg_spo2 = $result[1];

A few notes on the above code:

  • The "substr" functions are used to strip down the data in the table ("SPO2=094%") down to usable numbers ("94")
  • The "max(id)... minus 3600" find the latest entry and backs up 1 hour / 3600 rows, as there is usually 1 reading per second.
  • The "round" function makes sure we calculate to 1 decimal place to right, i.e. 94.5
  • The filters "where bpm/spo2 !=" gets rid of rows where the pulseox machine was collecting data but had no data. This can happen when the sensor is not on correctly or needs to be replaced from wear.

Description:

Now that I have our 4 pieces of data (bpm, spo2, avg bpm, avg spo2), I can use gauges to visualize the results in relation to value ranges that our daughter usually has. After looking around at different libraries, I chose to use Google Visualization for this. It seemed pretty straightforward Javascript to use, had the features I wanted, and did not require any local install on the web server.

The php code in the previous step that generated the data will be used to set the values on these gauges. Below is the code that converts the php results to a format that Google Visualization can understand. This code also set the text label for each gauge.

var bpm_data = google.visualization.arrayToDataTable([['Label', 'Value'],['BPM', <?php echo $chart_bpm_data; ?>]]);
var spo2_data = google.visualization.arrayToDataTable([['Label', 'Value'],['SPO2', <?php echo $chart_spo2_data; ?>]]);
var avg_bpm_data = google.visualization.arrayToDataTable([['Label', 'Value'],['AVG BPM', <?php echo $chart_avg_bpm_data; ?>]]);
var avg_spo2_data = google.visualization.arrayToDataTable([['Label', 'Value'],['AVG SPO2', <?php echo $chart_avg_spo2_data; ?>]]);

Next up is setting the color ranges on the gauges - remember that higher is better for oxygen levels, but lower is (generally) better for heart rate:

var BPM_Chart_Options = {
  min: 0, max: 200,
  greenFrom: 60, greenTo: 130,
  redFrom: 160, redTo: 200,
  yellowFrom:130, yellowTo: 160,
  minorTicks: 5,
};
        		    
var SPO2_Chart_Options = {
  min: 0, max: 100,
  greenFrom: 90, greenTo: 100,
  redFrom: 50, redTo: 80,
  yellowFrom:80, yellowTo: 90,
  minorTicks: 5,
};

Finally I use the following Javascript to create the actual gauges, and then assign the data/options from above to each:

var bpm_chart = new google.visualization.Gauge(document.getElementById('chart1'));
bpm_chart.draw(bpm_data, BPM_Chart_Options);

var spo2_chart = new google.visualization.Gauge(document.getElementById('chart2'));
spo2_chart.draw(spo2_data, SPO2_Chart_Options);

var avg_bpm_chart = new google.visualization.Gauge(document.getElementById('chart3'));
avg_bpm_chart.draw(avg_bpm_data, BPM_Chart_Options);

var avg_spo2_chart = new google.visualization.Gauge(document.getElementById('chart4'));
avg_spo2_chart.draw(avg_spo2_data, SPO2_Chart_Options);

As in previous steps, the full contents of this file (gpulse.php) can be found in the zip attached to the "Software" step above. I put that file in my web server home directory and then opened the page from my phone. The screenshot can be found above.

Description:

I enjoyed this project, and it makes a big difference to us. We still get worried and wake up in the middle of the night, but the combination of a baby monitor and this pulseox setup and our great night nurses reduce the number of trips (and possible disruptions) to our daughter's room. We have also had doctors ask us to record and share data with them, instead of having her come in for a short stay to record data. Now I can just go to this database and pull out exactly what I need.

I had to include a picture of me and my little princess decked out in her favorite color purple this past Halloween - she's awesome and a lot of my projects are for her!

I hope you have learned something from this Instructable to apply to your data capture project - let me know if you have any questions or comments.


YOU MIGHT ALSO LIKE