Field note · serial data recovery
The Nonin 7500 reports blood-oxygen and pulse once a second on a plain serial port — no proprietary software required to read it. The official cable and software cost about $600. Here's how to do the same job with about $30 of generic parts, a Raspberry Pi you probably already have, two wires, and data you actually own.
The 7500 outputs a plain ASCII line every second on pin 3 of its DB9 connector: SpO2=096 HR=065. No proprietary software required to read it — just a serial adapter and something listening. The goal was overnight trend tracking: capture the whole night, then each morning compute means, low points, and desaturation counts, and drop a chart into a phone-friendly channel.
It reads the device; it never sends anything back. That one fact keeps the wiring down to two conductors and removes any chance of the logger disturbing the instrument.
A 6-pin FTDI cable with a genuine FT232RL chip. The 7500's serial output is 5 V TTL-level, so a 5 V adapter is the right match. The FTDI chip matters — clones are where flaky drivers and dropped bytes come from.
Breaks the nine DB9 pins out to labeled screw terminals so you can land a wire on pin 3 and pin 5 without soldering to a connector. Comes with both a male and a female side.
The host that runs the logger. The FTDI cable plugs into a USB port and the Pi captures the stream all night as a small always-on service. Any Pi works — this build ran on a modest older board. One caveat learned the hard way: use a proper power supply. A marginal one browning out under load looks exactly like a software hang. Not counted in the cost below since most tinkerers have a spare; budget ~$15–$50 if you don't.
The 7500's serial port is a male DB9, so you connect it to the female side of the breakout. Get this backwards and nothing mates.
Nonin sells a supported, plug-and-play path — and it's the reason this DIY build is worth documenting. You'd buy their 7500SC serial cable (~$200) and their nVISION Windows software (~$400) to read and analyze the data. That's about $600, Windows-only, and the data lives inside their application.
| Path | Parts | Cost | Runs on |
|---|---|---|---|
| DIY (this guide) | FTDI cable + DB9 breakout | ~$30 | Any OS · plain CSV |
| Official | 7500SC cable + nVISION | ~$600 | Windows only |
The official route is genuinely easier — it's plug-and-play and supported. What you trade for the ~$570 saving is a couple of hours of setup. What you gain, beyond the money, is data you own outright: a plain CSV you can graph, script, and archive however you like, on whatever machine you have. The 7500SC is, underneath, a DB9 serial cable — the same signal on the same pin 3 this guide taps directly.
| FTDI wire | DB9 pin | Signal | Why |
|---|---|---|---|
| Yellow (RXD) | Pin 3 | Device data out | The Pi receives what the 7500 transmits |
| Black (GND) | Pin 5 | Signal ground | Common reference |
| Orange (TXD) | — | unused | Read-only; nothing talks back |
| Red / Green / Brown | — | unused | No power or handshake needed |
Four of the six FTDI wires stay unconnected. That surprises people, so it's worth saying plainly: to read a device you need only its transmit line and a shared ground. The adapter's own transmit, its 5 V power, and the handshake lines all go nowhere. Plug the FTDI's USB end into the Pi and the device enumerates as /dev/ttyUSB0.
Nearly every serial device today runs 8N1 — eight data bits, no parity, one stop bit. The 7500's manual specifies 8N2: eight data bits, no parity, two stop bits. The stop bit carries no data — it's just guaranteed idle time between bytes — so an 8N2 device read by an 8N1 receiver mostly works. The eight data bits are identical either way.
"Mostly" is the trap. When the line is marginal — a long cable, a little noise — a receiver expecting only one stop bit has less timing slack to find the next byte boundary, and once in a while it latches onto the wrong bit and reports a garbled value. A pulse of 72 becomes 250. It's rare, maybe a fraction of a percent, but it wrecks any min/max you compute and manufactures desaturations that never happened.
Open the port as 9600 8N2 to match the device spec. In Python's pyserial: stopbits=serial.STOPBITS_TWO. It's one line, and it removes a whole class of silent corruption at the source.
Split into two programs on purpose. The logger has exactly one job — read the serial line and append timestamped rows to a CSV, forever, with no network and no analysis that could fail and cost you data. Everything that can go wrong (Wi-Fi, parsing choices, chart rendering) lives in a separate summary program that runs once a morning and can crash harmlessly.
The logger, at its core:
# open read-only, matching the device's 8N2 framing
ser = serial.Serial("/dev/ttyUSB0", 9600,
bytesize=8,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_TWO,
timeout=5)
# one ASCII line per second: "SpO2=096 HR=065"
LINE = re.compile(rb"SpO2=\s*(\d+)\s+HR=\s*(\d+)")
while True:
raw = ser.readline()
m = LINE.search(raw)
if not m:
continue
spo2, hr = int(m.group(1)), int(m.group(2))
# zeros mean "no reading" -> write blanks, not fake data
write_row(now(), spo2 or "", hr or "")
The summary reads the night's CSV and does the judgment the logger deliberately avoids:
Where it lands. The summary and its chart post to a Discord channel — text plus the graph as an image — so the night's result shows up on a phone without logging into anything. A scheduled run fires every morning. A second small service listens in the same channel for on-demand requests: post any message and it re-runs the summary against the latest recording and replies. That covers naps and odd-hour sessions, and lets a non-technical family member pull a fresh report by simply typing in the channel — no terminal, no commands to remember.
The nightly CSV isn't only for the Discord summary. Converted to a format OSCAR understands — the open-source CPAP/BiPAP analysis tool — the same data can be laid directly over a night of PAP therapy, so an oxygen dip lines up against the exact respiratory event that caused it. That overlay is the real payoff: SpO₂ and pulse in the same timeline as pressure, flow, and events.
A small converter turns the logger's epoch,spo2,hr CSV into a per-second data file (ChoiceMMed MedView .dat) and OSCAR's Oximetry Wizard imports it after the PAP session is loaded. The alignment works because the logger timestamps every sample from the Pi's NTP-synced clock — real wall-clock time, often more accurate than a consumer oximeter's own drifting internal clock. If the PAP machine's clock is correct, the two line up automatically; if it's off by a fixed amount, that offset is applied once at import. In practice the oximetry dropped straight onto the therapy timeline: SpO₂ and pulse traces sitting directly under the pressure, flow, and event flags, with the machine's own desaturation counts now computed against the two datasets together.
Some PAP machines derive time from a fixed GMT offset set at first use and don't adjust for daylight saving. The Pi handles DST automatically; the machine may not — so a pairing that aligns perfectly in summer can drift an hour in winter. Verify once per season, or punch a known marker (pull the sensor for fifteen seconds at a noted time) to measure the offset directly.
Two things had to be solved before the import was usable, and both are worth knowing if you're building the same thing:
The converter is a single Python script that needs nothing installed — standard library only, no third-party packages — so it runs on even a stripped-down Pi. It applies the same plausibility filter as the nightly summary, so the junk frames are gone from the OSCAR view too. Run it against a night's CSV, copy the resulting .dat to whatever machine runs OSCAR, import the PAP night first, then import the datafile through the Oximetry Wizard.
The 7500 stores up to 70 hours internally, and it can replay that memory over the same serial port. It sounds like the easier path: skip the overnight capture, just dump the stored night in the morning. But playback has to be started by hand on the device — you hold a button combination until the screen reads PLA bAC, and only then does the stored data stream out. Someone has to be there, awake, pressing buttons, every single morning.
Reading the live stream instead makes the whole thing hands-off. The logger runs as a service; the sensor goes on at bedtime and the data flows on its own. Nothing to remember, nothing to press, no morning ritual — the summary is simply waiting when everyone wakes up. For a routine that has to survive being tired, forgetful, and busy, hands-off beats the manual dump every time.
| Approach | How it starts | Every morning |
|---|---|---|
| Live stream (this build) | Sensor on, logger already running | Nothing — hands-off |
| Memory playback | Hold buttons for PLA bAC |
Manual dump at the device |
One published project comes close: rogerkl/poxparse, which pulls data from a Nonin 8500m. It's worth reading, and it's a different route to a related end. Rather than reading the live serial stream, that project captures the device's memory dump with a logic analyzer and decodes it in PulseView, then parses the export to CSV — a clever workaround for a modern PC that no longer has a serial port.
Two details there are useful to anyone extending this method across Nonin's line. First, the 8500m's framing is 8O1 — eight bits with odd parity — not the 7500's 8N2. Different model, different framing: exactly why reading the spec beats assuming. Second, its stored-memory playback is coarser and quirkier than a live stream — a datapoint every four seconds, arriving in two bursts — which is a good argument for reading the live 1 Hz output when the device offers it, as the 7500 does.
So the approaches are complementary rather than duplicate: that one recovers a stored session off an 8500m via logic analyzer; this one logs the live stream off a 7500 straight to a Pi. Same family of device, two different problems solved.
If something here helped, tripped you up, or you're rebuilding it on a different Nonin and want to compare notes, send a message. Email is optional — leave it if you'd like a reply.