Code the Pulse Oximeter Logger

Once your Feather nRF52840 is set up with CircuitPython, you'll also need to add some libraries. Follow this page for information on how to download and add libraries to your Feather.

From the library bundle you downloaded in that guide page, transfer the following libraries onto the Feather's /lib directory:

  • adafruit_ble
  • adafruit_ble_berrymed_pulse_oximeter
  • adafruit_bus_device
  • adafruit_register
  • adafruit_sdcard.mpy
  • neopixel.mpy

Text Editor

Adafruit recommends using the Mu editor for using your CircuitPython code with the Feather boards. You can get more info in this guide.

Alternatively, you can use any text editor that saves files.

Code.py

Copy the code below and paste it into Mu. Then, save it to your Feather as code.py.

"""
Read data from a BerryMed pulse oximeter, model BM1000C, BM1000E, etc.
Run this on Feather nRF52840
Log data to SD card on Autologger FeatherWing
"""

# Protocol defined here:
#     https://github.com/zh2x/BCI_Protocol
# Thanks as well to:
#     https://github.com/ehborisov/BerryMed-Pulse-Oximeter-tool
#     https://github.com/ScheindorfHyenetics/berrymedBluetoothOxymeter
#
# The sensor updates the readings at 100Hz.

import time
import adafruit_sdcard
import board
import busio
import digitalio
import storage
import adafruit_pcf8523
import _bleio
import adafruit_ble
from adafruit_ble.advertising.standard import Advertisement
from adafruit_ble.services.standard.device_info import DeviceInfoService
from adafruit_ble_berrymed_pulse_oximeter import BerryMedPulseOximeterService

# Logging setup
SD_CS = board.D10
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
cs = digitalio.DigitalInOut(SD_CS)
sd_card = adafruit_sdcard.SDCard(spi, cs)
vfs = storage.VfsFat(sd_card)
storage.mount(vfs, "/sd_card")

log_interval = 2  # you can adjust this to log at a different rate

# RTC setup
I2C = busio.I2C(board.SCL, board.SDA)
rtc = adafruit_pcf8523.PCF8523(I2C)

days = ("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")

set_time = False
if set_time:  # change to True if you want to write the time!
    #             year, mon, date, hour, min, sec, wday, yday, isdst
    t = time.struct_time((2020, 4, 21, 18, 13, 0, 2, -1, -1))
    # you must set year, mon, date, hour, min, sec and weekday
    # yearday  not supported, isdst can be set but we don't use it at this time
    print("Setting time to:", t)  # uncomment for debugging
    rtc.datetime = t
    print()

# PyLint can't find BLERadio for some reason so special case it here.
ble = adafruit_ble.BLERadio()  # pylint: disable=no-member

pulse_ox_connection = None

while True:
    t = rtc.datetime
    print("Scanning for Pulse Oximeter...")
    for adv in ble.start_scan(Advertisement, timeout=5):
        name = adv.complete_name
        if not name:
            continue
        # "BerryMed" devices may have trailing nulls on their name.
        if name.strip("\x00") == "BerryMed":
            pulse_ox_connection = ble.connect(adv)
            print("Connected")
            break

    # Stop scanning whether or not we are connected.
    ble.stop_scan()
    print("Stopped scan")

    try:
        if pulse_ox_connection and pulse_ox_connection.connected:
            print("Fetch connection")
            if DeviceInfoService in pulse_ox_connection:
                dis = pulse_ox_connection[DeviceInfoService]
                try:
                    manufacturer = dis.manufacturer
                except AttributeError:
                    manufacturer = "(Manufacturer Not specified)"
                try:
                    model_number = dis.model_number
                except AttributeError:
                    model_number = "(Model number not specified)"
                print("Device:", manufacturer, model_number)
            else:
                print("No device information")

            pulse_ox_service = pulse_ox_connection[BerryMedPulseOximeterService]

            while pulse_ox_connection.connected:
                values = pulse_ox_service.values
                if values is not None:
                    # unpack the message to 'values' list
                    valid, spo2, pulse_rate, pleth, finger = values
                    if not valid:
                        continue
                    if (
                            pulse_rate == 255
                    ):  # device sends 255 as pulse until it has a valid read
                        continue
                    print(
                        "SpO2: {}%  | ".format(spo2),
                        "Pulse Rate: {} BPM  | ".format(pulse_rate),
                        "Pleth: {}".format(pleth),
                    )
                    # print((pleth,))  # uncomment to see graph on Mu plotter

                    try:  # logging to SD card
                        with open("/sd_card/log.txt", "a") as sdc:
                            t = rtc.datetime
                            sdc.write(
                                "{} {}/{}/{} {}:{}:{}, ".format(
                                    days[t.tm_wday],
                                    t.tm_mday,
                                    t.tm_mon,
                                    t.tm_year,
                                    t.tm_hour,
                                    t.tm_min,
                                    t.tm_sec
                                )
                            )
                            sdc.write(
                                "{}, {}, {:.2f}\n".format(
                                    spo2, pulse_rate, pleth
                                )
                            )

                            time.sleep(log_interval)
                    except OSError:
                        pass
                    except RuntimeError:
                        pass
    except _bleio.ConnectionError:
        try:
            pulse_ox_connection.disconnect()
        except _bleio.ConnectionError:
            pass
        pulse_ox_connection = None

How it Works

The code does the following things:

  • Imports necessary libraries, including adafruit_ble_berrymed_pulse_oximeter library
  • Sets up SD card interface to use as a file system
  • Sets up the real-time clock (RTC) on the I2C bus for setting and then keeping track of the day, date, and time
  • Scan for and connect to a Bluetooth LE peripheral advertising the BerryMed pulse oximeter service
  • Receive pulse oximeter service messages and unpack their values into SpO2%, pulse rate, and pleth (pulse strength graph)
  • When a valid pulse oximeter message is received, log the values along with the day, date, and time from the RTC onto the SD card in a file called log.txt
  • Repeat this process after the specified log_interval (a couple of seconds for example)
  • If/when the connection is dropped, go back to BLE search mode

Now, let's try it out!

This guide was first published on Apr 28, 2020. It was last updated on Apr 28, 2020.
This page (Code the Pulse Oximeter Logger) was last updated on Jul 14, 2020.