Berry Oximeter 🫀
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.
Features ✨
- 🔌 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
Installation 📦
Install via pip:
pip install berry-oximeter
Or using uv:
uv add berry-oximeter
Quick Start 🚀
Basic Usage
from 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()
Callback-based Streaming
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()
Data Collection & Analysis
# 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()
CSV Logging
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()
Context Manager (Auto-cleanup)
with BerryOximeter() as oximeter:
oximeter.connect()
oximeter.log_to_console(True)
time.sleep(30)
# Automatically disconnects when done
API Reference 📚
BerryOximeter Class
The main interface for interacting with the pulse oximeter.
Connection Methods
connect(device_address=None, timeout=10.0)- Connect to devicedisconnect()- Disconnect from deviceis_connected- Property to check connection status
Data Access Methods
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
Logging Methods
start_logging(filename=None)- Start CSV loggingstop_logging()- Stop logging and return filenamelog_to_console(enabled=True)- Enable/disable console output
Configuration
set_filter(min_signal_strength=None)- Filter by signal quality (0-8)
OximeterReading Object
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.)
Exceptions
DeviceNotFoundError- No BerryMed device foundConnectionError- Failed to connect to deviceNoDataError- No data received within timeout
Advanced Usage 🔧
Filtering Low-Quality Signals
# 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)
Custom Logging
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)
Async Integration
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)
)
Troubleshooting 🔍
Device Not Found
- 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
Connection Issues
- 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
No Readings
- Make sure your finger is properly inserted
- Keep your hand still - movement affects readings
- Warm up cold fingers first
- Check the signal strength indicator
Supported Devices 📱
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)
Contributing 🤝
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
Acknowledgments 🙏
- Thanks to the Bleak project for the excellent BLE library
- Inspired by the need for simple, reliable pulse oximeter data collection in research settings
Metadata
Release files for berry-oximeter 0.0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| berry_oximeter-0.0.3.tar.gz | 886.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| berry_oximeter-0.0.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 897.6 kB
Release files / berry_oximeter-0.0.3.tar.gz
| Download URL | berry_oximeter-0.0.3.tar.gz |
|---|---|
| Size | 886.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
edd1a43e712ae4593c9847ae7dbd73d0bbb02a4ccd4e6c3ad6f0ae10b5288aa3
|
|
BLAKE2b-256 checksum How to use checksums |
d3208abb1c04c183302e076d3f3d366470732ae94b7c27116ed12283ecb1ed04
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.12.9
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 4, 2025.
Transparency logRelease files / berry_oximeter-0.0.3-py3-none-any.whl
| Download URL | berry_oximeter-0.0.3-py3-none-any.whl |
|---|---|
| Size | 10.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6dd39e30192ecddcd91d90f3716f39c77b058bf99058fc6e85c1d200410d3d72
|
|
BLAKE2b-256 checksum How to use checksums |
fc00023460c08067cf4d7e2cec652c420dab1f8c30dd896a8e9926874af0d2fd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.12.9
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 4, 2025.
Transparency log