Skip to content

Add support for the Shearwater Perdix 3 BLE protocol#117

Open
splichy wants to merge 1 commit into
subsurface:Subsurface-DS9from
splichy:Subsurface-DS9
Open

Add support for the Shearwater Perdix 3 BLE protocol#117
splichy wants to merge 1 commit into
subsurface:Subsurface-DS9from
splichy:Subsurface-DS9

Conversation

@splichy

@splichy splichy commented Jul 11, 2026

Copy link
Copy Markdown

The Shearwater Perdix 3 keeps the classic Shearwater application protocol
(SLIP, RDBI/WDBI, block download, same register IDs and dive parser) but
changes two things at the BLE transport layer. This adds a framing switch
to the shared Shearwater/Petrel code so both the classic devices and the
Perdix 3 are handled by one implementation.

What changes on the Perdix 3

  • New GATT service. It does not expose the classic Shearwater serial
    service; it advertises a new vendor service. Detection is by the BLE
    name Perdix 3 (matched before Perdix, since matching is by prefix).
  • No per-write frame prefix. Classic BLE framing prefixes every write
    with a 2-byte [nframes, index] header. The Perdix 3 sends a raw SLIP
    stream with no per-packet header; notifications are reassembled until
    the SLIP END byte 0xC0.
  • 24-bit transfer length. The transfer header carries a 24-bit
    big-endian payload length (FF 01 <len24> request, 01 FF <len24>
    response) instead of the classic 8-bit length.
  • Wider download blocks. The download init uses record type 0x24
    with a 16-bit size field and the init response is 75 20 <blocksize:16be>.
    Observed block sizes are 514 bytes (manifest) and 2306 bytes (compressed
    dive), so the packet buffers are raised from 254 to 4096 bytes.

Implementation

A framing field on shearwater_common_device_t
(SHEARWATER_FRAMING_CLASSIC / SHEARWATER_FRAMING_PERDIX3) selects the
behavior. It is derived from the descriptor model, which is now passed into
shearwater_petrel_device_open. A new descriptor row maps the Perdix 3
BLE name to a new model and maps hardware type 0x39C2 to it. The classic
path is unchanged.

Testing

Verified end-to-end against a Perdix 3 (firmware V38) over BLE on Linux:
identity reads (serial/firmware/hardware), manifest read, compressed dive
download, and full dive import all succeed. The same build cross-compiled
for Android arm64 was validated end-to-end on a physical phone.

Notes for reviewers

Portions of the new protocol logic were AI-assisted; the non-trivial new
blocks in shearwater_common.c are marked with an AI-generated (Claude)
comment. The protocol was reverse-engineered from an HCI snoop of the
official Shearwater app and byte-matched against the capture.

Generated with Claude Code (https://claude.com/claude-code)

The Perdix 3 does not expose the classic Shearwater serial service.
It uses a new vendor service and a modified transport framing while
keeping the application protocol (SLIP, RDBI/WDBI, block download)
unchanged:

- The transfer header carries a 24-bit big-endian payload length
  (FF 01 <len24> request, 01 FF <len24> response) instead of the
  classic 8-bit length, and BLE writes are a raw SLIP stream without
  the per-packet [nframes, index] prefix.
- The download init uses record type 0x24 with a 16-bit size field,
  and the init response is 75 20 <blocksize:16be>. The device uses
  514-byte manifest blocks and 2306-byte compressed dive blocks, so
  the packet buffers are raised from 254 to 4096 bytes.

A framing field on shearwater_common_device_t selects the classic or
Perdix 3 behavior. It is derived from the descriptor model, which is
now passed into shearwater_petrel_device_open. A new descriptor row
maps the "Perdix 3" BLE name to model 14 and hardware type 0x39C2 to
the new model.

Verified end-to-end against a Perdix 3 (firmware V38): identity
reads, manifest and compressed dive downloads, and dive import all
succeed over BLE on Linux.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Vojtěch Šplíchal <splichal@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for the Shearwater Perdix 3 over BLE by extending the existing Shearwater/Petrel implementation with a framing switch, enabling both classic Shearwater BLE framing and the Perdix 3’s raw-SLIP + 24-bit length transport.

Changes:

  • Extends the Petrel device open path to accept a descriptor model and select classic vs Perdix 3 framing.
  • Updates Shearwater common transport code for Perdix 3 framing (no per-write BLE header, 24-bit transfer length, updated download init) and increases packet buffer sizing.
  • Adds a new descriptor entry and model mapping for “Perdix 3” and maps hardware type 0x39C2 to the new model.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/shearwater_petrel.h Extends the device open API to accept a model for framing selection.
src/shearwater_petrel.c Selects Perdix 3 framing at open time based on descriptor model.
src/shearwater_common.h Adds model/framing constants and a framing field to the shared device struct.
src/shearwater_common.c Implements Perdix 3 transfer/download framing behavior and increases packet buffer sizes.
src/device.c Passes descriptor model into the Shearwater Petrel open call.
src/descriptor.c Adds “Perdix 3” descriptor + name filter entry for discovery.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/descriptor.c
Comment on lines 764 to +768
"NERD",
"NERD 2",
"Perdix",
"Perdix 2",
"Perdix 3",
Comment thread src/shearwater_common.c
Comment on lines +31 to +35
// The Perdix 3 framing uses a 24-bit length field and negotiates larger
// download blocks than the classic 254 byte packets: 514 bytes (including
// the 2 byte block header) for uncompressed manifest reads, and 2306 bytes
// for compressed dive downloads (init response 75 20 0902 observed live).
#define SZ_PACKET 4096
@splichy

splichy commented Jul 11, 2026

Copy link
Copy Markdown
Author

@mikeller

@mikeller

Copy link
Copy Markdown
Member

Making this change directly in the Subsurface 'fork' of libdivecomputer is not the best way to get this done - this wil create friction when merging changes from uptream libdivecomputer in the future.
So it will be better if we wait until libdivecomputer has added support for the Perdix 3, and then cherry-pick that change. Feel free to ping me when this has happened.

Also note that this is an open source project, and maintainers will volunteer their free time to work on it whenever they have it available, and have the motivation to do so. Pinging them within hours of submitting something will not help with motivation...

@splichy

splichy commented Jul 12, 2026

Copy link
Copy Markdown
Author

Ooh, didn't know it's a fork :)
I see there is an active PR for Perdix 3 support in upstream, and the implementation seems roughly the same.

@mikeller

Copy link
Copy Markdown
Member

Yeah, it's an unofficial fork of libdivecomputer - we regularly pull in updates from upstream, but we also have some improvements (like support for sending string messages to the host applications, or file / MTP based devices) and bugfixes that were never accepted into upstream.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants