Skip to main content

BLE client and CSV parser for the EMAY SleepO2 pulse oximeter

Project description

EMAY SleepO2 BLE SDK

A multi-language SDK for the EMAY SleepO2 pulse oximeter's real-time Bluetooth streaming protocol. Read SpO₂ and pulse rate at 1 Hz from a $30 consumer device.

Every package exposes the same simple API. Pick your language — the EMAYClient interface is identical across Swift, Python, Node.js, Rust, Go, and Kotlin.

Quick Start

Swift

import EMAYSleepO2

let emay = EMAYClient()
emay.onReading = { reading in
    print("SpO₂: \(reading.spo2 ?? 0)%  HR: \(reading.pulse ?? 0)")
}
try await emay.start()

Python

import asyncio
from emay_sleepo2 import EMAYClient

async def main():
    emay = EMAYClient()
    emay.on_reading = lambda r: print(f"SpO₂: {r.spo2}%  HR: {r.pulse}")
    await emay.start()
    await asyncio.sleep(30)  # stream for 30 seconds
    await emay.stop()

asyncio.run(main())

Node.js

import { EMAYClient } from 'emay-sleepo2';

const emay = new EMAYClient();
emay.on('reading', (r) => {
    console.log(`SpO₂: ${r.spo2}%  HR: ${r.pulse}`);
});
await emay.start();

// ... stream ...

await emay.stop();

Rust

use emay_sleepo2::EMAYClient;

#[tokio::main]
async fn main() {
    let mut emay = EMAYClient::new().await?;
    emay.on_reading(|r| println!("SpO₂: {}%  HR: {}", r.spo2, r.pulse));
    emay.start().await?;
    tokio::time::sleep(Duration::from_secs(30)).await;
    emay.stop().await?;
}

Go

package main

import (
    "fmt"
    "time"
    emay "github.com/chenders/emay-sleepo2"
)

func main() {
    client, _ := emay.NewClient(emay.DefaultAdapter())
    client.OnReading = func(r emay.Reading) {
        fmt.Printf("SpO₂: %d%%  HR: %d\n", r.SpO2, r.Pulse)
    }
    client.Start()
    time.Sleep(30 * time.Second)
    client.Stop()
}

Kotlin (Android)

val emay = EMAYClient(context)
emay.onReading = { reading ->
    println("SpO₂: ${reading.spo2}%  HR: ${reading.pulse}")
}
emay.start(scope = lifecycleScope)

API Reference

Every binding exposes the same minimal interface:

class EMAYClient {
    // Lifecycle
    start()                  — connect and begin streaming
    stop()                   — stop streaming and disconnect
    isStreaming: bool        — whether currently streaming

    // Data
    onReading: (Reading) -> void   — called at ~1 Hz with new readings

    // Status
    status: Status           — Idle | Scanning | Connecting | Streaming | Failed
    onStatusChange: (Status) -> void

    // Advanced
    start(address)           — connect to a specific device by address
    batteryLevel: Int?       — battery percentage (after connection)
    heartbeatInterval: Duration — default 1.5s

    // CSV (parse only, no BLE)
    static parseCSV(data) -> [Reading]
    static parseCSVFile(path) -> [Reading]
}

Reading Type

Reading {
    spo2: Int?          // SpO₂ percent (0–100), nil when finger off
    pulse: Int?         // Pulse rate in bpm, nil when no reading
    timestamp: Instant  // When this reading was captured
}

The Protocol (tl;dr)

  • BLE service FF12, write FF01, notify FF02
  • Commands: payload + sum(payload) & 0x7F
  • Start sequence: hello → deviceState → startRealtime → getBattery
  • Sustain with a heartbeat command every ~1.5 s
  • Data frames: 8 bytes — EB 01 05 [PR] [SpO2] 7F 00 [cks]
  • Full specification: spec.md

Packages

Language Source Platform BLE Library
Swift swift/ iOS/macOS CoreBluetooth
Python python/ macOS/Linux/Windows/RPi bleak
Node.js node/ macOS/Linux/Windows/RPi noble
Rust rust/ macOS/Linux/Windows btleplug
Go go/ macOS/Linux/Windows/embedded TinyGo BLE
Kotlin kotlin/ Android 8.0+ Android BLE

About

The EMAY SleepO2 is a $30 continuous pulse oximeter capable of overnight streaming. No public documentation existed for its protocol. We reverse-engineered it and open-source the result so health-app developers, researchers, and tinkerers can stream oxygen data without cracking the spec themselves.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

emay_sleepo2-1.0.2.tar.gz (14.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

emay_sleepo2-1.0.2-py3-none-any.whl (11.9 kB view details)

Uploaded Python 3

File details

Details for the file emay_sleepo2-1.0.2.tar.gz.

File metadata

  • Download URL: emay_sleepo2-1.0.2.tar.gz
  • Upload date:
  • Size: 14.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for emay_sleepo2-1.0.2.tar.gz
Algorithm Hash digest
SHA256 54be2de1aab762236c5d223348957f56ecc25d577ad42c2e7e5927353eb5657b
MD5 6710be219f9adb10a218898637872134
BLAKE2b-256 9b6cad88e78da4b3927ffab30de6e7f39ad10abf20623be6c42f38f077a005a3

See more details on using hashes here.

File details

Details for the file emay_sleepo2-1.0.2-py3-none-any.whl.

File metadata

  • Download URL: emay_sleepo2-1.0.2-py3-none-any.whl
  • Upload date:
  • Size: 11.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for emay_sleepo2-1.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 b09ad9b1aa861217c8c10c0f9c5268cca2be94a377830f712ff47d1d00d2a6c2
MD5 c4f73697d07648dec12531ad6de886df
BLAKE2b-256 83e2f4283875f3b7e20b4c1ca1aff7c298eea684bffcae7c7bf9acd7a2de78d2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page