A simple Python library for real-time data collection from BerryMed pulse oximeters via Bluetooth LE. Get heart rate, SpO2, and perfusion index readings with just a few lines of code!
Warning
This library is for educational and research purposes only. It is not intended for medical diagnosis or treatment. Always consult with a qualified healthcare provider for medical advice.
- π Auto-discovery - Automatically finds and connects to your BerryMed device
- π Real-time streaming - Get live readings with customizable callbacks
- π Data collection - Collect readings over time for analysis
- πΎ CSV logging - Built-in data logging to CSV files
- π― Signal filtering - Filter readings by signal quality
- π Pythonic API - Simple, intuitive interface
- π§ͺ Type hints - Full type hint support for better IDE experience
Install via pip:
pip install berry-oximeterOr using uv:
uv add berry-oximeterfrom berry_oximeter import BerryOximeter
# Create instance and connect
oximeter = BerryOximeter()
oximeter.connect()
# Enable console output
oximeter.log_to_console(True)
# Let it run for 30 seconds
import time
time.sleep(30)
# Disconnect
oximeter.disconnect()def handle_reading(reading):
if reading.is_valid:
print(f"β₯ SpO2: {reading.spo2}%, Pulse: {reading.pulse_rate} BPM")
else:
print(f"Status: {reading.status}")
oximeter = BerryOximeter()
oximeter.connect()
oximeter.start_streaming(handle_reading)
# Stream for 60 seconds
time.sleep(60)
oximeter.stop_streaming()
oximeter.disconnect()# Collect 60 seconds of data
oximeter = BerryOximeter()
oximeter.connect()
readings = oximeter.get_readings(duration_seconds=60)
# Analyze the data
valid_readings = [r for r in readings if r.is_valid]
avg_spo2 = sum(r.spo2 for r in valid_readings) / len(valid_readings)
avg_pulse = sum(r.pulse_rate for r in valid_readings) / len(valid_readings)
print(f"Average SpO2: {avg_spo2:.1f}%")
print(f"Average Pulse: {avg_pulse:.1f} BPM")
oximeter.disconnect()oximeter = BerryOximeter()
oximeter.connect()
# Start logging (auto-generates filename with timestamp)
filename = oximeter.start_logging()
print(f"Logging to: {filename}")
# Or specify your own filename
# oximeter.start_logging("patient_123.csv")
# Collect data
oximeter.log_to_console(True)
time.sleep(300) # 5 minutes
# Stop logging
oximeter.stop_logging()
oximeter.disconnect()with BerryOximeter() as oximeter:
oximeter.connect()
oximeter.log_to_console(True)
time.sleep(30)
# Automatically disconnects when doneThe main interface for interacting with the pulse oximeter.
connect(device_address=None, timeout=10.0)- Connect to devicedisconnect()- Disconnect from deviceis_connected- Property to check connection status
start_streaming(callback)- Start streaming with callback functionstop_streaming()- Stop streamingget_reading(timeout=5.0)- Get a single readingget_readings(duration_seconds)- Collect readings for specified duration
start_logging(filename=None)- Start CSV loggingstop_logging()- Stop logging and return filenamelog_to_console(enabled=True)- Enable/disable console output
set_filter(min_signal_strength=None)- Filter by signal quality (0-8)
Each reading contains:
timestamp- When the reading was takenspo2- Oxygen saturation (%) or Nonepulse_rate- Heart rate (BPM) or Nonepleth- Plethysmograph value or Nonesignal_strength- Signal quality (0-8)is_valid- True if SpO2 and pulse are validstatus- Human-readable status ("reading", "no_finger", "searching", etc.)
DeviceNotFoundError- No BerryMed device foundConnectionError- Failed to connect to deviceNoDataError- No data received within timeout
# Only accept readings with signal strength >= 5
oximeter.set_filter(min_signal_strength=5)
def quality_callback(reading):
print(f"High quality: SpO2 {reading.spo2}%, Signal: {reading.signal_strength}/8")
oximeter.start_streaming(quality_callback)import csv
def custom_logger(reading):
if reading.is_valid:
with open('custom_log.csv', 'a', newline='') as f:
writer = csv.writer(f)
writer.writerow([
reading.timestamp,
reading.spo2,
reading.pulse_rate,
reading.pleth,
reading.signal_strength
])
oximeter.start_streaming(custom_logger)Since the library runs BLE operations in a separate thread, it works well with async code:
import asyncio
async def monitor_patient(patient_id, duration):
oximeter = BerryOximeter()
oximeter.connect()
oximeter.start_logging(f"patient_{patient_id}.csv")
await asyncio.sleep(duration)
oximeter.stop_logging()
oximeter.disconnect()
# Run multiple monitors concurrently
async def main():
await asyncio.gather(
monitor_patient("001", 300),
monitor_patient("002", 300)
)- Make sure your BerryMed oximeter is turned on
- Check that Bluetooth is enabled on your computer
- The device name should be "BerryMed" - if yours is different, you'll need to modify the code
- On Linux, you may need to run with
sudoor add your user to thebluetoothgroup
- Ensure the device isn't already connected to another application
- Try moving closer to the device
- Some devices may need to be in pairing mode
- Make sure your finger is properly inserted
- Keep your hand still - movement affects readings
- Warm up cold fingers first
- Check the signal strength indicator
This library is tested with:
- BerryMed BM1000C
- Other BerryMed pulse oximeters using the BCI protocol
The library should work with any Berry device that:
- Advertises as "BerryMed" over Bluetooth LE
- Uses the standard BCI protocol (5-byte packets)
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
- Thanks to the Bleak project for the excellent BLE library
- Inspired by the need for simple, reliable pulse oximeter data collection in research settings
