How do I get data from Nonin Xpod PulseOxy Sensor to Raspberry Pi

Question:

I am using a Raspberry Pi 2 board and I have connected the Nonin Xpod PulseOxy Sensor to it. I used the PyUSB module in a python script to access basic data of the sensor like vendor id , product id, etc. I am not able to set the configuration of the device and proceed with the collection of readings. I tried installing the ftdi drivers provided by http://www.ftdichip.com/ for Raspberry Pi. But d2xx module isnt getting imported in my python script.
I am new to writing code for devices. Please help me on how to proceed.

I tired the following:

import usb.core
import usb.util
dev = usb.core.find(idVendor=0x0424, idProduct=0x9514)
dev.set_configuration()

I am getting error in the set configuration.

Traceback (most recent call last):
File "s1.py", line 18, in <module>
main()
File "s1.py", line 13, in main
dev.set_configuration()
File "/usr/local/lib/python2.7/dist-packages/usb/core.py", line 799, in         set_configuration
self._ctx.managed_set_configuration(self, configuration)
File "/usr/local/lib/python2.7/dist-packages/usb/core.py", line 128, in  managed_set_configuration
self.backend.set_configuration(self.handle, cfg.bConfigurationValue)
File "/usr/local/lib/python2.7/dist-packages/usb/backend/libusb0.py", line   439, in set_configuration
_check(_lib.usb_set_configuration(dev_handle, config_value))
File "/usr/local/lib/python2.7/dist-packages/usb/backend/libusb0.py", line 380, in _check
raise USBError(errmsg, ret)
usb.core.USBError: [Errno None] could not set config 1: Device or resource busy 

The datasheet for the sensor is Nonin Xpod 3012LP

Firstly thank you for the answer. I have tried the libftd2XX installation and i am getting the following error after i use the command make -B

for n in BitMode EEPROM/erase EEPROM/read EEPROM/write EEPROM/user/read    EEPROM/user/size EEPROM/user/write Events LargeRead MultiThread SetVIDPID Simple   Timeouts ; do make -C $n || exit 1; done
make[1]: Entering directory '/home/pi/iiitd/release/examples/BitMode'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/home/pi/iiitd/release/examples/BitMode'
make[1]: Entering directory '/home/pi/iiitd/release/examples/EEPROM/erase'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/home/pi/iiitd/release/examples/EEPROM/erase'
make[1]: Entering directory '/home/pi/iiitd/release/examples/EEPROM/read'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/home/pi/iiitd/release/examples/EEPROM/read'
make[1]: Entering directory '/home/pi/iiitd/release/examples/EEPROM/write'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/home/pi/iiitd/release/examples/EEPROM/write'
make[1]: Entering directory    '/home/pi/iiitd/release/examples/EEPROM/user/read'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/home/pi/iiitd/release/examples/EEPROM/user/read'
make[1]: Entering directory '/home/pi/iiitd/release/examples/EEPROM/user/size'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/home/pi/iiitd/release/examples/EEPROM/user/size'
make[1]: Entering directory '/home/pi/iiitd/release/examples/EEPROM/user/write'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/home/pi/iiitd/release/examples/EEPROM/user/write'
make[1]: Entering directory '/home/pi/iiitd/release/examples/Events'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/home/pi/iiitd/release/examples/Events'
make[1]: Entering directory '/home/pi/iiitd/release/examples/LargeRead'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/home/pi/iiitd/release/examples/LargeRead'
make[1]: Entering directory '/home/pi/iiitd/release/examples/MultiThread'
gcc main.c -o multi -Wall -Wextra -L. -lftd2xx -Wl,-rpath /usr/local/lib
/usr/bin/ld: /tmp/ccStfEVz.o: undefined reference to symbol    'pthread_join@@GLIBC_2.4'
//lib/arm-linux-gnueabihf/libpthread.so.0: error adding symbols: DSO missing    from command line
collect2: ld returned 1 exit status
Makefile:9: recipe for target 'multi' failed
make[1]: *** [multi] Error 1
make[1]: Leaving directory '/home/pi/iiitd/release/examples/MultiThread'
Makefile:11: recipe for target 'subdirs' failed
make: *** [subdirs] Error 1
Asked By: Sucharitha Reddy

||

Answers:

I haven’t used the Nonin Xpod PulseOxy Sensor, but it’s not clear now it interfaces. If there is a datasheet for communication please post that.

If the device appears as a virtual com port, Raspberry PI should have the library ready so from Python, pySerial should work.

If you need to use the FTDI D2XX library, first install the native library first(direct link). Details available here and check out the readme, especially this part:

> If the message "FT_Open failed" appears:
>     Perhaps the kernel automatically loaded another driver for the 
>     FTDI USB device.
> 
>     `sudo lsmod`
> 
>     If "ftdi_sio" is listed:
>         Unload it (and its helper module, usbserial), as follows.
> 
>         `sudo rmmod ftdi_sio`
>         `sudo rmmod usbserial`
> 
>     Otherwise, it's possible that libftd2xx does not recognise your 
>     device's Vendor and Product Identifiers.  Call FT_SetVIDPID before
>     calling FT_Open/FT_OpenEx/FT_ListDevices.

I found that Raspberry PI loads the ftdi_sio and usbserial drivers by default, so had to disable those first before I could list FTDI devices and details using the D2XX library. Make sure you can compile their samples first (libraries are correctly linked) and also run their samples (you see your FTDI devices listed and along with the details (such as VID/PID/etc.)). Simply navigate to the samples folders and use make -b.
Note that you may need to run them as sudo.

If the above works well, all that is left is to install the Python bindings for the D2XX library. I’ve used these ftd2xx Python bindings. Setup should be straight forward. If you get errors, check that ftd2xx is looking for the .so file in the right path (/usr/local/lib/libftd2xx.so)

Once that’s installed, you can try listing your devices first:

import ftd2xx
print ftd2xx.listDevices()

Update

it looks like just one of the samples had errors which depeneds on the pthreads library. I’d try to install the ftd2xx python library now.
The part that is confusing is that plain serial should work:

“Green Wire = Serial Output: 9600 Baud, 8 data bits, One Start bit
(Start bit =0), One Stop bit (Stop bit = 1), No Parity.”

You should try to simply read data with the serial library:

import serial,time

def stringAsHex(s):
        return ":".join("{:02x}".format(ord(c)) for c in s)
sensor = serial.Serial('/dev/ttyACM0',timeout=1)#should default to 9600, 8 data bits, 1 stop bit, but feel free to use the constructor arguments to configure it

while True:
    data = sensor.read()
    if len(data) > 0:
        print "data str:",data,"hex:",stringAsHex(data)

Be sure to read the notes on 3.8v to 3.3v conversion and the different data formats based on the resistors used (on page 3 and 4 of the datasheet).

Notice the port I use (/dev/ttyACM0): that’s usually what a new Arduino port shows up as. You’ll have to check what that is with your device( may show up as /dev/ttyUSB0, do ls /dev/tty* before and after connecting your device over to the usb port).

Answered By: George Profenza

I am finally able to get the data from the sensor using the following code:

import serial,time,sys
#returns non zero value if the bit is set
def testbit(value,bit):
return (value & 1 << bit)
#converts raw string to hexadecimal
def stringAsHex(s):
    return ":".join("{:02x}".format(ord(c)) for c in s)

#extract the information from a packet
def processpacket(p):
    msb=p[19][3]
    lsb=p[20][3]
    hr= ((msb<<7)|(lsb)) & 0x1ff
    emsb=p[21][3]
    elsb=p[22][3]
    ehr= ((emsb<<7)|(elsb)) & 0x1ff
    oxy=p[8][3]
    ol= oxy & 0x7f
    eoxy=p[16][3]
    eol= eoxy & 0x7f
    #print "Pulse Rate:", hr
    #print "Oxygen Level:", ol
    #if hr != 511 and  ol !=127 :
    if hr > 40 and hr < 200 and ol > 85 and ol < 127:
        #print "Pulse Rate:", hr
        #print "Extended Pulse Rate:",ehr
        #print "Oxygen Level:", ol
        #print "Extended Oxygen Level:",eol
        return hr,ol
     else:
        #print "Place your finger"
        hr=0
        ol=0
        return hr,ol

def readsensor(n):
    sd=[]
    val=[]
    sensor = serial.Serial('/dev/ttyUSB0',timeout=1,baudrate=9600)
    frame=[]
    fc=0
    packet=[]
    while True:
    frame=[]
            data = sensor.read()
            d=stringAsHex(data)
            intdata=int(d,16)
    #print 'loop 0'
            #print intdata 
            if intdata == 1:
                    data1 = sensor.read()
                    d1=stringAsHex(data1)
                    intdata1=int(d1,16)
                    #print 'if intdata == 1 sensor.read:',intdata1
                    tb = testbit(intdata1,0)
                    #print "test bit:", tb
                    if tb > 0 :
            #print 'packet began'
                            frame.append(intdata)
                            frame.append(intdata1)
                            fc = 2
            cnt = 0
            wc=0
                            while ( fc <= 125 ):
                wc = wc + 1
                #print 'while inside',wc
                                    data = sensor.read()
                                d=stringAsHex(data)
                                    intdata=int(d,16) 
                if len(frame)<=5:
                    frame.append(intdata)
                else:
                    #print 'oversized frame'
                    break
                #print 'fc',fc
                                fc = fc + 1


                if fc%5 == 0:
                    #print 'if fc%5 == 0'
                                            #print frame
                                            if frame[0] == 1:
                        cnt = cnt + 1
                        #print cnt
                                                    packet.append(frame)
                                                    frame=[]
                        #print 'frame empty'
                                            else:
                        #print 'else fc%5',fc
                        break

                if fc == 126:
                                            n = n - 1
                                            if n >= 0:
                                                    h,o= processpacket(packet)
                        sd.append([h,o])
                        #print sd
                                                    packet=[]
                        #print 'packet empty'
                                                    #print "***********Packet received***********"
                                                    fc = 1
                                            else:
                                                    return sd




        else:
            continue
    else:
        continue
print readsensor(20)

I realized that the sensor was giving characters. Then I got the data converted it into integer and put it in the frames and organized the frames into a packet(the frame and packet details were mentioned in the data sheet). Then I extracted the sensor readings from the packet.
The frame and packet are implemented using lists in python.

Answered By: Sucharitha Reddy
import serial

def stringAsHex(s):
    return ":".join("{:02x}".format(c) for c in s)

def readFrame():
    while True:
        b1 = sensor.read()
        if len(b1) > 0:
            if int(stringAsHex(b1), 16) == 0b1:
                b2 = sensor.read()
                b3 = sensor.read()
                b4 = sensor.read()
                b5 = sensor.read()
                #print "Byte: ", stringAsHex(b1), stringAsHex(b2), stringAsHex(b3), stringAsHex(b4), stringAsHex(b5)
                return [b1, b2, b3, b4, b5]
                break

# This function will keep running until the frame is valid, by checking the OUT OF TRACK bit.
def readValidFrameSet():
    sensor.reset_input_buffer()
    while True:
        frame1 = readFrame()
        if int(stringAsHex(frame1[1]), 16) & 0b1:
            #print("first frame!")
            frame2 = readFrame()
            frame3 = readFrame()
            # We only care about frame 1 to 3 so far
            hr1 = ((int(stringAsHex(frame1[3]), 16) & 0b11) << 7)
            hr2 = (int(stringAsHex(frame2[3]), 16) & 0b1111111)
            hr = hr1 + hr2
            spo = int(stringAsHex(frame3[3]), 16) & 0b1111111
            #print "HR=", hr, " SPO2=", spo
            if ((int(stringAsHex(frame1[1]), 16) & 0b10000 == 0) & (spo != 0 & hr < 300)):
                # Out of track bit is not set, frame is valid
                #print stringAsHex(frame1[3])
                #print stringAsHex(frame2[3])
                #print stringAsHex(frame3[3])
                sensor.reset_input_buffer()
                return [hr, spo]
            #else:
                # Out of track bit is set, frame is invalid
                #print("OUT OF TRACK")

# should default to 9600, 8 data bits, 1 stop bit, but feel free to use the constructor arguments to configure it
sensor = serial.Serial('/dev/ttyUSB0', timeout=1)
sensor.flushInput()
while True:
    # Run this once if you only want 1 set values. result[0]=heart rate (in BPM), result[1]= spo2%
    result = readValidFrameSet()
    print("HR=", result[0], " SPO2=", result[1])

Code on my GitHub: https://github.com/vaibhavbachuwar/Nonin-XPOD-Pulse-Oximter/blob/main/noninxpod.py

Answered By: Vaibhav Bachuwar
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.