So, in my previous post, I wrote a bit about retrieving live 60 Hz data from a Contec CMS50D+ pulse oximeter. As mentioned, the device also has another standalone mode where it records pulse rate and blood SpO2 at 1 Hz for up to 24 hours. That is, if your batteries last that long. This thing is quite power hungry.

You can read all about the recording mode in the manual. In this post I’ll focus on the actual data download from the device. There’s really not much to it, so it will be a short post this time…

Let’s look at some recorded data

Whereas the live-mode is strictly one-way, the recorded mode involves a tiny bit of two-way communication to work. You should enable xonxoff to convince Python to talk to the device. The protocol goes as follows:

  • Open a connection at 19200 baud, 1O8 with xonxoff enabled.
  • Listen for live data. If we get none, the device is disconnected or turned off.
  • Send [0xF5, 0xF5]. This switches the device to download mode.
  • Wait for the preamble. It’s three times [0xF2, 0x80, 0x00]. In the beginning we might also have some leftover live data.
  • Then we get the content length as three bytes. See below for an explanation.
  • Receive the specified number of bytes. Each measurement is three bytes. See below for an explanation. Sometimes the download fails and halts midway for some reason and has to be restarted.
  • Send [0xF6, 0xF6, 0xF6]. This switches the device back into live mode.
  • Disconnect.

Now you should have a bunch of data to insert into a spreadsheet or whatever.

The length header

The length header tells us how many bytes of data the device will send. It consists of three bytes. The first two bytes always have their MSB set while it’s never set on the last. This gives us 21 useful bits which is enough. If we have recorded 24 hours of data, this will yield 24 * 60 * 60 = 86400 measurements. And if each measurement is three bytes, then the maximum content length will be 259200 bytes. This only requires 18 bits.

Curiously enough, the content length is always one off compared to the actual data length. So we need to add 1 to the result. Let’s look at an example:

  • We have received the length header [0x81, 0x8A, 0x2C].
  • Validate and strip off MSBs from the first and second byte. Now we have [0x01, 0x0A, 0x2C].
  • Left-shift the first byte by 14 bits and the second byte by 7 bits. Combine the three numbers by using the bitwise OR operator. Now we have 0x452C or 17708 in decimal.
  • Add 1 to the result. This means that the content length is 17709 bytes.
  • Each measurement is three bytes, so we have 17709 / 3 = 5903 measurements. As the device samples at 1 Hz, this means 1 hour 38 minutes and 23 seconds worth of data.

The measurements

Each measurement consists of three bytes.

  • The first byte is always 0xF0 or 0xF1. The 1 is the MSB of the pulse rate in the next byte.
  • The second byte is the pulse rate. As the device only utilizes 7 bits per byte for data, the MSB is moved to the first byte. A human pulse rate can quite easily go over 127 BPM…
  • The third byte is the SpO2 percentage.

That’s it!

Again, all code for this project can be found on GitHub. If you have any questions, please comment below.


KD · 2015-07-21 at 13:27

I am trying to download recorded data from device, but each and every time it is showing ” No data stream from device!” , I recorded in same manner, given in manual here, and then I used command “sudo ./ RECORDED /dev/ttyUSB0 recorded.csv”. What should I do?

    atbrask · 2015-07-21 at 20:58

    Hmm.. Let’s try to troubleshoot this. Given the error message I assume that the serial port is the correct one. You use some Linux distribution, right? If the port name was wrong you would most likely get a different message. This means that the script succesfully opens the USB-to-serial connection to the device, and that something goes wrong when it tries to listen for data from it.

    It almost sounds like the device is off when you try to retrieve data from it. Please make sure that the device is turned on before you run the script. When turned on, it will automatically turn off very quickly once you remove it from your finger – even if the data cable is connected. So right after you turn it on, you need to hold the button to enter the menu. Once in the menu, it will stay on while you download the data from it. You exit the menu by pressing the button shortly a couple of times to move the arrow next to “Exit”, and then holding the button until it returns to live mode. Again, if it doesn’t detect a finger, it will turn off shortly.

    I hope this helps. If not, please provide more details about your setup.

      KD · 2015-07-22 at 08:13

      Nope.. It is hang after “Please wait as the latest session is downloaded…”,
      My Setup.. I connected CONTEC, long pressed the button to enter in menu then I switched On the Recording, came back to measurement screen. After 30 sec it is going to sleep after showing recording message.
      After 1 min I pressed button for long time to enter in menu, turned off the button, and remain in that screen.
      I changed the permission for /dev/ttyUSB0 using chmod 777, then I used command “sudo ./ RECORDED /dev/ttyUSB0 kd2207.csv”. Is there anything wrong?

        atbrask · 2015-07-22 at 15:30

        That sounds like the correct procedure. Have you tried the live mode with the oximeter on your finger? Do you have access to a Windows machine? Then you could try to see if the official software is working.

        Please note that you have to use the bundled USB cable, as the big USB plug contains a USB-to-serial converter. The CMS50D+ does not work with a standard USB cable…

          KD · 2015-07-23 at 06:52

          Live mode is working fine in my linux machine.. :-). Ok I will check in windows.
          I am using same cable whateever you have mentioned.

          atbrask · 2015-07-23 at 20:49

          If live mode is working, then recorded mode should work as well. Have you tried executing the command for downloading recorded data while the oximeter is running on your finger (not in a menu)?

KD · 2015-07-29 at 07:50

Yes, I have tried everything, but it is not working for me.
One more question I have, for LIVE data, I changed the static method like
def getCsvColumns():
return [“PulseRate”, “SpO2”, “PulseWaveform”]

def getCsvData(self):
return [self.pulseRate, self.bloodSpO2, self.pulseWaveform]
I want to print these 3 values as well as I want to save in csv, It is saving properly, but what line should I add for printing it before writting in csv.
Thank you

    KD · 2015-07-29 at 13:27

    I changed Static method to print values
    def getCsvData(self):
    print self.pulseRate,’ ‘,self.bloodSpO2,’ ‘,self.pulseWaveform
    return [self.pulseRate, self.bloodSpO2, self.pulseWaveform]

    But one new problem I am facing, Sometimes, Device is not getting Live data also, and after opening the port, It is waiting for some times and printing “Done” Directly.
    Is it Connection problem, Power problem Or something else?

      atbrask · 2015-07-29 at 22:54

      It’s difficult to say. I have only tested this code with my own unit. To me it sounds like a connection problem. Perhaps a bad connection in the cable? The best way to rule out things like that is to try acquiring data using the Windows applications that were bundled with the oximeter.

sunshine · 2017-02-26 at 15:33

I have a problem too with RECORDED mode. It prints out: “Please wait as the latest session is downloaded…”, and that’s all.
Is there possibility to change recording time in LIVE mode? I would like to get 1Hz output, not 60Hz in LIVE mode.

    Mike · 2017-03-17 at 23:36

    Hi Sunshine,
    I am trying to test out the atbrask program but am not sure how to run this on a Pi3. Can you give me the order of how you start the program. if you are doing the sudo cmds first and what cmds you are putting in and then what atbrask routines you are running?

Leo · 2017-04-19 at 17:56

Hi Atbrask or Sunshine,

I spent lots of time trying to get this working in LIVE mode. The system was working fine in Windows system, so I knew cable and unit are fine. When switch to Linux (I also tried Mac with python), I could see the drive was loaded fine (/dev/ttyUSB0). After setting the port to 19200 8O1 with software control Yes (using minicom), I ran the program. It seems getting live data, but it ended automatically after some time (depending on the the timeout value, which pointed to a communication issue). When I opened the CSV file, it only had the head line — no data. Any suggestion what I missed?



Simon · 2017-11-08 at 09:13

I got a CMS50D+ pulse oximeter. I wanted to read the serial port data on raspbian. I listed the usb ports from dev directory, but my pulse oximeter’s port didn’t appeared. Can you give me a hint what can I do? I would like to mention that the pulse oximeter is working!

    atbrask · 2017-11-08 at 09:37


    Did you use the bundled USB cable? I don’t know if they have changed it in later revisions, but mine only works with that cable, as it contains the required USB to serial converter. What does it say if you run dmesg on your RPi after plugging in the device? It should spit out a few lines that it found a USB to serial device.

Kishore · 2018-11-12 at 13:45

I am getting an error like “a bytes-like object is required, not ‘str’ “, on running the code on windows environment. I am using the command “run cms50dplus LIVE COM3 foo.csv” spyder dev environment. Could you please tell what is the issue.

Buckwheat · 2019-10-09 at 17:10

Just an FYI if anyone is trying to do this in Matlab. for some reason the hex values 0x80-0x9F over serial always return 0x3F. I’m not sure if this is on the send or receive. I’m not sure if this is my hangup, but I will find out here soon as I switch to a different controlling software or hardware.

Carlos Rodriguez · 2020-08-30 at 20:59

Hi guys

I share my code, 100% tested

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *