Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
c7009dc
First version of the Central adapter base class and factory functions.
cshmookler May 8, 2026
da77bc2
Add an example for the Central adapter
cshmookler May 11, 2026
76af836
Fix factory functions for CentralAdapter so they compile on Windows
cshmookler May 11, 2026
7cd4a5a
Merge the CentralAdapter with ShmemSession::Impl
cshmookler May 13, 2026
d930fdb
Remove shared memory version detection when connecting to STANDALONE …
cshmookler May 13, 2026
3bd94bd
Split central_types.h into multiple version-specific types files.
cshmookler May 15, 2026
2997ce0
Add central types for 3.11.
cshmookler May 18, 2026
cb348b7
Convert macros to global constants in cbproto
cshmookler May 19, 2026
c997d39
Add version-specific structs for each member variable of CentralLegac…
cshmookler May 22, 2026
3771bc0
Minor stylistic edits to central_types/
cshmookler May 26, 2026
0db8a86
Add object translators for compatibility with Central
cshmookler May 26, 2026
f121b8c
Add Central adapters and basic integration with ShmemSession
cshmookler May 26, 2026
99c1a42
Fix array size bugs in central_translators/utils.h
cshmookler May 27, 2026
1583c3d
Change type names to match Central
cshmookler May 27, 2026
3699222
Add context for translations
cshmookler May 28, 2026
d5ff516
Implement remaining translator and adapter methods for ShmemSession
cshmookler Jun 5, 2026
4502eb1
Change call sites in cbsdk for the new ShmemSession API
cshmookler Jun 8, 2026
1ab99a0
Change ShmemSession to use the Central adapter
cshmookler Jun 10, 2026
2194fec
Consolidate Central translators into the Central adapters
cshmookler Jun 15, 2026
692066e
Make cbPcStatus operations multi-instrument safe
cshmookler Jun 15, 2026
5eb23ea
Implement spike buffer access methods for Central compat
cshmookler Jun 15, 2026
08abe08
Update file_recording example.
cboulay Jun 2, 2026
6c56626
Delete line that somehow added itself.
cboulay Jun 2, 2026
879fa54
Ignore .codegraph
cboulay Jun 2, 2026
165b380
CMP: match by (bank, term), header-driven columns, positions in µm
cboulay Jun 3, 2026
64e327f
CMP: Check for unit-indexing (min-spacing) rather than all grid spaci…
cboulay Jun 3, 2026
5d89f0d
pycbsdk - ignore shared objects in case we put one here for quick tes…
cboulay Jun 3, 2026
74a8763
Add more socket error reporting
cboulay Jun 12, 2026
891c057
Get a little more info from some example apps
cboulay Jun 12, 2026
870ef9a
Change call sites in cbsdk for the new ShmemSession API
cshmookler Jun 8, 2026
0bc7003
FIX: Stack overflow from translating large structs
cshmookler Jun 18, 2026
f868deb
FIX: Central 7.7.0 -> procotol 4.1
cshmookler Jun 18, 2026
d5d9846
Split adapters.h into per-version header files
cshmookler Jun 18, 2026
6ba6f0e
Add documentation for multi-version-central-compat
cshmookler Jun 23, 2026
4dcdedc
FIX: Channel info unpacking in CCFTest
cshmookler Jun 23, 2026
3308e0b
FIX: cbCFGBUFF reference in the cbshm README
cshmookler Jun 24, 2026
5f38df1
FIX: CENTRAL mode in the cbsdk README
cshmookler Jun 24, 2026
5fc2747
Per-instrument ShmemSession
cshmookler Jun 25, 2026
d93a4f5
Migrate all tests except test_shmem_session to the new API
cshmookler Jun 25, 2026
335b1a0
FIX: id/idx discrepencies
cshmookler Jun 25, 2026
6abaf07
Alter all pre-existing ShmemSession tests for the new API
cshmookler Jun 26, 2026
98f9ecc
FIX: 7.7.x & 7.8.x Central use both cbproto and cbhwlib constants
cshmookler Jun 30, 2026
26ea1a1
Move Central version detection outside of ShmemSession
cshmookler Jul 1, 2026
a7042ff
FIX: nullptr cannot be compared to Result type
cshmookler Jul 1, 2026
daf6d33
FIX: cbRECBUFFLEN instead of sizeof(cbRECBUFFLEN)
cshmookler Jul 1, 2026
aa726cc
Fix the example for the Central client
cshmookler Jul 1, 2026
4738c8f
FIX: xmt -> xmt_local for local operations
cshmookler Jul 1, 2026
db37643
FIX: Central adapter construction
cshmookler Jul 1, 2026
ecc2289
FIX: STANDALONE+CENTRAL (theoretically possible, not supported from t…
cshmookler Jul 1, 2026
7bbbd00
Add client mode sequence diagrams.
cboulay Jul 2, 2026
b90f5e7
Add additional tests for the Central adapter
cshmookler Jul 2, 2026
f75d19f
Remove tests dependent on unknown behavior
cshmookler Jul 6, 2026
e9f8f36
Update sequence diagram docs and clean other docs
cshmookler Jul 6, 2026
f88beb3
Merge remote-tracking branch 'origin/master' into multi-version-centr…
cshmookler Jul 8, 2026
c9aac1f
FIX: check result of device_session->getChanInfo
cshmookler Jul 8, 2026
a6a34ee
FIX: Shared memory names too long for MacOS
cshmookler Jul 8, 2026
1d2cc65
FIX: channel, group, and filter labels fetched with cbsdk
cshmookler Jul 9, 2026
ad57321
FIX: Formatting in pycbsdk/session.py
cshmookler Jul 9, 2026
f35872a
FIX: Populate missing cbPcStatus fields for v7.0
cshmookler Jul 10, 2026
49044ce
Remove duplicate line
cboulay Jul 10, 2026
3f650fb
Fix error message
cboulay Jul 10, 2026
716274f
FIX: CLIENT reader fails safe on rec-buffer overrun instead of emitti…
cboulay Jul 10, 2026
22685bc
FIX: virtual dtor for CentralBootstrapAdapterBase; drop bogus const o…
cboulay Jul 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Modular library stack:

- **STANDALONE** — CereLink owns the device connection and shared memory
- **CLIENT** — Attach to another CereLink instance's shared memory
- **CENTRAL_COMPAT CLIENT** — Attach to Central's shared memory with on-the-fly protocol translation
- **CENTRAL CLIENT** — Attach to Central's shared memory with on-the-fly protocol translation

## Build

Expand Down
7 changes: 1 addition & 6 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,4 @@ cbmex.pdf and .doc are here for historical reasons only. Contact the manufacture

## Source Code Architecture

### cbSdkOpen

Here is a high-level flowchart for what happens inside the SDK when you call cbSdkOpen.

![cbSdkOpen flowchart](cbSdkOpen_flowchart.svg)

See the [client sequence diagrams](client_mode_sequence_diagrams.md).
4 changes: 0 additions & 4 deletions docs/cbSdkOpen_flowchart.svg

This file was deleted.

4 changes: 0 additions & 4 deletions docs/cbSdkOpen_flowchart_V2.svg

This file was deleted.

275 changes: 275 additions & 0 deletions docs/client_mode_sequence_diagrams.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
# Client Mode Sequence Diagrams

Sequence diagrams for the workflow between CereLink clients and cbsdk, for each of the
three client modes, and the differences in Central-compatible client mode when attached
to older Central versions.

Reflects the code as of PR #190 (multi-version Central compatibility). Key sources:
`src/cbsdk/src/sdk_session.cpp`, `src/cbshm/src/shmem_session.cpp`,
`src/cbshm/src/central_version.cpp`, `src/cbshm/include/cbshm/central_adapters/*`.

**See also**: [Shared memory architecture](shared_memory_architecture.md),
[Multi-version Central compatibility](multi_version_central_compat/README.md).

## Mode selection (common entry point)

Every client — C++ or pycbsdk (`Session()` → `cbsdk_session_create()` →
`SdkSession::create()`) — goes through the same three-way probe:

```mermaid
sequenceDiagram
participant App as Client app / pycbsdk
participant SDK as SdkSession::create()
participant SHM as cbshm::ShmemSession

App->>SDK: create(config{device_type=HUB1, ...})
Note over SDK: instrument index from GEMSTART==2 map<br/>(Hub1→0, Hub2→1, Hub3→2, NSP→3, legacy→0)
SDK->>SHM: create(CLIENT, CENTRAL, instance="", inst)
alt Central.exe running & its segments open
SHM-->>SDK: ok → CENTRAL-COMPAT CLIENT mode
else fails (no Central / non-Windows / wrong version)
SDK->>SHM: create(CLIENT, NATIVE, "hub1", inst 0)
alt cbshm_hub1_* segments exist AND owner alive
Note over SHM: isOwnerAlive(): kill(owner_pid, 0)<br/>rejects stale segments
SHM-->>SDK: ok → NATIVE CLIENT mode
else fails or stale
SDK->>SHM: create(STANDALONE, NATIVE, "hub1", inst 0)
SHM-->>SDK: creates 7 segments → NATIVE STANDALONE mode
end
end
```

Note that the first attempt does not merely test whether Central's shared memory exists:
`ShmemSession::Impl::open()` calls `detectCentralVersion()` first, which requires finding
a running `Central.exe` process (Windows-only). If Central crashed leaving segments
behind, or on any POSIX platform, attempt 1 fails before any segment is touched.

## Mode 1 — NATIVE STANDALONE (owns the device)

```mermaid
sequenceDiagram
participant App as Client app
participant SDK as SdkSession
participant Q as SPSC queue +<br/>callback thread
participant SHM as ShmemSession<br/>(NATIVE, writer)
participant DEV as cbdev DeviceSession<br/>(recv + send threads)
participant NSP as Device (UDP)

rect rgba(88,110,255,0.15)
Note over App,NSP: Startup
App->>SDK: create()
SDK->>SHM: create(STANDALONE, NATIVE, "hub1")
Note over SHM: shm_unlink stale → create 7 segments<br/>init cfg (owner_pid), xmt rings, status, spike cache
SDK->>DEV: createDeviceSession(params)
Note over DEV: ProtocolDetector probes device →<br/>wraps in DeviceSession_311/400/410 if old protocol
SDK->>SDK: start(): spawn callback thread,<br/>register recv + datagram callbacks,<br/>DEV.startReceiveThread(), spawn send thread
SDK->>DEV: handshake (autorun ? performStartupHandshake : requestConfiguration)
DEV->>NSP: SYSSET/config request
NSP-->>DEV: SYSREP + config dump (PROCREP, CHANREP...)
SDK->>DEV: sendClockProbe() (initial)
end

rect rgba(46,180,80,0.15)
Note over App,NSP: Steady state — receive path
NSP-->>DEV: UDP datagram (device-native protocol)
Note over DEV: versioned wrapper translates every packet<br/>→ CURRENT format before anyone sees it
loop each packet in datagram
DEV->>SDK: receive callback(pkt)
SDK->>SHM: storePacket → writeToReceiveBuffer<br/>(wrap-marker padding, release-store head)
SDK->>SHM: mirror config reps: setProcInfo / setSysInfo /<br/>setGroupInfo / setChanInfo (+CMP overlay)
SDK->>Q: push(pkt)
end
DEV->>SDK: datagram-complete callback
SDK->>DEV: sendClockProbe() (every ~100 ms)
SDK->>SHM: setClockSync(offset_ns, uncertainty)
SDK->>SHM: signalData() — sem_post / SetEvent (wakes CLIENTs)
SDK->>Q: notify callback thread
Q->>App: dispatchBatch: group-batch callbacks,<br/>then per-packet (event/group/config) callbacks
end

rect rgba(255,150,40,0.15)
Note over App,NSP: Steady state — send path
App->>SDK: sendPacket(cmd)
SDK->>SHM: enqueuePacket (time stamped ns, CURRENT format → xmt ring)
Note over SDK: send thread polls xmt ring
SDK->>SHM: dequeuePacket
SDK->>DEV: sendPacket (wrapper translates to device protocol)
DEV->>NSP: UDP (paced: sleep every 8 packets)
end
```

Three worker threads (plus main): the cbdev UDP-receive thread — which also runs the
receive and datagram-complete callbacks that feed the SPSC queue — the SDK callback
dispatcher, and the SDK send thread.

## Mode 2 — NATIVE CLIENT (attaches to a CereLink STANDALONE)

```mermaid
sequenceDiagram
participant App as Client app
participant SDK as SdkSession
participant SHM as ShmemSession<br/>(NATIVE, reader)
participant SA as STANDALONE process
participant NSP as Device

rect rgba(88,110,255,0.15)
Note over App,SA: Startup
App->>SDK: create()
SDK->>SHM: create(CLIENT, NATIVE, "hub1")
Note over SHM: attach cbshm_hub1_* (rec/status/spk read-only,<br/>cfg + xmt read-write), tail synced to current head
SDK->>SHM: isOwnerAlive()? (kill(owner_pid,0))
SDK->>SDK: start(): spawn ONE shmem-receive thread
SDK->>SHM: rebuildChannelTypeCache() from shmem chaninfo
end

rect rgba(46,180,80,0.15)
Note over App,NSP: Steady state — receive
NSP-->>SA: UDP → translated to CURRENT → rec ring
SA-->>SHM: signalData()
SHM-->>SDK: waitForData(250ms) returns true
loop drain ring until empty (batches of 128)
SDK->>SHM: readReceiveBuffer()
Note over SHM: CURRENT format already — no translation,<br/>no instrument filtering (single-device ring),<br/>skip chid=0/type=0 wrap markers
SDK->>SDK: scan batch: SYSREP → runlevel,<br/>NPLAYREP → complete client clock probe
SDK->>App: dispatchBatch → user callbacks (same thread)
end
end

rect rgba(255,150,40,0.15)
Note over App,NSP: Send + client clock sync
App->>SDK: sendPacket(cmd)
SDK->>SHM: enqueuePacket → cbshm_hub1_xmt_global
SA->>SHM: dequeuePacket (its send thread)
SA->>NSP: UDP
Note over SDK: every ~2 s: enqueue NPLAYSET probe the same way —<br/>NPLAYREP returns via rec ring → ClockSync sample
end
```

No callback thread — the ~256 MB ring absorbs bursts, so callbacks run directly on the
shmem-receive thread.

## Mode 3 — CENTRAL-COMPAT CLIENT, latest Central (7.8 / protocol 4.2)

This is the baseline that the versioned adapter machinery runs through even when no
translation is needed:

```mermaid
sequenceDiagram
participant App as Client app
participant SDK as SdkSession
participant SHM as ShmemSession<br/>(CENTRAL layout)
participant VER as central_version.cpp
participant ADP as central::Adapter (v7_8)
participant CTL as Central.exe
participant NSP as Devices (up to 4)

rect rgba(88,110,255,0.15)
Note over App,CTL: Startup
App->>SDK: create(HUB1) — instrument idx 0 (GEMSTART==2)
SDK->>SHM: create(CLIENT, CENTRAL, instance="", inst)
SHM->>VER: detectCentralVersion()
VER->>CTL: enumerate processes → find Central.exe →<br/>read VersionInfo.ProductVersion
VER-->>SHM: "7.8.x" → CentralVersion::CURRENT → protocol 4.2
Note over SHM: BootstrapAdapter(CURRENT) supplies buffer sizes
SHM->>CTL: OpenFileMapping: cbCFGbuffer, cbRECbuffer,<br/>XmtGlobal, XmtLocal, cbSTATUSbuffer, cbSPKbuffer, cbSIGNALevent
SHM->>ADP: construct with raw pointers into each segment
Note over SHM: tail ← head (only NEW packets)
SDK->>SDK: start(): shmem-receive thread,<br/>rebuildChannelTypeCache via adapter
end

rect rgba(46,180,80,0.15)
Note over App,NSP: Steady state — receive
NSP-->>CTL: UDP (all instruments) → Central writes RAW device<br/>packets into ONE shared cbRECbuffer, SetEvent(~100/s)
CTL-->>SDK: waitForData wakes
loop drain ring
SDK->>SHM: readReceiveBuffer()
Note over SHM: protocol == CURRENT → parse 16-byte header,<br/>copy packet as-is (no translation)<br/>non-Gemini system: header.time ticks→ns via sysfreq<br/>then FILTER: instrument != selected idx (0 for HUB1) → discard (after copy)
SDK->>App: dispatchBatch → callbacks
end
end

rect rgba(255,150,40,0.15)
Note over App,NSP: Config access & send
App->>SDK: getChanInfo(n)
SDK->>SHM: getChanInfo → layout==CENTRAL
SHM->>ADP: getChanInfo(buf, idx)
Note over ADP: translate Central's cbPKT_CHANINFO struct →<br/>cbproto type (7.8: field-for-field copy)
App->>SDK: sendPacket(cmd)
SDK->>SHM: enqueuePacket (ns→ticks if non-Gemini) → XmtGlobal
CTL->>NSP: Central's own loop drains XmtGlobal (4 pkts/10 ms)
end
```

Key contrasts with native client mode: the ring holds **raw device packets from all
instruments** (filtering discards other instruments' packets *after* fully
copying/translating them), config reads go through a struct-translating adapter instead
of direct pointer reads, and instrument status is assumed always-active / read-only.

## How the flow changes with older Central versions

The skeleton above is identical for 7.0–7.7; the deltas are confined to three seams —
version resolution at open, the adapter behind every config accessor, and per-packet
translation in the ring buffer paths:

```mermaid
sequenceDiagram
participant SDK as SdkSession
participant SHM as ShmemSession
participant ADP as v7_x Adapter
participant PT as cbproto::PacketTranslator
participant CTL as Central (old)

Note over SHM,CTL: Open: version → adapter selection
SHM->>CTL: read Central.exe ProductVersion
alt 7.7 / 7.6 → protocol 4.1
SHM->>ADP: v7_7 / v7_6 BootstrapAdapter + Adapter
else 7.5 → protocol 4.0
SHM->>ADP: v7_5 (16-byte header, 8-bit type widens to 16 → shifted offsets)
else 7.0 → protocol 3.11
SHM->>ADP: v7_0 (8-byte header, uint32 tick timestamps)
else major < 7
SHM-->>SDK: error "update Central" → SDK falls back to NATIVE modes
end
Note over SHM,ADP: BootstrapAdapter returns VERSION-SPECIFIC buffer sizes<br/>(segment mapping would be misaligned with wrong sizes)

Note over SDK,PT: Receive: readReceiveBuffer (per packet)
SDK->>SHM: readReceiveBuffer()
alt protocol 3.11
SHM->>SHM: parse 8-byte HEADER_311, copy raw to temp buf
SHM->>SHM: header→current: time = ticks·1e9/30000,<br/>type u8→u16, instrument := 0
SHM->>PT: translatePayload_311_to_current<br/>(NPLAY, COMMENT, CHANINFO, SYSPROTOCOLMONITOR resize)
else protocol 4.0
SHM->>SHM: parse HEADER_400, copy raw to temp buf
SHM->>SHM: header→current (field reorder, type widened)
SHM->>PT: translatePayload_400_to_current
else protocol 4.1
SHM->>SHM: header identical — copy in place
SHM->>PT: translatePayload_410_to_current (payload-only diffs)
else protocol 4.2 (Central 7.8)
SHM->>SHM: straight copy, no PacketTranslator
end
Note over SHM: then (4.0+, non-Gemini): time ticks→ns via sysfreq<br/>then instrument filter — same as current

Note over SDK,CTL: Transmit: enqueuePacket (reverse translation)
SDK->>SHM: enqueuePacket(current-format pkt, time=ns)
alt 3.11
SHM->>PT: current→311 header (ns→30 kHz ticks) + payload
else 4.0 / 4.1
SHM->>PT: current→400/410 (+ ns→ticks when non-Gemini)
end
SHM->>CTL: write legacy-format packet into XmtGlobal ring

Note over SDK,ADP: Config: every get/set goes through the versioned adapter
SDK->>SHM: getProcInfo / getChanInfo / getSpikeCache / getPcStatus ...
SHM->>ADP: v7_x translates that version's struct layout ↔ cbproto types<br/>(array-size clamping/zero-fill via copyArr helpers)
```

### Version-to-behavior summary

| Central app version | Protocol | Header in `cbRECbuffer` | Packet translation | Config/status/spike access |
|---|---|---|---|---|
| 7.8, and any unrecognized 7.x minor | 4.2 (CURRENT) | 16 B, ns timestamps | none | `central` (v7_8) adapter, ~identity copy |
| 7.7, 7.6 | 4.1 | 16 B, identical to current | payload-only (3 packets / 4 type codes) | v7_7 / v7_6 adapters |
| 7.5 | 4.0 | 16 B, u8 type widened to u16 → shifted offsets | header + payload both ways | v7_5 adapter |
| 7.0 | 3.11 | 8 B, u32 ticks @30 kHz | header + payload + timestamp unit conversion; instrument forced to 0 | v7_0 adapter |
| major < 7 | — | — | attach refused → SDK falls back to native modes | — |
Loading
Loading