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
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).
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.
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
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
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).
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.
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