diff --git a/README.md b/README.md index 4140ed35..dc96836e 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/README.md b/docs/README.md index 662cea2f..646bb993 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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. - - - +See the [client sequence diagrams](client_mode_sequence_diagrams.md). diff --git a/docs/cbSdkOpen_flowchart.svg b/docs/cbSdkOpen_flowchart.svg deleted file mode 100644 index de1e07d8..00000000 --- a/docs/cbSdkOpen_flowchart.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -cbSdkOpencbSdkOpeninstantiate app;app.SdkOpeninstantiate a...No?UDPNo?...Yes?CentralYes?...SharedMutexExists?Shared...OpenOpenRegister `this`IntNetworkEventCallbackRegister `this`...Register `this`ProcessIncomingPacketdelegateRegister `this`...SdkApp : InstNetwork < QThread, InstNetwork:ListenerSdkApp : InstNetwork < QThread, InstNetwo...QAppPriv::pAppQCoreApplicationQAppPriv::pApp...start(highPriority)start(highPriority)wait(connect mutex);wait(connect mut...return error codereturn error codeInstNetwork::runInstNetwork::runNET_EVENTINITNET_EVENT...cbOpencbOpenYesYesstandalone?standalone?OpenCentral'sbuffersOpen...NoNoCreatesharedbuffersCreate...NET_EVENTNETSTANDALONENET_EVENT...NET_EVENTNETCLIENTNET_EVENT...Instrument.Reset()(clear cache)Instrument...InstrumentOpen()(open UDP)Instrument...startTimer(10)startTimer(10)exec()(wait for quit();)exec()...while!m_bDonewhile...cbWaitForData();cbWaitForData();OnWaitEvent();OnWaitEvent();Monitor semaphore in shared bufferMonitor semaphor...cbCheckForDatacbCheckForDataProcessIncomingPacketProcess...handle config packetshandle config pa...listener->ProcessIncomingPacketlistener->...NET_EVENTCLOSENET_EVENT...InstrumentCloseInstrument...cbClosecbCloseInstNetwork::timerEventInstNetwork::timerEventyesyesnonom_bDone?m_bDone?killTimer()QThread::quit()killTimer()...continue handshakecontinue handsha...yesyesfirst 5 secs?first 5 secs?InstrumentTick()Instrument...InstrumentRecv()Instrument...ProcessIncomingPacketProcess...InstrumentSend()Instrument...process packet cacheprocess packet c...cbSdkClosecbSdkCloseDeallocate Trial constructsDeallocate Trial con...NULL callbacksNULL callbacksm_bDone = true;m_bDone = true;Text is not SVG - cannot display \ No newline at end of file diff --git a/docs/cbSdkOpen_flowchart_V2.svg b/docs/cbSdkOpen_flowchart_V2.svg deleted file mode 100644 index 421c0005..00000000 --- a/docs/cbSdkOpen_flowchart_V2.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -cbSdkClosecbSdkCloseDeallocate Trial constructsDeallocate Trial con...NULL callbacksNULL callbacksm_bDone = true;m_bDone = true;cbSdkOpencbSdkOpeninstantiate app;app.SdkOpeninstantiate a...No?ConType UDPNo?...Yes?conType CentralYes?...SdkAppOpenSdkApp...Register `this`IntNetworkEventCallbackRegister `this`...Register `this`ProcessIncomingPacketdelegateRegister `this`...SdkApp : InstNetwork, InstNetwork:ListenerSdkApp : InstNetwork, InstNetwork...return error codereturn error codeSharedMutexExists?Shared...NET_EVENTINITNET_EVENT...cbOpencbOpenyesyesstandalone?standalone?wait for device_iomutexwait for device_i...std::thread device_iostd::thread device_ioCreatesharedbuffersCreate...Instrument.Reset()(clear cache)Instrument...InstrumentOpen()(open UDP)Instrument...m_bDone?m_bDone?first 5 secs?first 5 secs?nonoInstrumentRecv()Instrument...InstrumentSend()Instrument...continue handshakecontinue handsha...if no datasleep(1 ms)if no data...cleanupthreadcleanup...bufferbufferbufferbufferstd::thread buffer_iostd::thread buffer_ioOpensharedbuffersOpen...NET_EVENTCLOSENET_EVENT...InstrumentCloseInstrument...cbClosecbClosem_bDone?m_bDone?cbWaitForData();cbWaitForData();OnWaitEvent();OnWaitEvent();Monitor semaphore in shared bufferMonitor semaphor...cbCheckForDatacbCheckForDataProcessIncomingPacketProcess...handle config packetshandle config pa...listener->ProcessIncomingPacketlistener->...Text is not SVG - cannot display \ No newline at end of file diff --git a/docs/client_mode_sequence_diagrams.md b/docs/client_mode_sequence_diagrams.md new file mode 100644 index 00000000..19e23c99 --- /dev/null +++ b/docs/client_mode_sequence_diagrams.md @@ -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(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)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 +callback thread + participant SHM as ShmemSession(NATIVE, writer) + participant DEV as cbdev DeviceSession(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 segmentsinit cfg (owner_pid), xmt rings, status, spike cache + SDK->>DEV: createDeviceSession(params) + Note over DEV: ProtocolDetector probes device →wraps in DeviceSession_311/400/410 if old protocol + SDK->>SDK: start(): spawn callback thread,register recv + datagram callbacks,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→ CURRENT format before anyone sees it + loop each packet in datagram + DEV->>SDK: receive callback(pkt) + SDK->>SHM: storePacket → writeToReceiveBuffer(wrap-marker padding, release-store head) + SDK->>SHM: mirror config reps: setProcInfo / setSysInfo /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,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(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,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,no instrument filtering (single-device ring),skip chid=0/type=0 wrap markers + SDK->>SDK: scan batch: SYSREP → runlevel,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 —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(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 →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,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,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 devicepackets 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,copy packet as-is (no translation)non-Gemini system: header.time ticks→ns via sysfreqthen 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 →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(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,type u8→u16, instrument := 0 + SHM->>PT: translatePayload_311_to_current(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 sysfreqthen 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(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 | — | diff --git a/docs/multi_version_central_compat/README.md b/docs/multi_version_central_compat/README.md new file mode 100644 index 00000000..2b54d69f --- /dev/null +++ b/docs/multi_version_central_compat/README.md @@ -0,0 +1,239 @@ +# Multi-Version Central Compatibility + +**Author**: Caden Shmookler +**Date**: 2026-06-16 + + +## Brief + +This document describes a compatibility layer within CereLink which enables cross-version compatibility with Central's shared memory. Previous versions of CereLink only supported the latest version of Central. This compatibility layer consists of an `Adapter` class which hides the implementation differences between Central versions behind a common interface. Adapters are selected per Central application version (see the `CentralVersion` enum in [Organization](#organization)). + +Central has a 'protocol version' and an 'application version'. As of the writing of this document, the most recent protocol and application versions are 4.2 and 7.8.0 respectively. Shared memory compatibility is dependent on the protocol version. Protocols with different major version numbers are completely incompatible whereas protocols with different minor version numbers may be partially compatible. Different application versions do not break protocol compatibility unless the protocol version has also changed. + + +## Organization + +Each supported application version of Central has a `BootstrapAdapter` and `Adapter`. The `BootstrapAdapter` class fetches the sizes of shared memory structs in order to instantiate an `Adapter` with raw pointers to each struct. Each adapter has a collection of Central structs which are translated to and from the native CereLink equivalents. These native equivalents (defined in cbproto and native_types.h) are the common language used by `ShmemSession` to perform operations on the shared memory buffers. + +Adapters are named after the Central *application* version they support (e.g. `v7_0`, `v7_5`), not the protocol version. The `CentralVersion` enum in `src/cbshm/include/cbshm/central_version.h` enumerates every supported application version: `V7_0`, `V7_5`, `V7_6`, `V7_7`, and `CURRENT` (the newest supported version, currently 7.8). + +The structure and sizes of types are defined in `src/cbshm/include/cbshm/central_types/.h`. The adapter and bootstrap adapter classes are declared in `src/cbshm/include/cbshm/central_adapters/.h`, and their translation behavior is defined in `src/cbshm/src/central_adapters/.cpp`. Each version lives in its own namespace (e.g. `central_v7_0`). The current version's types and adapters live in `central_v7_8`, which `src/cbshm/include/cbshm/central_current.h` aliases as `central`. Version headers in `src/cbshm/include/cbshm/central_adapters/*` should be nearly identical. + +At runtime, `detectCentralVersion` (declared in `src/cbshm/include/cbshm/central_version.h`, defined in `src/cbshm/src/central_version.cpp`) inspects the application version of the running `Central.exe` and returns the matching `CentralVersion`. `getProtocolVersion` converts a `CentralVersion` to its protocol version for the receive/transmit buffer logic. `ShmemSession::Impl::open` uses the detected `CentralVersion` to select the appropriate `BootstrapAdapter` and `Adapter`. + + +## Limitations + +This compatibility layer is limited to Central's configuration, status, and spike buffers. The receive and transmit buffers are handled by brittle logic spread throughout cbdev, cbproto, and cbshm. Replacing this brittle logic with another adapter class encapsulating all version-specific code would dramatically simplify the process of adding protocol versions. + + +## Add a Version + +This section provides instructions for adding support for a version of Central's shared memory. + +By default, new application versions of Central are assumed to use the latest protocol version. If Central's application version increments but it's protocol version remains the same, no action is required to enable compatibility. + +If a version of Central has a newer protocol version than what's currently supported, follow the [newer version](#add-a-newer-protocol-version) instructions below. + +If a version of Central has an older protocol version than what's currently supported, follow the [older version](#add-an-older-protocol-version) instructions below. + + +### Add a newer protocol version + +#### 1. Add the version to cbproto_protocol_version + +```bash +editor src/cbproto/include/cbproto/connection.h +``` + +`CBPROTO_PROTOCOL_CURRENT` now codes for the added version. Add a new value for the replaced version (e.g. `CBPROTO_PROTOCOL_420`). + +#### 2. Implement receive/transmit buffer operations + +Multiple files throughout cbdev, cbproto, and cbshm contain version-specific logic (pertaining to the receive and transmit buffers) that must be changed to include a case for the added version. + +> Note: This is a complex operation involving large portions of CereLink (depending on what was changed in the new protocol). See the [limitations](#limitations) section for an idea on how this process could be simplified in the future. + +#### 3. Duplicate version headers and implementation from an existing version + +Replace `` with the name of an existing version and `` with the name of the version that's being added. Duplicate an existing version that's similar in structure and behavior to the version that's being added. + +```bash +cp src/cbshm/include/cbshm/central_types/.h src/cbshm/include/cbshm/central_types/.h +cp src/cbshm/include/cbshm/central_adapters/.h src/cbshm/include/cbshm/central_adapters/.h +cp src/cbshm/src/central_adapters/.cpp src/cbshm/src/central_adapters/.cpp +``` + +#### 4. Name the added version + +Change all references to the existing version to the added version. + +```bash +editor src/cbshm/include/cbshm/central_adapters/.h +editor src/cbshm/include/cbshm/central_types/.h +editor src/cbshm/src/central_adapters/.cpp +``` + +#### 5. Rectify the types for the added version + +```bash +editor src/cbshm/include/cbshm/central_types/.h +``` + +If the added version contains changes to types or constants that are not already in the header, add them by copying directly from Central or cbproto. Update the hardcoded sizes within the static_assert expressions to match ground truth in Central and cbproto. Verify your changes by diffing the existing version header with the added version header. + +#### 6. Rectify the translators and adapter for the added version + +```bash +editor src/cbshm/src/central_adapters/.cpp +``` + +Verify your changes by diffing the existing version header with the added version header. + +#### 7. Register the version + +First, add a value for the version to the `CentralVersion` enum. Because the added version is newer than any currently supported version, it becomes the new `CURRENT`; add an explicit value for the version that `CURRENT` previously coded for (e.g. `V7_8`). + +```bash +editor src/cbshm/include/cbshm/central_version.h +``` + +Repoint the `central` alias at the added version's namespace. + +```bash +editor src/cbshm/include/cbshm/central_current.h +``` + +Map the application version to the added `CentralVersion` value in `detectCentralVersion`, and map each `CentralVersion` value to its protocol version in `getProtocolVersion`. The added version maps to `CBPROTO_PROTOCOL_CURRENT`; the previously-current version now maps to its own (frozen) protocol version. + +```bash +editor src/cbshm/src/central_version.cpp +``` + +Finally, register the adapter with `ShmemSession`. Add `#include .h>` and `#include .h>` near the top of the file and append cases for the new `CentralVersion` values to both switch statements in `ShmemSession::Impl::open` (one selects the `BootstrapAdapter`, the other selects the `Adapter`). The `CURRENT` case uses the `central::` alias, so the added version is reached through it; add an explicit case for the previously-current version. + +```bash +editor src/cbshm/src/shmem_session.cpp +``` + +#### 8. Add the adapter implementation to CMakeLists.txt + +```bash +editor src/cbshm/CMakeLists.txt +``` + +Append `src/central_adapters/.cpp` to the `CBSHMEM_SOURCES` environment variable. + +#### 9. Update types in cbproto to match the added version + +The types in cbproto must match the most recent protocol version, `CBPROTO_PROTOCOL_CURRENT`. Changes to these types may cause downstream side effects elsewhere in CereLink, including but not limited to the `PacketTranslator` and `DeviceSession` classes. + +> Note: The cbproto types are used for direct communication with instruments instead of with Central. If there are differences in the protocol between these targets then they must be reflected in the code. + +> Note: This is a complex operation involving large portions of CereLink (depending on what was changed in the new protocol). See the [limitations](#limitations) section for an idea on how this process could be simplified in the future. + +#### 10. Rectify the translators and adapters for all older versions + +All translators and adapters use the cbproto types, so these methods must be fixed to translate to the added version instead. + +#### 11. Add the version to the adapter unit tests + +```bash +editor tests/unit/test_central_adapters.cpp +``` + +Add `#include .h>` near the top of the file, define a `VersionTraits` alias for the version (e.g. `using V7_9 = VersionTraits;`), and add that alias to the `AllVersions` type list so the round-trip invariants run against it. If the added version's protocol diverges from the others (e.g. the NSP-status and Gemini fields introduced in protocol 4.0+), cover that behavior with dedicated tests. The test file has no entry in `tests/unit/CMakeLists.txt` to update — it already compiles every version through the `AllVersions` type list. + + +### Add an older protocol version + +#### 1. Add the version to cbproto_protocol_version + +```bash +editor src/cbproto/include/cbproto/connection.h +``` + +Add a new value for the added version (e.g. `CBPROTO_PROTOCOL_309`). + +#### 2. Implement receive/transmit buffer operations + +Multiple files throughout cbdev, cbproto, and cbshm contain version-specific logic (pertaining to the receive and transmit buffers) that must be changed to include a case for the added version. + +> Note: This is a complex operation involving large portions of CereLink (depending on what was changed in the new protocol). See the [limitations](#limitations) section for an idea on how this process could be simplified in the future. + +#### 3. Duplicate version headers and implementation from an existing version + +Replace `` with the name of an existing version and `` with the name of the version that's being added. Duplicate an existing version that's similar in structure and behavior to the version that's being added. + +```bash +cp src/cbshm/include/cbshm/central_types/.h src/cbshm/include/cbshm/central_types/.h +cp src/cbshm/include/cbshm/central_adapters/.h src/cbshm/include/cbshm/central_adapters/.h +cp src/cbshm/src/central_adapters/.cpp src/cbshm/src/central_adapters/.cpp +``` + +#### 4. Name the added version + +Change all references to the existing version to the added version. + +```bash +editor src/cbshm/include/cbshm/central_adapters/.h +editor src/cbshm/include/cbshm/central_types/.h +editor src/cbshm/src/central_adapters/.cpp +``` + +#### 5. Rectify the types for the added version + +```bash +editor src/cbshm/include/cbshm/central_types/.h +``` + +If the added version contains changes to types or constants that are not already in the header, add them by copying directly from Central or cbproto. Update the hardcoded sizes within the static_assert expressions to match ground truth in Central and cbproto. Verify your changes by diffing the existing version header with the added version header. + +#### 6. Rectify the translators and adapter for the added version + +```bash +editor src/cbshm/src/central_adapters/.cpp +``` + +Verify your changes by diffing the existing version header with the added version header. + +#### 7. Register the version + +First, add a value for the version to the `CentralVersion` enum. + +```bash +editor src/cbshm/include/cbshm/central_version.h +``` + +Map the application version to the added `CentralVersion` value in `detectCentralVersion`, and map the added `CentralVersion` value to its protocol version in `getProtocolVersion`. + +```bash +editor src/cbshm/src/central_version.cpp +``` + +Finally, register the adapter with `ShmemSession`. Add `#include .h>` and `#include .h>` near the top of the file and append a case for the added `CentralVersion` value to both switch statements in `ShmemSession::Impl::open` (one selects the `BootstrapAdapter`, the other selects the `Adapter`). + +```bash +editor src/cbshm/src/shmem_session.cpp +``` + +#### 8. Add the adapter implementation to CMakeLists.txt + +```bash +editor src/cbshm/CMakeLists.txt +``` + +Append `src/central_adapters/.cpp` to the `CBSHMEM_SOURCES` environment variable. + +#### 9. Add the version to the adapter unit tests + +```bash +editor tests/unit/test_central_adapters.cpp +``` + +Add `#include .h>` near the top of the file, define a `VersionTraits` alias for the version (e.g. `using V7_1 = VersionTraits;`), and add that alias to the `AllVersions` type list so the round-trip invariants run against it. If the added version's protocol diverges from the others (e.g. the NSP-status and Gemini fields introduced in protocol 4.0+), cover that behavior with dedicated tests. The test file has no entry in `tests/unit/CMakeLists.txt` to update — it already compiles every version through the `AllVersions` type list. + + +## Remove a protocol version + +Follow the instructions for adding a new version in reverse. To disable support for a specific version instead of outright removing it, replace the adapter construction in `ShmemSession::Impl::open` within `src/cbshm/src/shmem_session.cpp` with an error. diff --git a/docs/planning/multi_version_central_compat.md b/docs/multi_version_central_compat/proposal.md similarity index 98% rename from docs/planning/multi_version_central_compat.md rename to docs/multi_version_central_compat/proposal.md index 33145fe3..dc94606d 100644 --- a/docs/planning/multi_version_central_compat.md +++ b/docs/multi_version_central_compat/proposal.md @@ -37,9 +37,9 @@ a stable interface, and keeps the most-common path (no Central running) unchange ## 2. Background -See [shared_memory_architecture.md](shared_memory_architecture.md) for the current +See [shared_memory_architecture.md](../shared_memory_architecture.md) for the current two-mode (NATIVE / CENTRAL_COMPAT) layout, and -[central_shared_memory_layout.md](central_shared_memory_layout.md) for upstream +[central_shared_memory_layout.md](../central_shared_memory_layout.md) for upstream Central's layout. The current `ShmemSession` carries a `ShmemLayout` enum (`CENTRAL`, `CENTRAL_COMPAT`, @@ -559,8 +559,8 @@ Each step is independently shippable; old behavior is preserved until step 6. ## 13. References -- [shared_memory_architecture.md](shared_memory_architecture.md) — current architecture -- [central_shared_memory_layout.md](central_shared_memory_layout.md) — Central's layout +- [shared_memory_architecture.md](../shared_memory_architecture.md) — current architecture +- [central_shared_memory_layout.md](../central_shared_memory_layout.md) — Central's layout - `src/cbproto/include/cbproto/packet_translator.h` — existing version-tagged packet translation - `src/cbshm/src/shmem_session.cpp` — current `ShmemSession` dispatch sites - `src/cbshm/include/cbshm/central_types.h` — current `CentralLegacyCFGBUFF` diff --git a/docs/shared_memory_architecture.md b/docs/shared_memory_architecture.md index be9e45e1..01cc4302 100644 --- a/docs/shared_memory_architecture.md +++ b/docs/shared_memory_architecture.md @@ -8,17 +8,17 @@ CereLink supports two shared memory modes: Lean, right-sized buffers. No instance index -- devices identified by type. Packets are always in the current protocol format. -- **Central compat mode** (fallback): CereLink attaches to Central's existing shared memory - as a CLIENT. Uses `CentralLegacyCFGBUFF` to match Central's exact binary layout (which - differs from CereLink's `cbConfigBuffer`). Instrument filtering extracts only the - requested device's packets from Central's shared receive buffer. Protocol translation - handles older Central formats (3.11, 4.0, 4.1) automatically. +- **Central mode** (fallback): CereLink attaches to Central's existing shared memory + as a CLIENT. Uses `cbCFGBUFF` to match Central's exact binary layout (which differs from + CereLink's `cbConfigBuffer`). Instrument filtering extracts only the requested device's + packets from Central's shared receive buffer. Protocol translation handles older Central + formats (3.11, 4.0, 4.1) automatically. -Mode is auto-detected at startup: if Central's shared memory exists, use compat mode; +Mode is auto-detected at startup: if Central's shared memory exists, use Central mode; otherwise, use native mode. **See also**: [Central's shared memory layout](central_shared_memory_layout.md) for the -upstream layout that compat mode interoperates with. +upstream layout that central mode interoperates with. ## Device Identification @@ -45,39 +45,39 @@ auto session = SdkSession::open(DeviceType::HUB1); ## Architecture Diagram (Native Mode) ``` -+------------------------------------------------------------------------------------+ ++-------------------------------------------------------------------------------------+ | CEREBUS DEVICE | | (NSP Hardware - UDP Protocol) | -+--------------------+-----------------------------------+---------------------------+ ++--------------------+-----------------------------------+----------------------------+ | | | UDP Packets | UDP Packets | (Port varies by device) | (Port varies by device) v ^ +-------------------------------------------------------------------------------------+ -| STANDALONE PROCESS (owns device) | +| STANDALONE PROCESS (owns device) | | +--------------------------------------------------------------------------------+ | -| | THREADS | | -| | | | -| | +----------------+ +----------------+ +-----------------+ | | -| | | UDP Receive | | UDP Send | | Callback | | | -| | | Thread | | Thread | | Dispatcher | | | -| | | (cbdev) | | (cbdev) | | Thread | | | +| | THREADS | | +| | | | +| | +----------------+ +----------------+ +-----------------+ | | +| | | UDP Receive | | UDP Send | | Callback | | | +| | | Thread | | Thread | | Dispatcher | | | +| | | (cbdev) | | (cbdev) | | Thread | | | | | +--------+-------+ +--------^-------+ +--------^-------+ | | -| | | | | | | -| | | Packets | Dequeue | Process | | -| | | (translated to | packets | callbacks | | -| | | current protocol) | | | | -| | v | | | | -| | +--------+-----------------------+-----------------------+-------+ | | -| | | onPacketsReceivedFromDevice() | | | -| | | | | | -| | | 1. storePacket() -> rec buffer (ring buffer) | | | -| | | 2. storePacket() -> cfg buffer (config updates) | | | -| | | 3. signalData() -> signal event <-----------+ | | | -| | | 4. Enqueue to local SPSC queue | SIGNAL! | | | -| | +--------------------------------------------------+ | | | +| | | | | | | +| | | Packets | Dequeue | Process | | +| | | (translated to | packets | callbacks | | +| | | current protocol) | | | | +| | v | | | | +| | +--------+-----------------------+-----------------------+-------+ | | +| | | onPacketsReceivedFromDevice() | | | +| | | | | | +| | | 1. storePacket() -> rec buffer (ring buffer) | | | +| | | 2. storePacket() -> cfg buffer (config updates) | | | +| | | 3. signalData() -> signal event <-----------+ | | | +| | | 4. Enqueue to local SPSC queue | SIGNAL! | | | +| | +--------------------------------------------------+ | | | | +--------------------------------------------------------------------------------+ | -+------------------+----------------------------------------------------------------------+ ++------------------+------------------------------------------------------------------+ | | Writes to (Producer) | @@ -85,12 +85,12 @@ auto session = SdkSession::open(DeviceType::HUB1); || NATIVE SHARED MEMORY (per device) || || Named: cbshm_{device}_{segment} || || || - || 1. cbshm_hub1_cfg | Config for 1 device (284 channels, ~1 MB) || - || 2. cbshm_hub1_rec | Receive ring buffer (~256 MB) || - || 3. cbshm_hub1_xmt | Transmit queue (~5 MB) || + || 1. cbshm_hub1_config | Config for 1 device (284 channels, ~1 MB) || + || 2. cbshm_hub1_receive | Receive ring buffer (~256 MB) || + || 3. cbshm_hub1_xmt_global | Transmit queue (~5 MB) || || 4. cbshm_hub1_xmt_local | Local transmit queue (~2 MB) || || 5. cbshm_hub1_status | Device status (~few KB) || - || 6. cbshm_hub1_spk | Spike cache (272 analog channels) || + || 6. cbshm_hub1_spike | Spike cache (272 analog channels) || || 7. cbshm_hub1_signal | Data availability signal || || || || All packets stored in CURRENT protocol format (translation at cbdev layer) || @@ -101,17 +101,17 @@ auto session = SdkSession::open(DeviceType::HUB1); | Reads from (Consumer) | +------------------v-----------------------------------------------------------------------+ -| CLIENT PROCESS (attaches to shmem) | +| CLIENT PROCESS (attaches to shmem) | | +-------------------------------------------------------------------------------------+ | | | Shared Memory Receive Thread | | -| | | | -| | while (running) { | | -| | waitForData(250ms) <-- wakeup from signal | | -| | if (signaled) { | | -| | readReceiveBuffer() <-- all packets are this device's | | -| | packet_callback(packets, count) <-- direct invocation, no queue | | -| | } | | -| | } | | +| | | | +| | while (running) { | | +| | waitForData(250ms) <-- wakeup from signal | | +| | if (signaled) { | | +| | readReceiveBuffer() <-- all packets are this device's | | +| | packet_callback(packets, count) <-- direct invocation, no queue | | +| | } | | +| | } | | | +-------------------------------------------------------------------------------------+ | +-----------------------------------------------------------------------------------------+ ``` @@ -126,16 +126,16 @@ Each device gets its own set of shared memory segments: cbshm_{device}_{segment} Examples: - cbshm_nsp_cfg cbshm_hub1_cfg cbshm_hub2_cfg - cbshm_nsp_rec cbshm_hub1_rec cbshm_hub2_rec - cbshm_nsp_xmt cbshm_hub1_xmt cbshm_hub2_xmt + cbshm_nsp_config cbshm_hub1_config cbshm_hub2_config + cbshm_nsp_receive cbshm_hub1_receive cbshm_hub2_receive + cbshm_nsp_xmt_global cbshm_hub1_xmt_global cbshm_hub2_xmt_global cbshm_nsp_xmt_local cbshm_hub1_xmt_local cbshm_hub2_xmt_local cbshm_nsp_status cbshm_hub1_status cbshm_hub2_status - cbshm_nsp_spk cbshm_hub1_spk cbshm_hub2_spk + cbshm_nsp_spike cbshm_hub1_spike cbshm_hub2_spike cbshm_nsp_signal cbshm_hub1_signal cbshm_hub2_signal ``` -On POSIX, names are prefixed with `/` (e.g., `/cbshm_hub1_rec`). +On POSIX, names are prefixed with `/` (e.g., `/cbshm_hub1_receive`). ### Per-Device Config Buffer (Native) @@ -183,15 +183,15 @@ Channel count per device (`NATIVE_MAXCHANS` with `cbMAXPROCS=1`): ### Per-Device Memory Footprint -| Segment | Central-compat (4 instruments) | Native (1 device) | Savings | -|----------------|--------------------------------|---------------------------|----------| -| Config buffer | ~4 MB (880 ch, `[4]` arrays) | ~1 MB (284 ch, scalars) | ~75% | -| Receive buffer | ~768 MB (768 FE ch) | ~256 MB (256 FE ch) | ~67% | -| XmtGlobal | ~290 MB (5000 * max-UDP-size) | ~5 MB (5000 * 1024 bytes) | ~98% | -| XmtLocal | ~116 MB (2000 * max-UDP-size) | ~2 MB (2000 * 1024 bytes) | ~98% | -| Spike cache | large (832 analog ch) | ~1/3 (272 analog ch) | ~67% | -| Status | ~few KB | ~few KB | -- | -| **Total** | **~1.2 GB** | **~265 MB** | **~78%** | +| Segment | Central (4 instruments) | Native (1 device) | +|----------------|--------------------------------|---------------------------| +| Config buffer | ~4 MB (880 ch, `[4]` arrays) | ~1 MB (284 ch, scalars) | +| Receive buffer | ~768 MB (768 FE ch) | ~256 MB (256 FE ch) | +| XmtGlobal | ~290 MB (5000 * max-UDP-size) | ~5 MB (5000 * 1024 bytes) | +| XmtLocal | ~116 MB (2000 * max-UDP-size) | ~2 MB (2000 * 1024 bytes) | +| Spike cache | large (832 analog ch) | ~1/3 (272 analog ch) | +| Status | ~few KB | ~few KB | +| **Total** | **~1.2 GB** | **~265 MB** | The transmit buffers are dramatically smaller because they carry only config/command packets (max 1024 bytes each), not max-UDP-sized packets. Central's XmtGlobal is drained at 4 @@ -210,14 +210,13 @@ Device (any protocol) --> cbdev DeviceSession wrapper --> translates to current This means CLIENT processes never need to know what protocol the device speaks. -## Central Compat Mode +## Central Mode When CereLink detects that Central is running (its shared memory segments exist), it -attaches as a CLIENT using the `CENTRAL_COMPAT` shared memory layout. This layout uses -`CentralLegacyCFGBUFF` -- a struct matching Central's exact binary field order -- instead -of CereLink's own `cbConfigBuffer` (which has incompatible field ordering). +attaches as a CLIENT using the `CENTRAL` shared memory layout. This layout uses +`cbCFGBUFF` which matches Central's exact binary field order. -### Segment Names (Central's) +### Segment Names Central uses instance-0 bare names: - `cbCFGbuffer`, `cbRECbuffer`, `XmtGlobal`, `XmtLocal`, `cbSTATUSbuffer`, `cbSPKbuffer`, @@ -250,38 +249,42 @@ For legacy (non-Gemini) NSP systems, only instrument index 0 is used. ### Receive Buffer Filtering -In Central's shared memory, ALL devices' packets go into ONE receive ring buffer. CereLink's -`setInstrumentFilter()` method configures `readReceiveBuffer()` to filter by instrument: - -1. `SdkSession::create()` sets the instrument filter based on DeviceType → instrument index -2. `readReceiveBuffer()` reads all packets from `cbRECbuffer` (advances the ring buffer tail) -3. For each packet, checks `cbpkt_header.instrument` against the filter -4. Packets not matching the filter are consumed (tail advances) but not delivered to the caller - -```cpp -// Set automatically by SdkSession::create() in compat mode -shmem_session.setInstrumentFilter(getCentralInstrumentIndex(config.device_type)); - -// readReceiveBuffer() internally skips non-matching packets -session.readReceiveBuffer(packets, max_count, packets_read); -// packets_read only includes packets matching the instrument filter -``` +In Central's shared memory, ALL devices' packets go into ONE receive ring buffer. +`readReceiveBuffer()` filters these packets so only those belonging to the selected instrument +are read. This is less efficient than native mode (where the receive buffer only contains one device's -packets), but the large buffer size (~768 MB) makes this a negligible cost. +packets). Because `readReceiveBuffer()` copies and translates each packet into the output slot +*before* inspecting its instrument field, packets belonging to other instruments are fully +processed and then discarded (the slot is overwritten on the next iteration). The overhead +therefore scales with the total cross-instrument packet rate, not just the selected device's +traffic. The per-packet instrument check itself is a single integer comparison; the cost that +matters is the wasted copy/translate work for discarded packets. -### Protocol Translation in Compat Mode (Phase 3 - Complete) +### Protocol Translation in Compat Mode When CereLink attaches to Central's shared memory, Central may be running an older protocol (3.11, 4.0, or 4.1). Central stores raw device packets in `cbRECbuffer` without translation. CereLink detects the protocol version and translates packets on-the-fly. -**Protocol detection** reads `procinfo[0].version` from `CentralLegacyCFGBUFF`: -- `version = (major << 16) | minor` (MAKELONG format) -- major < 4 → Protocol 3.11 (8-byte headers, 32-bit timestamps) -- major=4, minor=0 → Protocol 4.0 (16-byte headers, different field layout) -- major=4, minor=1 → Protocol 4.1 (16-byte headers, current layout) -- major=4, minor≥2 → Current protocol (no translation needed) +**Version detection** (`detectCentralVersion`) cannot rely on Central's shared memory: +the `version` field in `cbCFGBUFF` is a magic number (`96`), not a meaningful version, and +`procinfo[].version` is unusable because its byte offset shifts between protocol versions. +Instead, detection inspects the **application version** of the running `Central.exe` +(`VersionInfo.ProductVersion`) and maps it to a `CentralVersion` enum value. A companion +function, `getProtocolVersion`, converts that `CentralVersion` to its protocol version. This +is **Windows-only**; on other platforms compat mode is unavailable. + +The application-version → protocol-version mapping: +- Central 7.0 → Protocol 3.11 (8-byte headers, 32-bit timestamps) +- Central 7.5 → Protocol 4.0 (16-byte headers, different field layout) +- Central 7.6, 7.7 → Protocol 4.1 (16-byte headers, current layout) +- Central 7.8 (and newer 7.x) → Current protocol 4.2+ (no translation needed) +- Central major version < 7 → unsupported (caller must upgrade Central) + +This indirect approach is brittle: a future Central with a different executable name or +version-string format would defeat it. See `detectCentralVersion` and `getProtocolVersion` +in `central_version.cpp`. **Receive path** (`readReceiveBuffer`): Parses the protocol-specific header to extract `dlen`, copies raw bytes from the ring buffer, translates header + payload to current @@ -297,30 +300,32 @@ library) so that both `cbshm` and `cbdev` can access it. ### Config Buffer Access in Compat Mode -Central's `cbCFGBUFF` has a different field layout than CereLink's `NativeConfigBuffer` or -`cbConfigBuffer` (see "Differences from Central" section below). The `CENTRAL_COMPAT` layout -uses a `CentralLegacyCFGBUFF` struct that matches Central's exact field order to read the -config buffer correctly. +Central's `cbCFGBUFF` has a different field layout than CereLink's `NativeConfigBuffer`. +The `CENTRAL` layout uses a `cbCFGBUFF` struct that matches Central's exact field order to +read the config buffer correctly. All `ShmemSession` accessor methods (`getProcInfo`, `setBankInfo`, `getFilterInfo`, etc.) -dispatch on the layout and use the correct struct: +dispatch on the layout and either use the native buffer or the Central adapter: ```cpp -// In CENTRAL_COMPAT mode, accessors use legacyCfg() -if (layout == ShmemLayout::CENTRAL_COMPAT) - return legacyCfg()->procinfo[idx]; // CentralLegacyCFGBUFF -else if (layout == ShmemLayout::NATIVE) - return nativeCfg()->procinfo; // NativeConfigBuffer (scalar) -else - return centralCfg()->procinfo[idx]; // cbConfigBuffer +if (layout == ShmemLayout::NATIVE) { + return Result::ok(nativeCfg()->procinfo); +} else { + auto info = Result::ok({}); + auto res = adapter->getProcInfo(info.value()); + if (res.isError()) { + return Result::error(res.error()); + } + return info; +} ``` -Instrument status in compat mode: +Instrument status in central mode: - `isInstrumentActive()` always returns **true** (Central has no `instrument_status` field; if the shared memory exists, instruments are configured by Central) - `setInstrumentActive()` returns **error** (read-only in compat mode) - `getConfigBuffer()` returns **nullptr** (wrong struct type for compat mode) -- `getLegacyConfigBuffer()` returns the `CentralLegacyCFGBUFF*` pointer +- `getLegacyConfigBuffer()` returns a copy of Central's configuration struct translated to a `NativeConfigBuffer` ## Mode Auto-Detection @@ -329,12 +334,12 @@ SdkSession::open(DeviceType::HUB1) | +-- Can open Central's "cbSharedDataMutex" (instance 0)? | | - | YES --> Central Compat Mode (CENTRAL_COMPAT layout) + | YES --> Central Mode (CENTRAL layout) | - Map Central's 7 instance-0 segments - | - Use CentralLegacyCFGBUFF for config buffer (Central's binary layout) + | - Use cbCFGBUFF for config buffer (Central's binary layout) | - Use GEMSTART==2 mapping: Hub1 = instrument index 0 - | - Set instrument filter (setInstrumentFilter) for receive buffer - | - Index into [4] arrays for config access via legacyCfg() + | - Set instrument filter for receive buffer + | - Target instrument is fixed for the session's lifetime (set at creation) | - Detect protocol version, translate packets if non-current | +-- NO --> Can open "cbshm_hub1_signal"? @@ -346,10 +351,10 @@ SdkSession::open(DeviceType::HUB1) | - Config is single-instrument (no indexing) | +-- NO --> Native Standalone Mode - - Create cbshm_hub1_* segments - - Start cbdev (protocol auto-detect + translation) - - Write current-format packets to native shmem - - Other CLIENTs can attach via Native Client Mode + - Create cbshm_hub1_* segments + - Start cbdev (protocol auto-detect + translation) + - Write current-format packets to native shmem + - Other CLIENTs can attach via Native Client Mode ``` ## Key Data Flow Paths @@ -443,79 +448,6 @@ an SPSC queue to buffer between fast UDP receive and slow callback processing. - One extra thread (simpler architecture, less overhead) - One extra data copy (receive buffer -> callback, no packet_queue needed) -## Differences from Central-Suite's Shared Memory Layout - -CereLink's current `cbConfigBuffer` struct is **NOT binary-compatible** with Central's -`cbCFGBUFF`. For a complete description of Central's layout, see -[central_shared_memory_layout.md](central_shared_memory_layout.md). - -### cbCFGbuffer / Config Buffer (INCOMPATIBLE) - -Field ordering is changed and fields are added/removed: - -| # | Central `cbCFGBUFF` | CereLink `cbConfigBuffer` | -|----|----------------------|----------------------------------| -| 1 | `version` | `version` | -| 2 | `sysflags` | `sysflags` | -| 3 | **`optiontable`** | **`instrument_status[4]`** (NEW) | -| 4 | **`colortable`** | `sysinfo` | -| 5 | `sysinfo` | `procinfo[4]` | -| 6 | `procinfo[4]` | `bankinfo[4][30]` | -| 7 | `bankinfo[4][30]` | `groupinfo[4][8]` | -| 8 | `groupinfo[4][8]` | `filtinfo[4][32]` | -| 9 | `filtinfo[4][32]` | `adaptinfo[4]` | -| 10 | `adaptinfo[4]` | `refelecinfo[4]` | -| 11 | `refelecinfo[4]` | **`isLnc[4]`** (MOVED earlier) | -| 12 | `chaninfo[880]` | `chaninfo[880]` | -| 13 | `isSortingOptions` | `isSortingOptions` | -| 14 | `isNTrodeInfo[..]` | `isNTrodeInfo[..]` | -| 15 | `isWaveform[..][..]` | `isWaveform[..][..]` | -| 16 | **`isLnc[4]`** | `isNPlay` | -| 17 | `isNPlay` | `isVideoSource[..]` | -| 18 | `isVideoSource[..]` | `isTrackObj[..]` | -| 19 | `isTrackObj[..]` | `fileinfo` | -| 20 | `fileinfo` | **`optiontable`** (MOVED later) | -| 21 | **`hwndCentral`** | **`colortable`** (MOVED later) | -| 22 | -- | (hwndCentral OMITTED) | - -Central compat mode requires a separate `CentralLegacyCFGBUFF` struct matching Central's -exact layout to read the config buffer correctly. - -### cbSTATUSbuffer / PC Status (PARTIALLY COMPATIBLE) - -| Difference | Central `cbPcStatus` | CereLink `CentralPCStatus` | -|---------------------|-----------------------------------------------|-----------------------------| -| Type | C++ class (private/public) | Plain C struct | -| `APP_WORKSPACE[10]` | Present (at end) | **Omitted** | -| Overlap | Fields match in order up to `m_nGeminiSystem` | Same | - -CereLink's struct is a subset -- safe to read, safe to write (omitted field is at the end). - -### cbRECbuffer, XmtGlobal, XmtLocal (COMPATIBLE) - -Same field layout: -- `received`, `lasttime`, `headwrap`, `headindex`, `buffer[N]` (receive) -- `transmitted`, `headindex`, `tailindex`, `last_valid_index`, `bufferlen`, `buffer[N]` (transmit) - -Central uses flexible array member (`buffer[0]`), CereLink uses fixed-size. Binary layout -matches as long as allocated sizes match. - -### cbSPKbuffer, cbSIGNALevent (COMPATIBLE) - -Same structure layouts and mechanisms. - -### Compatibility Summary - -| Segment | Compatible? | Notes | -|----------------|--------------|--------------------------------------------------| -| cbCFGbuffer | **NO** | Field order differs; need `CentralLegacyCFGBUFF` | -| cbRECbuffer | Yes | Same layout | -| XmtGlobal | Yes | Same layout | -| XmtLocal | Yes | Same layout | -| cbSTATUSbuffer | Partial | CereLink is a subset (missing workspace at end) | -| cbSPKbuffer | Yes | Same layout | -| cbSIGNALevent | Yes | Same mechanism | - ## Known Limitations - GEMSTART mapping is hardcoded to `GEMSTART==2`. If a Central build uses a different @@ -527,7 +459,10 @@ Same structure layouts and mechanisms. - **Shared Memory**: `src/cbshm/` - `include/cbshm/shmem_session.h` - Public API (ShmemSession class) - `src/shmem_session.cpp` - Implementation - - `include/cbshm/central_types.h` - Central-compatible buffer structures + `CentralLegacyCFGBUFF` + - `include/cbshm/central_adapters/base.h` - Base class of the Central adapters + - `include/cbshm/central_adapters/.h` - Per-version Central adapters + - `include/cbshm/central_types/.h` - Per-version Central-compatible buffer structures + `cbCFGBUFF` + - `src/central_adapters/.cpp` - Per-version Central adapter implementations - `include/cbshm/native_types.h` - Native-mode buffer structures (single-instrument) - `include/cbshm/config_buffer.h` - Configuration buffer struct definition (`cbConfigBuffer`) @@ -552,6 +487,7 @@ Same structure layouts and mechanisms. ## References - [Central's shared memory layout](central_shared_memory_layout.md) +- [Central adapter](multi_version_central_compat/README.md) - Central Suite source: `Central-Suite/cbhwlib/cbhwlib.h` and `cbhwlib.cpp` - Central instrument assignment: `Central-Suite/CentralCommon/CentralDlg.cpp` (GEMSTART) - Cerebus Protocol Specification diff --git a/examples/CCFTest/ccf_test.cpp b/examples/CCFTest/ccf_test.cpp index 28b8beeb..d12cd80c 100644 --- a/examples/CCFTest/ccf_test.cpp +++ b/examples/CCFTest/ccf_test.cpp @@ -35,8 +35,9 @@ DeviceType parseDeviceType(const std::string& s) { void printChannelSummary(SdkSession& session, const char* label, int n = 5) { std::cout << "=== " << label << " (channels 1-" << n << ") ===\n"; for (uint32_t ch = 1; ch <= (uint32_t)n; ++ch) { - const auto* info = session.getChanInfo(ch); - if (info) { + auto info_res = session.getChanInfo(ch); + if (info_res.isOk()) { + const auto* info = &info_res.value(); std::cout << " ch " << std::setw(3) << ch << ": spkopts=0x" << std::hex << std::setw(5) << std::setfill('0') << info->spkopts << " ainpopts=0x" << std::setw(4) << info->ainpopts @@ -125,8 +126,9 @@ int main(int argc, char* argv[]) { // Re-read saved CCF A to compare against current device state std::cout << "\n Field differences (ch 1-5) between saved A and current (B) state:\n"; for (uint32_t ch = 1; ch <= 5; ++ch) { - const auto* info = session.getChanInfo(ch); - if (!info) continue; + auto info_res = session.getChanInfo(ch); + if (info_res.isError()) continue; + const auto* info = &info_res.value(); std::cout << " ch " << ch << ":" << " spkopts=0x" << std::hex << info->spkopts << " ainpopts=0x" << info->ainpopts << std::dec diff --git a/examples/CentralClient/central_client.cpp b/examples/CentralClient/central_client.cpp index 5475f8d9..3ee246f9 100644 --- a/examples/CentralClient/central_client.cpp +++ b/examples/CentralClient/central_client.cpp @@ -11,13 +11,12 @@ /// central_client 1 # Instance 1 (for multi-instance setups) /////////////////////////////////////////////////////////////////////////////////////////////////// +#include #include -#include #include #include #include #include -#include #include #include #include @@ -28,11 +27,6 @@ std::atomic g_running{true}; void signalHandler(int) { g_running = false; } -static std::string makeName(const char* base, int instance) { - if (instance == 0) return base; - return std::string(base) + std::to_string(instance); -} - int main(int argc, char* argv[]) { int instance = 0; if (argc >= 2) instance = std::atoi(argv[1]); @@ -44,88 +38,30 @@ int main(int argc, char* argv[]) { std::cout << " CereLink Central Client Diagnostic\n"; std::cout << "==============================================\n\n"; - // Print struct sizes for comparison with Central - std::cout << "=== Struct Size Verification ===\n"; - std::cout << " sizeof(CentralLegacyCFGBUFF): " << sizeof(CentralLegacyCFGBUFF) << "\n"; - std::cout << " sizeof(CentralReceiveBuffer): " << sizeof(CentralReceiveBuffer) << "\n"; - std::cout << " sizeof(CentralTransmitBuffer): " << sizeof(CentralTransmitBuffer) << "\n"; - std::cout << " sizeof(CentralTransmitBufferLocal): " << sizeof(CentralTransmitBufferLocal) << "\n"; - std::cout << " sizeof(CentralPCStatus): " << sizeof(CentralPCStatus) << "\n"; - std::cout << " sizeof(CentralSpikeBuffer): " << sizeof(CentralSpikeBuffer) << "\n"; - std::cout << " sizeof(CentralSpikeCache): " << sizeof(CentralSpikeCache) << "\n"; - std::cout << " sizeof(CentralAppWorkspace): " << sizeof(CentralAppWorkspace) << "\n"; - std::cout << "\n"; - - // Print key constants - std::cout << "=== Key Constants ===\n"; - std::cout << " CENTRAL_cbMAXPROCS: " << CENTRAL_cbMAXPROCS << "\n"; - std::cout << " CENTRAL_cbNUM_FE_CHANS: " << CENTRAL_cbNUM_FE_CHANS << "\n"; - std::cout << " CENTRAL_cbMAXCHANS: " << CENTRAL_cbMAXCHANS << "\n"; - std::cout << " CENTRAL_cbMAXBANKS: " << CENTRAL_cbMAXBANKS << "\n"; - std::cout << " CENTRAL_cbMAXNTRODES: " << CENTRAL_cbMAXNTRODES << "\n"; - std::cout << " CENTRAL_AOUT_NUM_GAIN_CHANS: " << CENTRAL_AOUT_NUM_GAIN_CHANS << "\n"; - std::cout << " CENTRAL_cbPKT_SPKCACHELINECNT: " << CENTRAL_cbPKT_SPKCACHELINECNT << "\n"; - std::cout << " CENTRAL_cbMAXAPPWORKSPACES: " << CENTRAL_cbMAXAPPWORKSPACES << "\n"; - std::cout << " sizeof(PROCTIME): " << sizeof(PROCTIME) << "\n"; - std::cout << "\n"; - - // Construct names for this instance - std::string cfg_name = makeName("cbCFGbuffer", instance); - std::string rec_name = makeName("cbRECbuffer", instance); - std::string xmt_name = makeName("XmtGlobal", instance); - std::string xmtl_name = makeName("XmtLocal", instance); - std::string status_name = makeName("cbSTATUSbuffer", instance); - std::string spk_name = makeName("cbSPKbuffer", instance); - std::string signal_name = makeName("cbSIGNALevent", instance); + // For the CENTRAL layout, name_qualifier is the Central instance suffix + // ("" for the primary instance, "1" for instance 1, etc.). + std::string instance_suffix = (instance == 0) ? "" : std::to_string(instance); std::cout << "=== Attempting Central CLIENT mode (instance " << instance << ") ===\n"; - std::cout << " Config: " << cfg_name << "\n"; - std::cout << " Receive: " << rec_name << "\n"; - std::cout << " XmtGlob: " << xmt_name << "\n"; - std::cout << " XmtLoc: " << xmtl_name << "\n"; - std::cout << " Status: " << status_name << "\n"; - std::cout << " Spike: " << spk_name << "\n"; - std::cout << " Signal: " << signal_name << "\n\n"; - - auto result = ShmemSession::create( - cfg_name, rec_name, xmt_name, xmtl_name, - status_name, spk_name, signal_name, - Mode::CLIENT, ShmemLayout::CENTRAL_COMPAT); + std::cout << std::endl; + + // Each ShmemSession targets a single instrument for its lifetime, so attach + // a fresh session per instrument id we want to inspect. + auto makeSession = [&](cbproto::InstrumentId id) { + return ShmemSession::create( + Mode::CLIENT, ShmemLayout::CENTRAL, instance_suffix, id); + }; + + auto result = makeSession(cbproto::InstrumentId::fromOneBased(cbNSP1)); if (result.isError()) { std::cerr << "FAILED to attach to Central's shared memory: " << result.error() << "\n"; - std::cerr << "\nIs Central running?\n"; + std::cerr << "\nIs Central running?" << std::endl; return 1; } auto session = std::move(result.value()); - std::cout << "SUCCESS: Attached to Central's shared memory!\n\n"; - - // Read config buffer - auto* cfg = session.getLegacyConfigBuffer(); - if (!cfg) { - std::cerr << "ERROR: getLegacyConfigBuffer() returned null\n"; - return 1; - } - - std::cout << "=== Config Buffer Contents ===\n"; - std::cout << " version: " << cfg->version << "\n"; - std::cout << " sysflags: 0x" << std::hex << cfg->sysflags << std::dec << "\n"; - - // Read procinfo for each instrument - std::cout << "\n=== Processor Info ===\n"; - for (uint32_t i = 0; i < CENTRAL_cbMAXPROCS; ++i) { - auto& proc = cfg->procinfo[i]; - // procinfo version field = (major << 16) | minor - uint32_t ver = proc.cbpkt_header.type; // Version is stored in a known field - std::cout << " Proc[" << i << "]:" - << " time=" << proc.cbpkt_header.time - << " chid=" << proc.cbpkt_header.chid - << " type=0x" << std::hex << proc.cbpkt_header.type << std::dec - << " dlen=" << proc.cbpkt_header.dlen - << " inst=" << (int)proc.cbpkt_header.instrument - << "\n"; - } + std::cout << "SUCCESS: Attached to Central's shared memory!" << std::endl; // Detect protocol version auto proto = session.getCompatProtocolVersion(); @@ -139,25 +75,53 @@ int main(int argc, char* argv[]) { default: std::cout << "UNKNOWN\n"; break; } + // Read procinfo for each instrument (one session per instrument) + std::cout << "\n=== Processor Info ===\n"; + uint32_t max_procs = session.getMaxProcs(); + for (uint32_t i = 0; i < max_procs; ++i) { + auto inst_result = makeSession(cbproto::InstrumentId::fromIndex(i)); + if (inst_result.isError()) { + std::cerr << "ERROR: failed to attach for instrument " << i + << ": " << inst_result.error() << std::endl; + return 1; + } + auto inst_session = std::move(inst_result.value()); + + // Read config buffer + auto cfg = std::make_unique(); + auto cfg_res = inst_session.getLegacyConfigBuffer(*cfg); + if (cfg_res.isError()) { + std::cerr << "ERROR: getLegacyConfigBuffer() returned error: " << cfg_res.error() << std::endl; + return 1; + } + + auto& proc = cfg->procinfo; + std::cout << " Proc: " + << " time=" << proc.cbpkt_header.time + << " chid=" << proc.cbpkt_header.chid + << " type=0x" << std::hex << proc.cbpkt_header.type << std::dec + << " dlen=" << proc.cbpkt_header.dlen + << " inst=" << (int)proc.cbpkt_header.instrument + << "\n"; + } + // Read status buffer std::cout << "\n=== PC Status ===\n"; auto num_total = session.getNumTotalChans(); if (num_total.isOk()) { std::cout << " Total channels: " << num_total.value() << "\n"; } - for (uint32_t i = 0; i < CENTRAL_cbMAXPROCS; ++i) { - auto nsp = session.getNspStatus(cbproto::InstrumentId::fromIndex(i)); - if (nsp.isOk()) { - const char* status_str = "?"; - switch (nsp.value()) { - case NSPStatus::NSP_INIT: status_str = "INIT"; break; - case NSPStatus::NSP_NOIPADDR: status_str = "NOIPADDR"; break; - case NSPStatus::NSP_NOREPLY: status_str = "NOREPLY"; break; - case NSPStatus::NSP_FOUND: status_str = "FOUND"; break; - case NSPStatus::NSP_INVALID: status_str = "INVALID"; break; - } - std::cout << " NSP[" << i << "] status: " << status_str << "\n"; + auto nsp = session.getNspStatus(); + if (nsp.isOk()) { + const char* status_str = "?"; + switch (nsp.value()) { + case NativeNSPStatus::NSP_INIT: status_str = "INIT"; break; + case NativeNSPStatus::NSP_NOIPADDR: status_str = "NOIPADDR"; break; + case NativeNSPStatus::NSP_NOREPLY: status_str = "NOREPLY"; break; + case NativeNSPStatus::NSP_FOUND: status_str = "FOUND"; break; + case NativeNSPStatus::NSP_INVALID: status_str = "INVALID"; break; } + std::cout << " NSP status: " << status_str << "\n"; } auto gemini = session.isGeminiSystem(); if (gemini.isOk()) { @@ -166,7 +130,7 @@ int main(int argc, char* argv[]) { // Read some channel info std::cout << "\n=== Sample Channel Info ===\n"; - for (uint32_t ch = 0; ch < 5 && ch < CENTRAL_cbMAXCHANS; ++ch) { + for (uint32_t ch = 0; ch < 5 && ch < cbMAXCHANS; ++ch) { auto ci = session.getChanInfo(ch); if (ci.isOk()) { auto& chan = ci.value(); @@ -184,9 +148,6 @@ int main(int argc, char* argv[]) { std::cout << "\n=== Monitoring Receive Buffer ===\n"; std::cout << "Waiting for packets (Ctrl+C to stop)...\n\n"; - // Set instrument filter for Hub1 (index 0 in GEMSTART=2 mapping) - session.setInstrumentFilter(0); - uint64_t total_packets = 0; auto start = std::chrono::steady_clock::now(); diff --git a/examples/ClearConfigRepro/clear_config_repro.cpp b/examples/ClearConfigRepro/clear_config_repro.cpp index 71adac51..954b413b 100644 --- a/examples/ClearConfigRepro/clear_config_repro.cpp +++ b/examples/ClearConfigRepro/clear_config_repro.cpp @@ -49,8 +49,9 @@ int main(int argc, char* argv[]) { auto session = std::move(result.value()); std::cout << " connected. protocol code=" << session.getProtocolVersion() << " ident=\"" << session.getProcIdent() << "\""; - if (const auto* si = session.getSysInfo()) { - std::cout << " sysfreq=" << si->sysfreq; + auto sysinfo = session.getSysInfo(); + if (sysinfo.isOk()) { + std::cout << " sysfreq=" << sysinfo.value().sysfreq; } std::cout << "\n"; @@ -59,7 +60,7 @@ int main(int argc, char* argv[]) { // Gather all analog-in channels (mirrors Orion's allChannels). std::vector allChannels; for (uint32_t ch = 1; ch <= cbNUM_ANALOG_CHANS; ++ch) { - if (session.getChanInfo(ch) != nullptr) allChannels.push_back(ch); + if (session.getChanInfo(ch).isOk()) allChannels.push_back(ch); } std::cout << " discovered " << allChannels.size() << " analog channels\n"; if (allChannels.empty()) return 2; @@ -86,9 +87,9 @@ int main(int argc, char* argv[]) { }; for (uint32_t ch : allChannels) { // clear LNC - const auto* base = session.getChanInfo(ch); - if (!base) return false; - cbPKT_CHANINFO ci = *base; + auto base = session.getChanInfo(ch); + if (base.isError()) return false; + auto& ci = base.value(); ci.chan = ch; ci.cbpkt_header.type = cbPKTTYPE_CHANSETAINP; ci.ainpopts &= ~(cbAINP_LNC_MASK | cbAINP_RAWSTREAM); @@ -99,9 +100,9 @@ int main(int argc, char* argv[]) { paced(); } for (uint32_t ch : allChannels) { // clear spike processing - const auto* base = session.getChanInfo(ch); - if (!base) return false; - cbPKT_CHANINFO ci = *base; + auto base = session.getChanInfo(ch); + if (base.isError()) return false; + auto& ci = base.value(); ci.chan = ch; ci.cbpkt_header.type = cbPKTTYPE_CHANSETSPK; ci.spkopts &= ~SPIKE_PROCESSING_OPTION_MASK; diff --git a/pycbsdk/src/pycbsdk/_cdef.py b/pycbsdk/src/pycbsdk/_cdef.py index fdc44cbd..6994515b 100644 --- a/pycbsdk/src/pycbsdk/_cdef.py +++ b/pycbsdk/src/pycbsdk/_cdef.py @@ -200,10 +200,12 @@ uint32_t cbsdk_get_max_chans(void); uint32_t cbsdk_get_num_fe_chans(void); uint32_t cbsdk_get_num_analog_chans(void); -const char* cbsdk_session_get_channel_label(cbsdk_session_t session, uint32_t chan_id); +uint32_t cbsdk_session_get_channel_label_length(void); +int32_t cbsdk_session_get_channel_label(cbsdk_session_t session, uint32_t chan_id, char* buf, uint32_t buf_size); uint32_t cbsdk_session_get_channel_smpgroup(cbsdk_session_t session, uint32_t chan_id); uint32_t cbsdk_session_get_channel_chancaps(cbsdk_session_t session, uint32_t chan_id); -const char* cbsdk_session_get_group_label(cbsdk_session_t session, uint32_t group_id); +uint32_t cbsdk_session_get_group_label_length(void); +int32_t cbsdk_session_get_group_label(cbsdk_session_t session, uint32_t group_id, char* buf, uint32_t buf_size); cbsdk_result_t cbsdk_session_get_group_list(cbsdk_session_t session, uint32_t group_id, uint16_t* list, uint32_t* count); @@ -297,7 +299,8 @@ // Bulk configuration access uint32_t cbsdk_session_get_sysfreq(cbsdk_session_t session); uint32_t cbsdk_get_num_filters(void); -const char* cbsdk_session_get_filter_label(cbsdk_session_t session, uint32_t filter_id); +uint32_t cbsdk_session_get_filter_label_length(void); +int32_t cbsdk_session_get_filter_label(cbsdk_session_t session, uint32_t filter_id, char* buf, uint32_t buf_size); uint32_t cbsdk_session_get_filter_hpfreq(cbsdk_session_t session, uint32_t filter_id); uint32_t cbsdk_session_get_filter_hporder(cbsdk_session_t session, uint32_t filter_id); uint32_t cbsdk_session_get_filter_lpfreq(cbsdk_session_t session, uint32_t filter_id); diff --git a/pycbsdk/src/pycbsdk/session.py b/pycbsdk/src/pycbsdk/session.py index ece0d22b..208e67b8 100644 --- a/pycbsdk/src/pycbsdk/session.py +++ b/pycbsdk/src/pycbsdk/session.py @@ -718,10 +718,14 @@ def num_analog_chans() -> int: def get_channel_label(self, chan_id: int) -> Optional[str]: """Get a channel's label (1-based channel ID).""" _lib = _get_lib() - ptr = _lib.cbsdk_session_get_channel_label(self._session, chan_id) - if ptr == ffi.NULL: + buf_len = _lib.cbsdk_session_get_channel_label_length() + buf = ffi.new(f"char[{buf_len}]") + written = _lib.cbsdk_session_get_channel_label( + self._session, chan_id, buf, buf_len + ) + if written < 0: return None - return ffi.string(ptr).decode() + return ffi.string(buf).decode() def get_channel_smpgroup(self, chan_id: int) -> int: """Get a channel's sample group (0 = disabled, 1-6).""" @@ -825,10 +829,14 @@ def get_channel_field(self, chan_id: int, field: ChanInfoField) -> int: def get_group_label(self, group_id: int) -> Optional[str]: """Get a sample group's label (group_id 1-6).""" _lib = _get_lib() - ptr = _lib.cbsdk_session_get_group_label(self._session, group_id) - if ptr == ffi.NULL: + buf_len = _lib.cbsdk_session_get_group_label_length() + buf = ffi.new(f"char[{buf_len}]") + written = _lib.cbsdk_session_get_group_label( + self._session, group_id, buf, buf_len + ) + if written < 0: return None - return ffi.string(ptr).decode() + return ffi.string(buf).decode() def get_group_channels(self, group_id: int) -> list[int]: """Get the list of channel IDs in a sample group.""" @@ -1928,11 +1936,15 @@ def get_filter_info(self, filter_id: int) -> Optional[dict]: ``lporder``. Frequencies are in milliHertz. Returns None if invalid. """ _lib = _get_lib() - ptr = _lib.cbsdk_session_get_filter_label(self._session, filter_id) - if ptr == ffi.NULL: + buf_len = _lib.cbsdk_session_get_filter_label_length() + buf = ffi.new(f"char[{buf_len}]") + written = _lib.cbsdk_session_get_filter_label( + self._session, filter_id, buf, buf_len + ) + if written < 0: return None return { - "label": ffi.string(ptr).decode(), + "label": ffi.string(buf).decode(), "hpfreq": _lib.cbsdk_session_get_filter_hpfreq(self._session, filter_id), "hporder": _lib.cbsdk_session_get_filter_hporder(self._session, filter_id), "lpfreq": _lib.cbsdk_session_get_filter_lpfreq(self._session, filter_id), diff --git a/src/cbproto/include/cbproto/config.h b/src/cbproto/include/cbproto/config.h index 284a6866..cc67e320 100644 --- a/src/cbproto/include/cbproto/config.h +++ b/src/cbproto/include/cbproto/config.h @@ -18,12 +18,6 @@ #include -// Constants not yet in types.h but needed for config structure -// TODO: Move these to types.h when updating from upstream -#ifndef cbMAXBANKS -#define cbMAXBANKS 15 ///< cbNUM_FE_BANKS(8) + ANAIN(1) + ANAOUT(1) + AUDOUT(1) + DIGIN(1) + SERIAL(1) + DIGOUT(1) -#endif - namespace cbproto { /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -41,7 +35,7 @@ struct SpikeSorting { // Spike sorting parameters cbPKT_SS_DETECT detect; ///< Detection parameters cbPKT_SS_ARTIF_REJECT artifact_reject; ///< Artifact rejection parameters - cbPKT_SS_NOISE_BOUNDARY noise_boundary[cbMAXCHANS]; ///< Noise boundaries per channel + cbPKT_SS_NOISE_BOUNDARY noise_boundary[cbMAXCHANS]; ///< Noise boundaries per channel // VER: 4.0+, changed to cbMAXCHANS from cbNUM_ANALOG_CHANS cbPKT_SS_STATISTICS statistics; ///< Spike statistics cbPKT_SS_STATUS status; ///< Spike sorting status }; diff --git a/src/cbproto/src/packet_translator.cpp b/src/cbproto/src/packet_translator.cpp index b71ca7af..ac49a6b9 100644 --- a/src/cbproto/src/packet_translator.cpp +++ b/src/cbproto/src/packet_translator.cpp @@ -121,10 +121,11 @@ size_t cbproto::PacketTranslator::translate_CHANINFO_pre410_to_current(const uin constexpr size_t payload_to_union = offsetof(cbPKT_CHANINFO, eopchar) + sizeof(dest->eopchar) - cbPKT_HEADER_SIZE; std::memcpy(&dest->chan, src_payload, payload_to_union); size_t src_offset = payload_to_union; - // Narrow 3.11's uint32_t monsource to 4.1's uint16_t moninst, set uint16_t monchan to 0. - dest->moninst = static_cast(*reinterpret_cast(&src_payload[src_offset])); + // Split uint32_t monsource (3.11) into uint16_t moninst/monchan (4.1) to match CCFUtilsBinary. + uint32_t monsource = *reinterpret_cast(&src_payload[src_offset]); + dest->moninst = static_cast(monsource & 0xFFFF); + dest->monchan = static_cast(monsource >> 16); src_offset += 4; - dest->monchan = 0; // New field; set to 0. // outvalue and trigtype are unchanged dest->outvalue = *reinterpret_cast(&src_payload[src_offset]); src_offset += 4; @@ -147,8 +148,9 @@ size_t cbproto::PacketTranslator::translate_CHANINFO_current_to_pre410(const cbP constexpr size_t payload_to_union = offsetof(cbPKT_CHANINFO, eopchar) + sizeof(pkt.eopchar) - cbPKT_HEADER_SIZE; memcpy(dest_payload, &pkt.chan, payload_to_union); size_t dest_offset = payload_to_union; - // Expand 4.1's uint16_t moninst to 3.11's uint32_t monsource; ignore uint16_t monchan. - *reinterpret_cast(&dest_payload[dest_offset]) = static_cast(pkt.moninst); + // Merge uint16_t moninst/monchan (4.1) into uint32_t monsource (3.11) to match CCFUtilsBinary. + *reinterpret_cast(&dest_payload[dest_offset]) = + (static_cast(pkt.monchan) << 16) | static_cast(pkt.moninst); dest_offset += 4; // outvalue is unchanged *reinterpret_cast(&dest_payload[dest_offset]) = pkt.outvalue; diff --git a/src/cbsdk/README.md b/src/cbsdk/README.md index 3c858199..ace88b1a 100644 --- a/src/cbsdk/README.md +++ b/src/cbsdk/README.md @@ -6,7 +6,7 @@ single session interface. ## Responsibilities - **Session lifecycle:** Create, start, stop, destroy -- **Mode auto-detection:** CENTRAL_COMPAT CLIENT → Native CLIENT → Native STANDALONE +- **Mode auto-detection:** CENTRAL CLIENT → NATIVE CLIENT → NATIVE STANDALONE (three-way fallback) - **Device handshake:** Protocol detection, configuration request, run-level control - **Callback system:** Per-type callbacks (event, group, group batch, config, packet) diff --git a/src/cbsdk/include/cbsdk/cbsdk.h b/src/cbsdk/include/cbsdk/cbsdk.h index 96673608..499b818a 100644 --- a/src/cbsdk/include/cbsdk/cbsdk.h +++ b/src/cbsdk/include/cbsdk/cbsdk.h @@ -442,12 +442,18 @@ CBSDK_API uint32_t cbsdk_get_num_fe_chans(void); /// @return cbNUM_ANALOG_CHANS (compile-time constant) CBSDK_API uint32_t cbsdk_get_num_analog_chans(void); +/// Get the buffer size required to store a channel label. +/// @return cbLEN_STR_LABEL + 1 (label field width plus the null terminator) +CBSDK_API uint32_t cbsdk_session_get_channel_label_length(void); + /// Get a channel's label /// @param session Session handle (must not be NULL) /// @param chan_id 1-based channel ID (1 to cbMAXCHANS) -/// @return Pointer to null-terminated label string, or NULL if invalid. -/// Pointer is valid for the lifetime of the session. -CBSDK_API const char* cbsdk_session_get_channel_label(cbsdk_session_t session, uint32_t chan_id); +/// @param buf Output buffer for the label string (null-terminated on success) +/// @param buf_size Size of the output buffer in bytes +/// @return Number of bytes written (excluding null terminator), or -1 if unavailable +CBSDK_API int32_t cbsdk_session_get_channel_label( + cbsdk_session_t session, uint32_t chan_id, char* buf, uint32_t buf_size); /// Get a channel's sample group assignment /// @param session Session handle (must not be NULL) @@ -539,11 +545,18 @@ CBSDK_API int64_t cbsdk_session_get_channel_field( uint32_t chan_id, cbsdk_chaninfo_field_t field); +/// Get the buffer size required to store a group label. +/// @return cbLEN_STR_LABEL + 1 (label field width plus the null terminator) +CBSDK_API uint32_t cbsdk_session_get_group_label_length(void); + /// Get a sample group's label /// @param session Session handle (must not be NULL) /// @param group_id Group ID (1-6) -/// @return Pointer to null-terminated label string, or NULL if invalid -CBSDK_API const char* cbsdk_session_get_group_label(cbsdk_session_t session, uint32_t group_id); +/// @param buf Output buffer for the label string (null-terminated on success) +/// @param buf_size Size of the output buffer in bytes +/// @return Number of bytes written (excluding null terminator), or -1 if unavailable +CBSDK_API int32_t cbsdk_session_get_group_label( + cbsdk_session_t session, uint32_t group_id, char* buf, uint32_t buf_size); /// Get the list of channels in a sample group /// @param session Session handle (must not be NULL) @@ -819,11 +832,18 @@ CBSDK_API uint32_t cbsdk_session_get_sysfreq(cbsdk_session_t session); /// @return cbMAXFILTS (compile-time constant) CBSDK_API uint32_t cbsdk_get_num_filters(void); +/// Get the buffer size required to store a filter label. +/// @return cbLEN_STR_FILT_LABEL + 1 (label field width plus the null terminator) +CBSDK_API uint32_t cbsdk_session_get_filter_label_length(void); + /// Get a filter's label /// @param session Session handle (must not be NULL) /// @param filter_id Filter ID (0 to cbMAXFILTS-1) -/// @return Pointer to label string, or NULL if invalid -CBSDK_API const char* cbsdk_session_get_filter_label(cbsdk_session_t session, uint32_t filter_id); +/// @param buf Output buffer for the label string (null-terminated on success) +/// @param buf_size Size of the output buffer in bytes +/// @return Number of bytes written (excluding null terminator), or -1 if unavailable +CBSDK_API int32_t cbsdk_session_get_filter_label( + cbsdk_session_t session, uint32_t filter_id, char* buf, uint32_t buf_size); /// Get a filter's high-pass corner frequency /// @param session Session handle (must not be NULL) diff --git a/src/cbsdk/include/cbsdk/sdk_session.h b/src/cbsdk/include/cbsdk/sdk_session.h index 1066f92c..f8285ffb 100644 --- a/src/cbsdk/include/cbsdk/sdk_session.h +++ b/src/cbsdk/include/cbsdk/sdk_session.h @@ -420,18 +420,18 @@ class SdkSession { const SdkConfig& getConfig() const; /// Get system information - /// @return Pointer to system info packet, or nullptr if not available - const cbPKT_SYSINFO* getSysInfo() const; + /// @return Result containing a copy of the system info packet, or an error if not available + Result getSysInfo() const; /// Get channel information /// @param chan_id 1-based channel ID (1 to cbMAXCHANS) - /// @return Pointer to channel info, or nullptr if invalid/unavailable - const cbPKT_CHANINFO* getChanInfo(uint32_t chan_id) const; + /// @return Result containing a copy of the channel info, or an error if invalid/unavailable + Result getChanInfo(uint32_t chan_id) const; /// Get sample group information /// @param group_id Group ID (1-6) - /// @return Pointer to group info, or nullptr if invalid/unavailable - const cbPKT_GROUPINFO* getGroupInfo(uint32_t group_id) const; + /// @return Result containing a copy of the group info, or an error if invalid/unavailable + Result getGroupInfo(uint32_t group_id) const; /// Compute the list of channel IDs belonging to a sample group by /// scanning individual chaninfo records. More reliable than getGroupInfo() @@ -445,8 +445,8 @@ class SdkSession { /// Get filter information /// @param filter_id Filter ID (0 to cbMAXFILTS-1) - /// @return Pointer to filter info, or nullptr if invalid/unavailable - const cbPKT_FILTINFO* getFilterInfo(uint32_t filter_id) const; + /// @return Result containing a copy of the filter info, or an error if invalid/unavailable + Result getFilterInfo(uint32_t filter_id) const; /// Get current device run level /// @return Current run level (cbRUNLEVEL_*), or 0 if unknown diff --git a/src/cbsdk/src/cbsdk.cpp b/src/cbsdk/src/cbsdk.cpp index 7e7a9080..b8bad9a3 100644 --- a/src/cbsdk/src/cbsdk.cpp +++ b/src/cbsdk/src/cbsdk.cpp @@ -16,6 +16,7 @@ #include "cbsdk/sdk_session.h" #include #include +#include #include #include #include @@ -694,15 +695,21 @@ uint32_t cbsdk_get_num_analog_chans(void) { return cbNUM_ANALOG_CHANS; } -const char* cbsdk_session_get_channel_label(cbsdk_session_t session, uint32_t chan_id) { - if (!session || !session->cpp_session) { - return nullptr; - } +uint32_t cbsdk_session_get_channel_label_length(void) { + return cbLEN_STR_LABEL + 1; // + null terminator +} + +int32_t cbsdk_session_get_channel_label( + cbsdk_session_t session, uint32_t chan_id, char* buf, uint32_t buf_size) { + if (!session || !session->cpp_session || !buf || buf_size == 0) return -1; try { - const cbPKT_CHANINFO* info = session->cpp_session->getChanInfo(chan_id); - return info ? info->label : nullptr; + auto info = session->cpp_session->getChanInfo(chan_id); + if (info.isError()) return -1; + auto written = std::snprintf(buf, buf_size, "%.*s", static_cast(std::size(info.value().label)), info.value().label); + if (written < 0) return -1; + return written < buf_size ? written : buf_size - 1; // clamp if truncated } catch (...) { - return nullptr; + return -1; } } @@ -711,8 +718,8 @@ uint32_t cbsdk_session_get_channel_smpgroup(cbsdk_session_t session, uint32_t ch return 0; } try { - const cbPKT_CHANINFO* info = session->cpp_session->getChanInfo(chan_id); - return info ? info->smpgroup : 0; + auto info = session->cpp_session->getChanInfo(chan_id); + return info.isOk() ? info.value().smpgroup : 0; } catch (...) { return 0; } @@ -723,8 +730,8 @@ uint32_t cbsdk_session_get_channel_chancaps(cbsdk_session_t session, uint32_t ch return 0; } try { - const cbPKT_CHANINFO* info = session->cpp_session->getChanInfo(chan_id); - return info ? info->chancaps : 0; + auto info = session->cpp_session->getChanInfo(chan_id); + return info.isOk() ? info.value().chancaps : 0; } catch (...) { return 0; } @@ -735,10 +742,11 @@ cbproto_channel_type_t cbsdk_session_get_channel_type(cbsdk_session_t session, u return static_cast(-1); } try { - const cbPKT_CHANINFO* info = session->cpp_session->getChanInfo(chan_id); - if (!info) return static_cast(-1); + auto info = session->cpp_session->getChanInfo(chan_id); + if (info.isError()) return static_cast(-1); + cbPKT_CHANINFO& ci = info.value(); - const uint32_t caps = info->chancaps; + const uint32_t caps = ci.chancaps; if ((cbCHAN_EXISTS | cbCHAN_CONNECTED) != (caps & (cbCHAN_EXISTS | cbCHAN_CONNECTED))) return static_cast(-1); @@ -747,12 +755,12 @@ cbproto_channel_type_t cbsdk_session_get_channel_type(cbsdk_session_t session, u if (cbCHAN_AINP == (caps & (cbCHAN_AINP | cbCHAN_ISOLATED))) return CBPROTO_CHANNEL_TYPE_ANALOG_IN; if (cbCHAN_AOUT == (caps & cbCHAN_AOUT)) { - if (cbAOUT_AUDIO == (info->aoutcaps & cbAOUT_AUDIO)) + if (cbAOUT_AUDIO == (ci.aoutcaps & cbAOUT_AUDIO)) return CBPROTO_CHANNEL_TYPE_AUDIO; return CBPROTO_CHANNEL_TYPE_ANALOG_OUT; } if (cbCHAN_DINP == (caps & cbCHAN_DINP)) { - if (info->dinpcaps & cbDINP_SERIALMASK) + if (ci.dinpcaps & cbDINP_SERIALMASK) return CBPROTO_CHANNEL_TYPE_SERIAL; return CBPROTO_CHANNEL_TYPE_DIGITAL_IN; } @@ -768,72 +776,72 @@ cbproto_channel_type_t cbsdk_session_get_channel_type(cbsdk_session_t session, u uint32_t cbsdk_session_get_channel_smpfilter(cbsdk_session_t session, uint32_t chan_id) { if (!session || !session->cpp_session) return 0; try { - const cbPKT_CHANINFO* info = session->cpp_session->getChanInfo(chan_id); - return info ? info->smpfilter : 0; + auto info = session->cpp_session->getChanInfo(chan_id); + return info.isOk() ? info.value().smpfilter : 0; } catch (...) { return 0; } } uint32_t cbsdk_session_get_channel_spkfilter(cbsdk_session_t session, uint32_t chan_id) { if (!session || !session->cpp_session) return 0; try { - const cbPKT_CHANINFO* info = session->cpp_session->getChanInfo(chan_id); - return info ? info->spkfilter : 0; + auto info = session->cpp_session->getChanInfo(chan_id); + return info.isOk() ? info.value().spkfilter : 0; } catch (...) { return 0; } } uint32_t cbsdk_session_get_channel_spkopts(cbsdk_session_t session, uint32_t chan_id) { if (!session || !session->cpp_session) return 0; try { - const cbPKT_CHANINFO* info = session->cpp_session->getChanInfo(chan_id); - return info ? info->spkopts : 0; + auto info = session->cpp_session->getChanInfo(chan_id); + return info.isOk() ? info.value().spkopts : 0; } catch (...) { return 0; } } int32_t cbsdk_session_get_channel_spkthrlevel(cbsdk_session_t session, uint32_t chan_id) { if (!session || !session->cpp_session) return 0; try { - const cbPKT_CHANINFO* info = session->cpp_session->getChanInfo(chan_id); - return info ? info->spkthrlevel : 0; + auto info = session->cpp_session->getChanInfo(chan_id); + return info.isOk() ? info.value().spkthrlevel : 0; } catch (...) { return 0; } } uint32_t cbsdk_session_get_channel_ainpopts(cbsdk_session_t session, uint32_t chan_id) { if (!session || !session->cpp_session) return 0; try { - const cbPKT_CHANINFO* info = session->cpp_session->getChanInfo(chan_id); - return info ? info->ainpopts : 0; + auto info = session->cpp_session->getChanInfo(chan_id); + return info.isOk() ? info.value().ainpopts : 0; } catch (...) { return 0; } } uint32_t cbsdk_session_get_channel_lncrate(cbsdk_session_t session, uint32_t chan_id) { if (!session || !session->cpp_session) return 0; try { - const cbPKT_CHANINFO* info = session->cpp_session->getChanInfo(chan_id); - return info ? info->lncrate : 0; + auto info = session->cpp_session->getChanInfo(chan_id); + return info.isOk() ? info.value().lncrate : 0; } catch (...) { return 0; } } uint32_t cbsdk_session_get_channel_refelecchan(cbsdk_session_t session, uint32_t chan_id) { if (!session || !session->cpp_session) return 0; try { - const cbPKT_CHANINFO* info = session->cpp_session->getChanInfo(chan_id); - return info ? info->refelecchan : 0; + auto info = session->cpp_session->getChanInfo(chan_id); + return info.isOk() ? info.value().refelecchan : 0; } catch (...) { return 0; } } int16_t cbsdk_session_get_channel_amplrejpos(cbsdk_session_t session, uint32_t chan_id) { if (!session || !session->cpp_session) return 0; try { - const cbPKT_CHANINFO* info = session->cpp_session->getChanInfo(chan_id); - return info ? info->amplrejpos : 0; + auto info = session->cpp_session->getChanInfo(chan_id); + return info.isOk() ? info.value().amplrejpos : 0; } catch (...) { return 0; } } int16_t cbsdk_session_get_channel_amplrejneg(cbsdk_session_t session, uint32_t chan_id) { if (!session || !session->cpp_session) return 0; try { - const cbPKT_CHANINFO* info = session->cpp_session->getChanInfo(chan_id); - return info ? info->amplrejneg : 0; + auto info = session->cpp_session->getChanInfo(chan_id); + return info.isOk() ? info.value().amplrejneg : 0; } catch (...) { return 0; } } @@ -843,14 +851,15 @@ cbsdk_result_t cbsdk_session_get_channel_scaling( return CBSDK_RESULT_INVALID_PARAMETER; } try { - const cbPKT_CHANINFO* info = session->cpp_session->getChanInfo(chan_id); - if (!info) return CBSDK_RESULT_INVALID_PARAMETER; - scaling->digmin = info->scalin.digmin; - scaling->digmax = info->scalin.digmax; - scaling->anamin = info->scalin.anamin; - scaling->anamax = info->scalin.anamax; - scaling->anagain = info->scalin.anagain; - std::memcpy(scaling->anaunit, info->scalin.anaunit, sizeof(scaling->anaunit)); + auto info = session->cpp_session->getChanInfo(chan_id); + if (info.isError()) return CBSDK_RESULT_INVALID_PARAMETER; + cbPKT_CHANINFO& ci = info.value(); + scaling->digmin = ci.scalin.digmin; + scaling->digmax = ci.scalin.digmax; + scaling->anamin = ci.scalin.anamin; + scaling->anamax = ci.scalin.anamax; + scaling->anagain = ci.scalin.anagain; + std::memcpy(scaling->anaunit, ci.scalin.anaunit, sizeof(scaling->anaunit)); return CBSDK_RESULT_SUCCESS; } catch (...) { std::memset(scaling, 0, sizeof(cbsdk_channel_scaling_t)); @@ -870,15 +879,21 @@ int64_t cbsdk_session_get_channel_field( } catch (...) { return 0; } } -const char* cbsdk_session_get_group_label(cbsdk_session_t session, uint32_t group_id) { - if (!session || !session->cpp_session) { - return nullptr; - } +uint32_t cbsdk_session_get_group_label_length(void) { + return cbLEN_STR_LABEL + 1; // + null terminator +} + +int32_t cbsdk_session_get_group_label( + cbsdk_session_t session, uint32_t group_id, char* buf, uint32_t buf_size) { + if (!session || !session->cpp_session || !buf || buf_size == 0) return -1; try { - const cbPKT_GROUPINFO* info = session->cpp_session->getGroupInfo(group_id); - return info ? info->label : nullptr; + auto info = session->cpp_session->getGroupInfo(group_id); + if (info.isError()) return -1; + auto written = std::snprintf(buf, buf_size, "%.*s", static_cast(std::size(info.value().label)), info.value().label); + if (written < 0) return -1; + return written < buf_size ? written : buf_size - 1; // clamp if truncated } catch (...) { - return nullptr; + return -1; } } @@ -956,9 +971,9 @@ static cbsdk_result_t modify_and_send_chaninfo( auto sync_result = session->cpp_session->sync(5000); if (sync_result.isError()) return CBSDK_RESULT_INTERNAL_ERROR; } - const cbPKT_CHANINFO* info = session->cpp_session->getChanInfo(chan_id); - if (!info) return CBSDK_RESULT_INVALID_PARAMETER; - cbPKT_CHANINFO ci = *info; + auto info = session->cpp_session->getChanInfo(chan_id); + if (info.isError()) return CBSDK_RESULT_INVALID_PARAMETER; + cbPKT_CHANINFO& ci = info.value(); ci.chan = chan_id; ci.cbpkt_header.type = pkt_type; modify(ci); @@ -994,9 +1009,9 @@ cbsdk_result_t cbsdk_session_set_channel_smpgroup( auto sync_result = session->cpp_session->sync(5000); if (sync_result.isError()) return CBSDK_RESULT_INTERNAL_ERROR; } - const cbPKT_CHANINFO* info = session->cpp_session->getChanInfo(chan_id); - if (!info) return CBSDK_RESULT_INVALID_PARAMETER; - cbPKT_CHANINFO ci = *info; + auto info = session->cpp_session->getChanInfo(chan_id); + if (info.isError()) return CBSDK_RESULT_INVALID_PARAMETER; + cbPKT_CHANINFO& ci = info.value(); ci.chan = chan_id; if (rate == 0) { @@ -1225,8 +1240,8 @@ cbsdk_result_t cbsdk_session_get_channels_positions( uint32_t cbsdk_session_get_sysfreq(cbsdk_session_t session) { if (!session || !session->cpp_session) return 0; try { - const cbPKT_SYSINFO* info = session->cpp_session->getSysInfo(); - return info ? info->sysfreq : 0; + auto info = session->cpp_session->getSysInfo(); + return info.isOk() ? info.value().sysfreq : 0; } catch (...) { return 0; } } @@ -1234,43 +1249,51 @@ uint32_t cbsdk_get_num_filters(void) { return cbMAXFILTS; } -const char* cbsdk_session_get_filter_label(cbsdk_session_t session, uint32_t filter_id) { - if (!session || !session->cpp_session) return nullptr; +uint32_t cbsdk_session_get_filter_label_length(void) { + return cbLEN_STR_FILT_LABEL + 1; // + null terminator +} + +int32_t cbsdk_session_get_filter_label( + cbsdk_session_t session, uint32_t filter_id, char* buf, uint32_t buf_size) { + if (!session || !session->cpp_session || !buf || buf_size == 0) return -1; try { - const cbPKT_FILTINFO* info = session->cpp_session->getFilterInfo(filter_id); - return info ? info->label : nullptr; - } catch (...) { return nullptr; } + auto info = session->cpp_session->getFilterInfo(filter_id); + if (info.isError()) return -1; + auto written = std::snprintf(buf, buf_size, "%.*s", static_cast(std::size(info.value().label)), info.value().label); + if (written < 0) return -1; + return written < buf_size ? written : buf_size - 1; // clamp if truncated + } catch (...) { return -1; } } uint32_t cbsdk_session_get_filter_hpfreq(cbsdk_session_t session, uint32_t filter_id) { if (!session || !session->cpp_session) return 0; try { - const cbPKT_FILTINFO* info = session->cpp_session->getFilterInfo(filter_id); - return info ? info->hpfreq : 0; + auto info = session->cpp_session->getFilterInfo(filter_id); + return info.isOk() ? info.value().hpfreq : 0; } catch (...) { return 0; } } uint32_t cbsdk_session_get_filter_hporder(cbsdk_session_t session, uint32_t filter_id) { if (!session || !session->cpp_session) return 0; try { - const cbPKT_FILTINFO* info = session->cpp_session->getFilterInfo(filter_id); - return info ? info->hporder : 0; + auto info = session->cpp_session->getFilterInfo(filter_id); + return info.isOk() ? info.value().hporder : 0; } catch (...) { return 0; } } uint32_t cbsdk_session_get_filter_lpfreq(cbsdk_session_t session, uint32_t filter_id) { if (!session || !session->cpp_session) return 0; try { - const cbPKT_FILTINFO* info = session->cpp_session->getFilterInfo(filter_id); - return info ? info->lpfreq : 0; + auto info = session->cpp_session->getFilterInfo(filter_id); + return info.isOk() ? info.value().lpfreq : 0; } catch (...) { return 0; } } uint32_t cbsdk_session_get_filter_lporder(cbsdk_session_t session, uint32_t filter_id) { if (!session || !session->cpp_session) return 0; try { - const cbPKT_FILTINFO* info = session->cpp_session->getFilterInfo(filter_id); - return info ? info->lporder : 0; + auto info = session->cpp_session->getFilterInfo(filter_id); + return info.isOk() ? info.value().lporder : 0; } catch (...) { return 0; } } diff --git a/src/cbsdk/src/sdk_session.cpp b/src/cbsdk/src/sdk_session.cpp index 19256fc0..2ea432e8 100644 --- a/src/cbsdk/src/sdk_session.cpp +++ b/src/cbsdk/src/sdk_session.cpp @@ -302,14 +302,30 @@ struct SdkSession::Impl { void rebuildChannelTypeCache() { for (uint32_t ch = 0; ch < cbMAXCHANS; ++ch) { - const auto* ci = getChanInfoPtr(ch); - channel_type_cache[ch] = ci ? classifyChannelByCaps(*ci) : ChannelType::ANY; + auto ci = getChanInfo(ch + 1); + channel_type_cache[ch] = ci.isOk() ? classifyChannelByCaps(ci.value()) : ChannelType::ANY; } channel_cache_valid = true; } - /// Get chaninfo pointer for a 0-based channel index (works for both STANDALONE and CLIENT) - const cbPKT_CHANINFO* getChanInfoPtr(uint32_t idx) const; + // Helper: get chaninfo for a 1-based channel ID + Result getChanInfo(const uint32_t chan_id) const { + // Prefer shmem over device_config because CMP position overlays are + // written to shmem (device_config is owned by the receive thread and + // we avoid writing to it from other threads). + if (shmem_session && chan_id >= 1 && chan_id <= cbMAXCHANS) { + return shmem_session->getChanInfo(chan_id - 1); + } + // Fallback to device_config (no shmem available) + if (device_session) { + const auto* chaninfo = device_session->getChanInfo(chan_id); + if (!chaninfo) { + return Result::error("Failed to get channel information"); + } + return Result::ok(*chaninfo); + } + return Result::error("Channel information not available"); + } // Clock sync periodic probing std::chrono::steady_clock::time_point last_clock_probe_time{}; @@ -631,97 +647,6 @@ SdkSession::~SdkSession() { } } -// Helper function to map DeviceType to shared memory instance number. -// Central creates a SINGLE shared memory instance (0) for ALL instruments in a -// Gemini system. The different instruments (Hub1-3, NSP) share the same buffers -// and are distinguished by instrument INDEX within the buffers, not by separate -// shared memory instances. Therefore all device types map to instance 0. -static int getInstanceNumber(DeviceType /*type*/) { - return 0; -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// Central-compatible shared memory naming -// Names match Central's naming convention: base name + optional instance suffix -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// Helper function to get Central-compatible shared memory names -// Returns config buffer name (e.g., "cbCFGbuffer" or "cbCFGbuffer1") -static std::string getCentralConfigBufferName(DeviceType type) { - int instance = getInstanceNumber(type); - if (instance == 0) { - return "cbCFGbuffer"; - } else { - return "cbCFGbuffer" + std::to_string(instance); - } -} - -// Helper function to get Central-compatible transmit buffer name -// Returns transmit buffer name (e.g., "XmtGlobal" or "XmtGlobal1") -static std::string getCentralTransmitBufferName(DeviceType type) { - int instance = getInstanceNumber(type); - if (instance == 0) { - return "XmtGlobal"; - } else { - return "XmtGlobal" + std::to_string(instance); - } -} - -// Helper function to get Central-compatible receive buffer name -// Returns receive buffer name (e.g., "cbRECbuffer" or "cbRECbuffer1") -static std::string getCentralReceiveBufferName(DeviceType type) { - int instance = getInstanceNumber(type); - if (instance == 0) { - return "cbRECbuffer"; - } else { - return "cbRECbuffer" + std::to_string(instance); - } -} - -// Helper function to get Central-compatible local transmit buffer name -// Returns local transmit buffer name (e.g., "XmtLocal" or "XmtLocal1") -static std::string getCentralLocalTransmitBufferName(DeviceType type) { - int instance = getInstanceNumber(type); - if (instance == 0) { - return "XmtLocal"; - } else { - return "XmtLocal" + std::to_string(instance); - } -} - -// Helper function to get Central-compatible status buffer name -// Returns status buffer name (e.g., "cbSTATUSbuffer" or "cbSTATUSbuffer1") -static std::string getCentralStatusBufferName(DeviceType type) { - int instance = getInstanceNumber(type); - if (instance == 0) { - return "cbSTATUSbuffer"; - } else { - return "cbSTATUSbuffer" + std::to_string(instance); - } -} - -// Helper function to get Central-compatible spike cache buffer name -// Returns spike cache buffer name (e.g., "cbSPKbuffer" or "cbSPKbuffer1") -static std::string getCentralSpikeBufferName(DeviceType type) { - int instance = getInstanceNumber(type); - if (instance == 0) { - return "cbSPKbuffer"; - } else { - return "cbSPKbuffer" + std::to_string(instance); - } -} - -// Helper function to get Central-compatible signal event name -// Returns signal event name (e.g., "cbSIGNALevent" or "cbSIGNALevent1") -static std::string getCentralSignalEventName(DeviceType type) { - int instance = getInstanceNumber(type); - if (instance == 0) { - return "cbSIGNALevent"; - } else { - return "cbSIGNALevent" + std::to_string(instance); - } -} - /////////////////////////////////////////////////////////////////////////////////////////////////// // Native-mode shared memory naming // Names use per-device segments: "cbshm_{device}_{segment}" @@ -756,7 +681,10 @@ static int32_t getCentralInstrumentIndex(DeviceType type) { case DeviceType::HUB3: return 2; case DeviceType::NSP: return 3; case DeviceType::LEGACY_NSP: return 0; // Non-Gemini, single instrument - default: return -1; // No filter + // No CENTRAL instrument mapping (e.g. NPLAY). -1 yields an invalid + // InstrumentId, so CENTRAL-mode create() fails and the caller falls + // back to NATIVE. This is NOT a "match all instruments" sentinel. + default: return -1; } } @@ -770,43 +698,22 @@ Result SdkSession::create(const SdkConfig& config) { // 3. Fall back to native STANDALONE: create new native-mode segments bool is_standalone = false; + // Device token used to build NATIVE segment names. The SDK always targets + // Central's primary instance, so the CENTRAL instance suffix is empty. + std::string device_tag = getNativeDeviceName(config.device_type); + // --- Attempt 1: Central-compatible CLIENT mode --- // Try to attach to Central's shared memory (Central is running) - std::string central_cfg = getCentralConfigBufferName(config.device_type); - std::string central_rec = getCentralReceiveBufferName(config.device_type); - std::string central_xmt = getCentralTransmitBufferName(config.device_type); - std::string central_xmt_local = getCentralLocalTransmitBufferName(config.device_type); - std::string central_status = getCentralStatusBufferName(config.device_type); - std::string central_spk = getCentralSpikeBufferName(config.device_type); - std::string central_signal = getCentralSignalEventName(config.device_type); - + auto inst = cbproto::InstrumentId::fromIndex(getCentralInstrumentIndex(config.device_type)); auto shmem_result = cbshm::ShmemSession::create( - central_cfg, central_rec, central_xmt, central_xmt_local, - central_status, central_spk, central_signal, - cbshm::Mode::CLIENT, cbshm::ShmemLayout::CENTRAL_COMPAT); - - if (shmem_result.isOk()) { - // Set instrument filter for CENTRAL_COMPAT mode (Central's receive buffer - // contains packets from ALL instruments; we only want our device's packets) - int32_t inst_idx = getCentralInstrumentIndex(config.device_type); - shmem_result.value().setInstrumentFilter(inst_idx); - } + cbshm::Mode::CLIENT, cbshm::ShmemLayout::CENTRAL, /*instance=*/"", inst); if (shmem_result.isError()) { // --- Attempt 2: Native CLIENT mode --- // Try to attach to an existing CereLink STANDALONE's native segments - std::string native_cfg = getNativeSegmentName(config.device_type, "config"); - std::string native_rec = getNativeSegmentName(config.device_type, "receive"); - std::string native_xmt = getNativeSegmentName(config.device_type, "xmt_global"); - std::string native_xmt_local = getNativeSegmentName(config.device_type, "xmt_local"); - std::string native_status = getNativeSegmentName(config.device_type, "status"); - std::string native_spk = getNativeSegmentName(config.device_type, "spike"); - std::string native_signal = getNativeSegmentName(config.device_type, "signal"); - shmem_result = cbshm::ShmemSession::create( - native_cfg, native_rec, native_xmt, native_xmt_local, - native_status, native_spk, native_signal, - cbshm::Mode::CLIENT, cbshm::ShmemLayout::NATIVE); + cbshm::Mode::CLIENT, cbshm::ShmemLayout::NATIVE, device_tag, + cbproto::InstrumentId::fromIndex(0)); // Liveness check: reject stale segments from a dead STANDALONE process. // The ShmemSession destructor (triggered by reassignment) unmaps the segments; @@ -820,9 +727,8 @@ Result SdkSession::create(const SdkConfig& config) { // --- Attempt 3: Native STANDALONE mode --- // No existing shared memory found, create new native-mode segments shmem_result = cbshm::ShmemSession::create( - native_cfg, native_rec, native_xmt, native_xmt_local, - native_status, native_spk, native_signal, - cbshm::Mode::STANDALONE, cbshm::ShmemLayout::NATIVE); + cbshm::Mode::STANDALONE, cbshm::ShmemLayout::NATIVE, device_tag, + cbproto::InstrumentId::fromIndex(0)); if (shmem_result.isError()) { return Result::error("Failed to create shared memory: " + shmem_result.error()); @@ -982,9 +888,7 @@ Result SdkSession::start() { // can read device configuration (chaninfo, procinfo, sysinfo, groupinfo). if (pkt.cbpkt_header.type == cbPKTTYPE_PROCREP) { const auto* procinfo = reinterpret_cast(&pkt); - impl->shmem_session->setProcInfo( - cbproto::InstrumentId::fromPacketField(pkt.cbpkt_header.instrument), - *procinfo); + impl->shmem_session->setProcInfo(*procinfo); } if ((pkt.cbpkt_header.type & 0xF0) == cbPKTTYPE_SYSREP) { const auto* sysinfo = reinterpret_cast(&pkt); @@ -993,9 +897,7 @@ Result SdkSession::start() { if (pkt.cbpkt_header.type == cbPKTTYPE_GROUPREP) { const auto* groupinfo = reinterpret_cast(&pkt); if (groupinfo->group >= 1 && groupinfo->group <= cbMAXGROUPS) { - impl->shmem_session->setGroupInfo( - cbproto::InstrumentId::fromPacketField(pkt.cbpkt_header.instrument), - groupinfo->group - 1, *groupinfo); + impl->shmem_session->setGroupInfo(groupinfo->group - 1, *groupinfo); } } if ((pkt.cbpkt_header.type & 0xF0) == cbPKTTYPE_CHANREP) { @@ -1362,6 +1264,16 @@ Result SdkSession::start() { probe.cbpkt_header.chid = cbPKTCHAN_CONFIGURATION; probe.cbpkt_header.type = cbPKTTYPE_NPLAYSET; probe.cbpkt_header.dlen = cbPKTDLEN_NPLAY; + // Address the probe to this session's instrument so + // the fabric routes it to the correct hub and the + // echoed NPLAYREP carries the matching instrument + // index — otherwise a non-index-0 session's reply is + // dropped by readReceiveBuffer's instrument filter. + // Use the session's bound instrument (0 for NATIVE) + // so the probe and the filter compare the same value + // by construction. + probe.cbpkt_header.instrument = + impl->shmem_session->getInstrument().toPacketField(); probe.mode = 0xFFFF; probe.stime = static_cast( std::chrono::duration_cast( @@ -1518,57 +1430,29 @@ const SdkConfig& SdkSession::getConfig() const { return m_impl->config; } -const cbPKT_SYSINFO* SdkSession::getSysInfo() const { +Result SdkSession::getSysInfo() const { if (m_impl->device_session) - return &m_impl->device_session->getSysInfo(); - if (m_impl->shmem_session) { - const auto* native = m_impl->shmem_session->getNativeConfigBuffer(); - if (native) - return &native->sysinfo; - const auto* legacy = m_impl->shmem_session->getLegacyConfigBuffer(); - if (legacy) - return &legacy->sysinfo; - } - return nullptr; + return Result::ok(m_impl->device_session->getSysInfo()); + if (m_impl->shmem_session) + return m_impl->shmem_session->getSysInfo(); + return Result::error("System information not available"); } -const cbPKT_CHANINFO* SdkSession::getChanInfo(const uint32_t chan_id) const { - // Prefer shmem over device_config because CMP position overlays are - // written to shmem (device_config is owned by the receive thread and - // we avoid writing to it from other threads). - if (m_impl->shmem_session && chan_id >= 1 && chan_id <= cbMAXCHANS) { - const auto* native = m_impl->shmem_session->getNativeConfigBuffer(); - if (native) - return &native->chaninfo[chan_id - 1]; - const auto* legacy = m_impl->shmem_session->getLegacyConfigBuffer(); - if (legacy) - return &legacy->chaninfo[chan_id - 1]; - } - // Fallback to device_config (no shmem available) - if (m_impl->device_session) - return m_impl->device_session->getChanInfo(chan_id); - return nullptr; +Result SdkSession::getChanInfo(const uint32_t chan_id) const { + return m_impl->getChanInfo(chan_id); } -const cbPKT_GROUPINFO* SdkSession::getGroupInfo(uint32_t group_id) const { +Result SdkSession::getGroupInfo(uint32_t group_id) const { if (group_id == 0 || group_id > cbMAXGROUPS) - return nullptr; + return Result::error("Invalid group ID"); if (m_impl->device_session) { const auto& config = m_impl->device_session->getDeviceConfig(); - return &config.groupinfo[group_id - 1]; + return Result::ok(config.groupinfo[group_id - 1]); } if (m_impl->shmem_session) { - const auto* native = m_impl->shmem_session->getNativeConfigBuffer(); - if (native) - return &native->groupinfo[group_id - 1]; - const auto* legacy = m_impl->shmem_session->getLegacyConfigBuffer(); - if (legacy) { - int32_t inst = getCentralInstrumentIndex(m_impl->config.device_type); - if (inst >= 0) - return &legacy->groupinfo[inst][group_id - 1]; - } + return m_impl->shmem_session->getGroupInfo(group_id - 1); } - return nullptr; + return Result::error("Group information not available"); } uint32_t SdkSession::getGroupChannelList(uint32_t group_id, uint16_t* list, uint32_t max_count) const { @@ -1582,19 +1466,22 @@ uint32_t SdkSession::getGroupChannelList(uint32_t group_id, uint16_t* list, uint // Prefer device_config (updated in updateConfigFromBuffer before the // sendAndWait / SYSREP sync barrier fires). shmem chaninfo may lag // slightly because SDK callbacks run after the sync notification. - const cbPKT_CHANINFO* ci = nullptr; - if (m_impl->device_session) - ci = m_impl->device_session->getChanInfo(chan); - else - ci = getChanInfo(chan); - - if (!ci) continue; + cbPKT_CHANINFO ci{}; + if (m_impl->device_session) { + const auto* res = m_impl->device_session->getChanInfo(chan); + if (!res) continue; + ci = *res; + } else { + auto res = getChanInfo(chan); + if (res.isError()) continue; + ci = res.value(); + } bool in_group; if (is_raw) - in_group = (ci->ainpopts & cbAINP_RAWSTREAM) != 0; + in_group = (ci.ainpopts & cbAINP_RAWSTREAM) != 0; else - in_group = (ci->smpgroup == group_id); + in_group = (ci.smpgroup == group_id); if (in_group) list[count++] = static_cast(chan); @@ -1602,25 +1489,17 @@ uint32_t SdkSession::getGroupChannelList(uint32_t group_id, uint16_t* list, uint return count; } -const cbPKT_FILTINFO* SdkSession::getFilterInfo(const uint32_t filter_id) const { +Result SdkSession::getFilterInfo(const uint32_t filter_id) const { if (filter_id >= cbMAXFILTS) - return nullptr; + return Result::error("Invalid filter ID"); if (m_impl->device_session) { const auto& config = m_impl->device_session->getDeviceConfig(); - return &config.filtinfo[filter_id]; + return Result::ok(config.filtinfo[filter_id]); } if (m_impl->shmem_session) { - const auto* native = m_impl->shmem_session->getNativeConfigBuffer(); - if (native) - return &native->filtinfo[filter_id]; - const auto* legacy = m_impl->shmem_session->getLegacyConfigBuffer(); - if (legacy) { - int32_t inst = getCentralInstrumentIndex(m_impl->config.device_type); - if (inst >= 0) - return &legacy->filtinfo[inst][filter_id]; - } + return m_impl->shmem_session->getFilterInfo(filter_id + 1); } - return nullptr; + return Result::error("Filter information not available"); } uint32_t SdkSession::getRunLevel() const { @@ -1629,7 +1508,8 @@ uint32_t SdkSession::getRunLevel() const { // Fall back to the SYSINFO mirrored into shmem by the STANDALONE owner. // In CLIENT mode the device only emits SYSREP on runlevel-set commands, // so this session's receive ring may never see one in steady state. - if (const auto* si = getSysInfo()) return si->runlevel; + auto si = getSysInfo(); + if (si.isOk()) return si.value().runlevel; return 0; } @@ -1644,12 +1524,8 @@ uint32_t SdkSession::getProtocolVersion() const { const auto* native = m_impl->shmem_session->getNativeConfigBuffer(); if (native) return CBPROTO_PROTOCOL_CURRENT; // NATIVE layout always uses current protocol - const auto* legacy = m_impl->shmem_session->getLegacyConfigBuffer(); - if (legacy) { - int32_t inst = getCentralInstrumentIndex(m_impl->config.device_type); - if (inst >= 0) - return legacy->procinfo[inst].version; - } + else + return m_impl->shmem_session->getCompatProtocolVersion(); } return 0; } @@ -1662,35 +1538,35 @@ std::string SdkSession::getProcIdent() const { } if (m_impl->shmem_session) { const auto* native = m_impl->shmem_session->getNativeConfigBuffer(); - if (native) + if (native) { return std::string(native->procinfo.ident, strnlen(native->procinfo.ident, sizeof(native->procinfo.ident))); - const auto* legacy = m_impl->shmem_session->getLegacyConfigBuffer(); - if (legacy) { - int32_t inst = getCentralInstrumentIndex(m_impl->config.device_type); - if (inst >= 0) - return std::string(legacy->procinfo[inst].ident, - strnlen(legacy->procinfo[inst].ident, sizeof(legacy->procinfo[inst].ident))); + } else { + auto procinfo = m_impl->shmem_session->getProcInfo(); + if (procinfo.isError()) { + return {}; + } + return std::string(procinfo.value().ident, strnlen(procinfo.value().ident, sizeof(procinfo.value().ident))); } } return {}; } uint32_t SdkSession::getSpikeLength() const { - const auto* si = getSysInfo(); - return si ? si->spikelen : 0; + auto si = getSysInfo(); + return si.isOk() ? si.value().spikelen : 0; } uint32_t SdkSession::getSpikePretrigger() const { - const auto* si = getSysInfo(); - return si ? si->spikepre : 0; + auto si = getSysInfo(); + return si.isOk() ? si.value().spikepre : 0; } Result SdkSession::setSpikeLength(uint32_t spikelen, uint32_t spikepre) { - const auto* si = getSysInfo(); - if (!si) - return Result::error("System info not available"); - cbPKT_SYSINFO pkt = *si; + auto si = getSysInfo(); + if (si.isError()) + return Result::error(si.error()); + cbPKT_SYSINFO pkt = si.value(); pkt.cbpkt_header.type = cbPKTTYPE_SYSSETSPKLEN; pkt.spikelen = spikelen; pkt.spikepre = spikepre; @@ -1735,29 +1611,13 @@ static int64_t extractChanInfoField(const cbPKT_CHANINFO& ci, ChanInfoField fiel } } -/// Helper: get chaninfo pointer for a 0-based channel index -const cbPKT_CHANINFO* SdkSession::Impl::getChanInfoPtr(uint32_t idx) const { - // Prefer shmem: CMP position overlays are written there. - // device_config is owned by the receive thread and doesn't have positions. - if (shmem_session) { - const auto* native = shmem_session->getNativeConfigBuffer(); - if (native) return &native->chaninfo[idx]; - const auto* legacy = shmem_session->getLegacyConfigBuffer(); - if (legacy) return &legacy->chaninfo[idx]; - } - if (device_session) { - return device_session->getChanInfo(idx + 1); - } - return nullptr; -} - Result SdkSession::getChannelField(uint32_t chanId, ChanInfoField field) const { if (chanId == 0 || chanId > cbMAXCHANS) return Result::error("Invalid channel ID"); - const auto* ci = m_impl->getChanInfoPtr(chanId - 1); - if (!ci) + auto ci = getChanInfo(chanId); + if (ci.isError()) return Result::error("Channel info unavailable"); - return Result::ok(extractChanInfoField(*ci, field)); + return Result::ok(extractChanInfoField(ci.value(), field)); } Result> SdkSession::getMatchingChannelIds( @@ -1765,9 +1625,9 @@ Result> SdkSession::getMatchingChannelIds( std::vector ids; size_t count = 0; for (uint32_t ch = 0; ch < cbMAXCHANS && count < nChans; ++ch) { - const auto* ci = m_impl->getChanInfoPtr(ch); - if (!ci) continue; - if (classifyChannelByCaps(*ci) != chanType) continue; + auto ci = getChanInfo(ch + 1); + if (ci.isError()) continue; + if (classifyChannelByCaps(ci.value()) != chanType) continue; ids.push_back(ch + 1); count++; } @@ -1779,10 +1639,10 @@ Result> SdkSession::getChannelField( std::vector values; size_t count = 0; for (uint32_t ch = 0; ch < cbMAXCHANS && count < nChans; ++ch) { - const auto* ci = m_impl->getChanInfoPtr(ch); - if (!ci) continue; - if (classifyChannelByCaps(*ci) != chanType) continue; - values.push_back(extractChanInfoField(*ci, field)); + auto ci = getChanInfo(ch + 1); + if (ci.isError()) continue; + if (classifyChannelByCaps(ci.value()) != chanType) continue; + values.push_back(extractChanInfoField(ci.value(), field)); count++; } return Result>::ok(std::move(values)); @@ -1793,10 +1653,10 @@ Result> SdkSession::getChannelLabels( std::vector labels; size_t count = 0; for (uint32_t ch = 0; ch < cbMAXCHANS && count < nChans; ++ch) { - const auto* ci = m_impl->getChanInfoPtr(ch); - if (!ci) continue; - if (classifyChannelByCaps(*ci) != chanType) continue; - labels.emplace_back(ci->label); + auto ci = getChanInfo(ch + 1); + if (ci.isError()) continue; + if (classifyChannelByCaps(ci.value()) != chanType) continue; + labels.emplace_back(ci.value().label); count++; } return Result>::ok(std::move(labels)); @@ -1807,13 +1667,13 @@ Result> SdkSession::getChannelPositions( std::vector positions; size_t count = 0; for (uint32_t ch = 0; ch < cbMAXCHANS && count < nChans; ++ch) { - const auto* ci = m_impl->getChanInfoPtr(ch); - if (!ci) continue; - if (classifyChannelByCaps(*ci) != chanType) continue; - positions.push_back(ci->position[0]); - positions.push_back(ci->position[1]); - positions.push_back(ci->position[2]); - positions.push_back(ci->position[3]); + auto ci = getChanInfo(ch + 1); + if (ci.isError()) continue; + if (classifyChannelByCaps(ci.value()) != chanType) continue; + positions.push_back(ci.value().position[0]); + positions.push_back(ci.value().position[1]); + positions.push_back(ci.value().position[2]); + positions.push_back(ci.value().position[3]); count++; } return Result>::ok(std::move(positions)); @@ -1850,8 +1710,8 @@ static std::vector resolveTargetChans( result.reserve(std::min(nChans, cbMAXCHANS)); } for (uint32_t chan = 1; chan <= cbMAXCHANS && result.size() < nChans; ++chan) { - const cbPKT_CHANINFO* ci = session.getChanInfo(chan); - if (ci && classifyChannelByCaps(*ci) == chanType) { + auto ci = session.getChanInfo(chan); + if (ci.isOk() && classifyChannelByCaps(ci.value()) == chanType) { result.push_back(chan); } } @@ -1873,9 +1733,9 @@ Result SdkSession::setSampleGroup( // Always sends the full chaninfo (seeded from local cache), so a stale // CHANREP can't leave us stuck against a concurrent change. auto build_packet = [this](uint32_t chan, uint32_t grp) -> std::optional { - const cbPKT_CHANINFO* base = getChanInfo(chan); - if (!base || base->chan == 0) return std::nullopt; - cbPKT_CHANINFO chaninfo = *base; + auto base = getChanInfo(chan); + if (base.isError() || base.value().chan == 0) return std::nullopt; + cbPKT_CHANINFO& chaninfo = base.value(); chaninfo.chan = chan; if (grp > 0 && grp < 6) { chaninfo.cbpkt_header.type = cbPKTTYPE_CHANSETSMP; @@ -1910,8 +1770,8 @@ Result SdkSession::setSampleGroup( std::set target_set(targets.begin(), targets.end()); for (uint32_t chan = 1; chan <= cbMAXCHANS; ++chan) { if (target_set.count(chan)) continue; - const cbPKT_CHANINFO* ci = getChanInfo(chan); - if (!ci || classifyChannelByCaps(*ci) != chanType) continue; + auto ci = getChanInfo(chan); + if (ci.isError() || classifyChannelByCaps(ci.value()) != chanType) continue; if (auto pkt = build_packet(chan, 0u); pkt) { packets.push_back(reinterpret_cast(*pkt)); } @@ -1956,9 +1816,9 @@ static Result applyBulkSetter( std::vector packets; packets.reserve(targets.size()); for (uint32_t chan : targets) { - const cbPKT_CHANINFO* base = session.getChanInfo(chan); - if (!base || base->chan == 0) continue; - cbPKT_CHANINFO chaninfo = *base; + auto base = session.getChanInfo(chan); + if (base.isError() || base.value().chan == 0) continue; + cbPKT_CHANINFO& chaninfo = base.value(); chaninfo.chan = chan; mutate(chaninfo); packets.push_back(reinterpret_cast(chaninfo)); @@ -2125,12 +1985,12 @@ Result SdkSession::setAnalogOutputMonitor(uint32_t aout_chan_id, uint32_t if (aout_chan_id < 1 || aout_chan_id > cbMAXCHANS) return Result::error("Invalid analog output channel ID"); - const cbPKT_CHANINFO* info = getChanInfo(aout_chan_id); - if (!info) + auto info = getChanInfo(aout_chan_id); + if (info.isError()) return Result::error("Channel info not available for channel " + std::to_string(aout_chan_id)); // Copy current config and modify analog output fields - cbPKT_CHANINFO chaninfo = *info; + cbPKT_CHANINFO& chaninfo = info.value(); // Set monitor channel chaninfo.monchan = static_cast(monitor_chan_id); @@ -2253,17 +2113,17 @@ Result SdkSession::loadChannelMap( { std::lock_guard lock(m_impl->cmp_mutex); for (uint32_t chan_id = 1; chan_id <= cbMAXCHANS; ++chan_id) { - const cbPKT_CHANINFO* info = getChanInfo(chan_id); - if (!info || info->chan == 0) continue; - auto it = m_impl->cmp_entries.find(cmpKey(info->bank, info->term)); + auto info = getChanInfo(chan_id); + if (info.isError() || info.value().chan == 0) continue; + auto it = m_impl->cmp_entries.find(cmpKey(info.value().bank, info.value().term)); if (it == m_impl->cmp_entries.end()) continue; labels_to_push.emplace_back(chan_id, it->second.label); } } for (const auto& [chan_id, label] : labels_to_push) { - const cbPKT_CHANINFO* info = getChanInfo(chan_id); - if (!info) continue; - cbPKT_CHANINFO ci = *info; + auto info = getChanInfo(chan_id); + if (info.isError()) continue; + cbPKT_CHANINFO& ci = info.value(); ci.chan = chan_id; ci.cbpkt_header.type = cbPKTTYPE_CHANSETLABEL; std::strncpy(ci.label, label.c_str(), sizeof(ci.label) - 1); @@ -2284,9 +2144,9 @@ Result SdkSession::clearChannelMap() { std::lock_guard lock(m_impl->cmp_mutex); if (!m_impl->cmp_entries.empty()) { for (uint32_t chan_id = 1; chan_id <= cbMAXCHANS; ++chan_id) { - const cbPKT_CHANINFO* info = getChanInfo(chan_id); - if (!info || info->chan == 0) continue; - if (m_impl->cmp_entries.count(cmpKey(info->bank, info->term))) { + auto info = getChanInfo(chan_id); + if (info.isError() || info.value().chan == 0) continue; + if (m_impl->cmp_entries.count(cmpKey(info.value().bank, info.value().term))) { mapped_chans.push_back(chan_id); } } @@ -2313,9 +2173,9 @@ Result SdkSession::clearChannelMap() { // local-only. if (m_impl->device_session || m_impl->shmem_session) { for (uint32_t chan_id : mapped_chans) { - const cbPKT_CHANINFO* info = getChanInfo(chan_id); - if (!info) continue; - cbPKT_CHANINFO ci = *info; + auto info = getChanInfo(chan_id); + if (info.isError()) continue; + cbPKT_CHANINFO& ci = info.value(); ci.chan = chan_id; ci.cbpkt_header.type = cbPKTTYPE_CHANSETLABEL; char default_label[16]; @@ -2562,7 +2422,7 @@ std::optional SdkSession::getClockOffsetNs() const { return offset; } - // CLIENT fallback: use local ClockSync (CENTRAL_COMPAT — no shmem clock fields) + // CLIENT fallback: use local ClockSync (CENTRAL — no shmem clock fields) return m_impl->client_clock_sync.getOffsetNs(); } @@ -2577,7 +2437,7 @@ std::optional SdkSession::getClockUncertaintyNs() const { return uncert; } - // CLIENT fallback: use local ClockSync (CENTRAL_COMPAT) + // CLIENT fallback: use local ClockSync (CENTRAL) return m_impl->client_clock_sync.getUncertaintyNs(); } @@ -2595,7 +2455,7 @@ Result SdkSession::sendPacket(const cbPKT_GENERIC& pkt) { // Stamp packet with a nanosecond timestamp (Central's xmt consumer skips packets with time=0). // Use getLastTime() (always nanoseconds) so that enqueuePacket's per-protocol translation - // can convert to the format each consumer expects (e.g. 30kHz ticks for 3.11 CENTRAL_COMPAT). + // can convert to the format each consumer expects (e.g. 30kHz ticks for 3.11 CENTRAL). cbPKT_GENERIC stamped = pkt; PROCTIME t = m_impl->shmem_session->getLastTime(); stamped.cbpkt_header.time = (t != 0) ? t : 1; diff --git a/src/cbshm/CMakeLists.txt b/src/cbshm/CMakeLists.txt index b5456a7e..bca29984 100644 --- a/src/cbshm/CMakeLists.txt +++ b/src/cbshm/CMakeLists.txt @@ -9,6 +9,12 @@ project(cbshm # Library sources set(CBSHMEM_SOURCES src/shmem_session.cpp + src/central_version.cpp + src/central_adapters/v7_8.cpp + src/central_adapters/v7_7.cpp + src/central_adapters/v7_6.cpp + src/central_adapters/v7_5.cpp + src/central_adapters/v7_0.cpp ) # Build as STATIC library @@ -34,7 +40,7 @@ target_compile_features(cbshm PUBLIC cxx_std_17) if(WIN32) # Windows shared memory APIs (kernel32 is linked by default) target_compile_definitions(cbshm PRIVATE _WIN32_WINNT=0x0601) - + target_link_libraries(cbshm PRIVATE Kernel32 Version) elseif(APPLE) # macOS shared memory APIs target_link_libraries(cbshm PRIVATE pthread) diff --git a/src/cbshm/README.md b/src/cbshm/README.md index caca2edb..29a226c0 100644 --- a/src/cbshm/README.md +++ b/src/cbshm/README.md @@ -5,13 +5,13 @@ between STANDALONE and CLIENT sessions. ## Responsibilities -- **Three layout modes:** `NATIVE` (CereLink-to-CereLink), `CENTRAL_COMPAT` (attach to - Central's shared memory), `CENTRAL` (Central-compatible layout created by CereLink) +- **Two layout modes:** `NATIVE` (CereLink-to-CereLink), `CENTRAL` (attach to + Central's shared memory) - **Consistent packet indexing:** Always uses `packet.instrument` as the array index, regardless of mode - **Ring buffer I/O:** Write packets to the receive buffer (STANDALONE), read packets from it (CLIENT), with protocol-aware parsing and translation in compat mode -- **Instrument filtering:** In `CENTRAL_COMPAT` mode, filters packets from Central's +- **Instrument filtering:** In `CENTRAL` mode, filters packets from Central's shared receive buffer by instrument index - **Platform-specific implementations:** Windows (`CreateFileMapping`) and POSIX (`shm_open`) shared memory, plus platform-specific signaling @@ -21,14 +21,13 @@ between STANDALONE and CLIENT sessions. | Type | Purpose | |------|---------| | `ShmemSession` | Main API — create/attach segments, read/write buffers, access config | -| `ShmemLayout` | Enum: `CENTRAL`, `CENTRAL_COMPAT`, `NATIVE` | +| `ShmemLayout` | Enum: `CENTRAL`, `NATIVE` | | `NativeConfigBuffer` | Single-instrument config (284 channels, scalar arrays) | -| `CentralLegacyCFGBUFF` | Matches Central's exact binary layout for compat mode | -| `cbConfigBuffer` | CereLink's own multi-instrument config layout | +| `cbCFGBUFF` | Matches Central's exact binary layout for compat mode | ## Key Design Notes -- **Central vs CereLink constants:** Central uses `cbMAXPROCS=4`, `cbNUM_FE_CHANS=768`. +- **Central vs CereLink constants:** Central uses `cbMAXPROCS=4`, `cbNUM_FE_CHANS=768` (in v7.8.0). CereLink uses `cbMAXPROCS=1`, `cbNUM_FE_CHANS=256`. The compat types use Central's constants; native types use CereLink's. - **Not exposed to public API:** cbsdk orchestrates cbshm; users don't interact with it diff --git a/src/cbshm/include/cbshm/central_adapters/base.h b/src/cbshm/include/cbshm/central_adapters/base.h new file mode 100644 index 00000000..e8f578c6 --- /dev/null +++ b/src/cbshm/include/cbshm/central_adapters/base.h @@ -0,0 +1,185 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////// +/// @file base.h +/// @author Caden Shmookler +/// @date 2026-05-22 +/// +/// @brief Base classes for the Central-compatible shared memory access adapters +/// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef CBSHM_CENTRAL_ADAPTERS_BASE_H +#define CBSHM_CENTRAL_ADAPTERS_BASE_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cbshm { + +struct CentralAdapterArgs; +class CentralAdapterBase; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Base-class adapter that provides information used to fetch pointers to Central's shared memory +/// +class CentralBootstrapAdapterBase { +public: + virtual ~CentralBootstrapAdapterBase() = default; + + // Max instrument count + virtual uint32_t getMaxProcs() const = 0; + + // Buffer sizes + virtual size_t getConfigBufferSize() const = 0; + virtual size_t getReceiveBufferSize() const = 0; + virtual size_t getTransmitBufferSize() const = 0; + virtual size_t getTransmitBufferLocalSize() const = 0; + virtual size_t getStatusBufferSize() const = 0; + virtual size_t getSpikeBufferSize() const = 0; + virtual size_t getReceiveBufferLen() const = 0; + virtual size_t getReceiveReserveLen() const = 0; + virtual size_t getTransmitBufferLen() const = 0; + virtual size_t getTransmitBufferLocalLen() const = 0; + + // Adapter construction + virtual std::unique_ptr makeAdapter(const CentralAdapterArgs& args) const = 0; +}; + +struct CentralAdapterArgs { + uint8_t instrument_idx; + void* cfg_ptr; + void* rec_ptr; + void* xmt_ptr; + void* xmt_local_ptr; + void* status_ptr; + void* spike_ptr; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Base-class adapter for Central-compatible shared memory access +/// +class CentralAdapterBase { +protected: + template + static void copyArr(T(&lhs)[lhs_n], const T(&rhs)[rhs_n]) { + if (lhs_n <= rhs_n) { + std::memcpy(lhs, rhs, lhs_n * sizeof(T)); + } else { + std::memcpy(lhs, rhs, rhs_n * sizeof(T)); + std::memset(lhs + rhs_n, 0, (lhs_n - rhs_n) * sizeof(T)); + } + } + + template + static void copyArr(LHS_T(&lhs)[lhs_n], const RHS_T(&rhs)[rhs_n], const A* adapter, void(A::*translation_func)(LHS_T&, const RHS_T&) const) { + if (lhs_n <= rhs_n) { + for (size_t i = 0; i < lhs_n; ++i) { + (adapter->*translation_func)(lhs[i], rhs[i]); + } + } else { + for (size_t i = 0; i < rhs_n; ++i) { + (adapter->*translation_func)(lhs[i], rhs[i]); + } + std::memset(lhs + rhs_n, 0, (lhs_n - rhs_n) * sizeof(LHS_T)); + } + } + + template + static void copyArr2D(T(&lhs)[lhs_ny][lhs_nx], const T(&rhs)[rhs_ny][rhs_nx]) { + if (lhs_ny <= rhs_ny) { + if (lhs_nx == rhs_nx) { + std::memcpy(lhs, rhs, (lhs_ny * lhs_nx) * sizeof(T)); + } else { + for (size_t i = 0; i < lhs_ny; ++i) { + copyArr(lhs[i], rhs[i]); + } + } + } else { + if (lhs_nx == rhs_nx) { + std::memcpy(lhs, rhs, (rhs_ny * rhs_nx) * sizeof(T)); + } else { + for (size_t i = 0; i < rhs_ny; ++i) { + copyArr(lhs[i], rhs[i]); + } + } + std::memset(lhs + rhs_ny, 0, ((lhs_ny - rhs_ny) * lhs_nx) * sizeof(T)); + } + } + + template + static void copyArr2D(LHS_T(&lhs)[lhs_ny][lhs_nx], const RHS_T(&rhs)[rhs_ny][rhs_nx], const A* adapter, void(A::*translation_func)(LHS_T&, const RHS_T&) const) { + if (lhs_ny <= rhs_ny) { + for (size_t i = 0; i < lhs_ny; ++i) { + copyArr(lhs[i], rhs[i], adapter, translation_func); + } + } else { + for (size_t i = 0; i < rhs_ny; ++i) { + copyArr(lhs[i], rhs[i], adapter, translation_func); + } + std::memset(lhs + rhs_ny, 0, ((lhs_ny - rhs_ny) * lhs_nx) * sizeof(LHS_T)); + } + } + +public: + virtual ~CentralAdapterBase() = default; + + /////////////////////////////////////////////////////////////////////////////////////////////// + // DANGER !!! + // These methods are brittle and serve as a workaround for the ShmemSession + // implementation requiring direct pointer access to the receive and transmit buffers. + + // Receive buffer access + virtual uint32_t& getRecReceived() = 0; + virtual uint64_t getRecLasttime() = 0; + virtual void setRecLasttime(uint64_t lasttime) = 0; + virtual uint32_t& getRecHeadwrapPtr() = 0; + virtual uint32_t& getRecHeadindexPtr() = 0; + virtual uint32_t* getRecBufferPtr() = 0; + + // Transmit buffer access + virtual uint32_t& getXmtTransmittedPtr() = 0; + virtual uint32_t& getXmtHeadindexPtr() = 0; + virtual uint32_t& getXmtTailindexPtr() = 0; + virtual uint32_t& getXmtLastValidIndexPtr() = 0; + virtual uint32_t& getXmtBufferlenPtr() = 0; + virtual uint32_t* getXmtBufferPtr() = 0; + + virtual uint32_t& getLocalXmtTransmittedPtr() = 0; + virtual uint32_t& getLocalXmtHeadindexPtr() = 0; + virtual uint32_t& getLocalXmtTailindexPtr() = 0; + virtual uint32_t& getLocalXmtLastValidIndexPtr() = 0; + virtual uint32_t& getLocalXmtBufferlenPtr() = 0; + virtual uint32_t* getLocalXmtBufferPtr() = 0; + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// Config read operations + virtual cbutil::Result getProcInfo(::cbPKT_PROCINFO& buf) const = 0; + virtual cbutil::Result getBankInfo(::cbPKT_BANKINFO& buf, uint32_t bank_num) const = 0; + virtual cbutil::Result getFilterInfo(::cbPKT_FILTINFO& buf, uint32_t filter_num) const = 0; + virtual cbutil::Result getChanInfo(::cbPKT_CHANINFO& buf, uint32_t channel_idx) const = 0; + virtual cbutil::Result getSysInfo(::cbPKT_SYSINFO& buf) const = 0; + virtual cbutil::Result getGroupInfo(::cbPKT_GROUPINFO& buf, uint32_t group_idx) const = 0; + virtual cbutil::Result getConfigBuffer(NativeConfigBuffer& buf) const = 0; + virtual cbutil::Result getPcStatus(NativePCStatus& buf) const = 0; + virtual cbutil::Result getSpikeCache(NativeSpikeCache& buf, uint32_t channel_idx) const = 0; + + /// Config write operations + virtual cbutil::Result setProcInfo(const ::cbPKT_PROCINFO& info) = 0; + virtual cbutil::Result setBankInfo(uint32_t bank_num, const ::cbPKT_BANKINFO& info) = 0; + virtual cbutil::Result setFilterInfo(uint32_t filter_num, const ::cbPKT_FILTINFO& info) = 0; + virtual cbutil::Result setChanInfo(uint32_t channel_idx, const ::cbPKT_CHANINFO& info) = 0; + virtual cbutil::Result setSysInfo(const ::cbPKT_SYSINFO& info) = 0; + virtual cbutil::Result setGroupInfo(uint32_t group_idx, const ::cbPKT_GROUPINFO& info) = 0; + virtual cbutil::Result setNspStatus(const NativeNSPStatus& status) const = 0; + virtual cbutil::Result setGeminiSystem(bool is_gemini) const = 0; +}; + +} // namespace cbshm + +#endif // CBSHM_CENTRAL_ADAPTERS_BASE_H diff --git a/src/cbshm/include/cbshm/central_adapters/v7_0.h b/src/cbshm/include/cbshm/central_adapters/v7_0.h new file mode 100644 index 00000000..6cf9c873 --- /dev/null +++ b/src/cbshm/include/cbshm/central_adapters/v7_0.h @@ -0,0 +1,188 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////// +/// @file v7_0.h +/// @author Caden Shmookler +/// @date 2026-05-22 +/// +/// @brief Adapter for Central-compatible shared memory access +/// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef CBSHM_CENTRAL_ADAPTERS_V7_0_H +#define CBSHM_CENTRAL_ADAPTERS_V7_0_H + +#include +#include + +namespace cbshm { + +namespace central_v7_0 { + +/// +/// @brief Adapter that provides information for fetching pointers to Central's shared memory +/// +class BootstrapAdapter : public ::cbshm::CentralBootstrapAdapterBase { +public: + // Max instrument count + uint32_t getMaxProcs() const override; + + // Buffer sizes + size_t getConfigBufferSize() const override; + size_t getReceiveBufferSize() const override; + size_t getTransmitBufferSize() const override; + size_t getTransmitBufferLocalSize() const override; + size_t getStatusBufferSize() const override; + size_t getSpikeBufferSize() const override; + size_t getReceiveBufferLen() const override; + size_t getReceiveReserveLen() const override; + size_t getTransmitBufferLen() const override; + size_t getTransmitBufferLocalLen() const override; + + // Adapter construction + std::unique_ptr makeAdapter(const CentralAdapterArgs& args) const override; +}; + +/// +/// @brief Adapter for Central-compatible shared memory access +/// +class Adapter : public CentralAdapterBase { +private: + uint8_t instrument_idx; + cbCFGBUFF* cfg; + cbRECBUFF* rec; + cbXMTBUFF* xmt; + cbXMTBUFFLOCAL* xmt_local; + cbPcStatus* status; + cbSPKBUFF* spike; + + void fromLegacy(::cbPKT_HEADER& cur, const cbPKT_HEADER& leg) const; + void fromLegacy(::cbPKT_SYSINFO& cur, const cbPKT_SYSINFO& leg) const; + void fromLegacy(::cbPKT_PROCINFO& cur, const cbPKT_PROCINFO& leg) const; + void fromLegacy(::cbPKT_BANKINFO& cur, const cbPKT_BANKINFO& leg) const; + void fromLegacy(::cbPKT_GROUPINFO& cur, const cbPKT_GROUPINFO& leg) const; + void fromLegacy(::cbPKT_FILTINFO& cur, const cbPKT_FILTINFO& leg) const; + void fromLegacy(::cbPKT_ADAPTFILTINFO& cur, const cbPKT_ADAPTFILTINFO& leg) const; + void fromLegacy(::cbPKT_REFELECFILTINFO& cur, const cbPKT_REFELECFILTINFO& leg) const; + void fromLegacy(::cbSCALING& cur, const cbSCALING& leg) const; + void fromLegacy(::cbFILTDESC& cur, const cbFILTDESC& leg) const; + void fromLegacy(::cbMANUALUNITMAPPING& cur, const cbMANUALUNITMAPPING& leg) const; + void fromLegacy(::cbHOOP& cur, const cbHOOP& leg) const; + void fromLegacy(::cbPKT_CHANINFO& cur, const cbPKT_CHANINFO& leg) const; + void fromLegacy(::cbPKT_FS_BASIS& cur, const cbPKT_FS_BASIS& leg) const; + void fromLegacy(::cbPKT_SS_MODELSET& cur, const cbPKT_SS_MODELSET& leg) const; + void fromLegacy(::cbPKT_SS_DETECT& cur, const cbPKT_SS_DETECT& leg) const; + void fromLegacy(::cbPKT_SS_ARTIF_REJECT& cur, const cbPKT_SS_ARTIF_REJECT& leg) const; + void fromLegacy(::cbPKT_SS_NOISE_BOUNDARY& cur, const cbPKT_SS_NOISE_BOUNDARY& leg) const; + void fromLegacy(::cbPKT_SS_STATISTICS& cur, const cbPKT_SS_STATISTICS& leg) const; + void fromLegacy(::cbAdaptControl& cur, const cbAdaptControl& leg) const; + void fromLegacy(::cbPKT_SS_STATUS& cur, const cbPKT_SS_STATUS& leg) const; + void fromLegacy(::cbproto::SpikeSorting& cur, const cbSPIKE_SORTING& leg) const; + void fromLegacy(::cbPKT_NTRODEINFO& cur, const cbPKT_NTRODEINFO& leg) const; + void fromLegacy(::cbWaveformData& cur, const cbWaveformData& leg) const; + void fromLegacy(::cbPKT_AOUT_WAVEFORM& cur, const cbPKT_AOUT_WAVEFORM& leg) const; + void fromLegacy(::cbPKT_LNC& cur, const cbPKT_LNC& leg) const; + void fromLegacy(::cbPKT_NPLAY& cur, const cbPKT_NPLAY& leg) const; + void fromLegacy(::cbVIDEOSOURCE& cur, const cbVIDEOSOURCE& leg) const; + void fromLegacy(::cbTRACKOBJ& cur, const cbTRACKOBJ& leg) const; + void fromLegacy(::cbPKT_FILECFG& cur, const cbPKT_FILECFG& leg) const; + void fromLegacy(NativeConfigBuffer& cur, const cbCFGBUFF& leg) const; + void fromLegacy(NativeNSPStatus& cur, const NSPStatus& leg) const; + void fromLegacy(::cbPKT_UNIT_SELECTION& cur, const cbPKT_UNIT_SELECTION& leg) const; + void fromLegacy(NativePCStatus& cur, const cbPcStatus& leg) const; + void fromLegacy(NativeReceiveBuffer& cur, const cbRECBUFF& leg) const; + void fromLegacy(NativeTransmitBuffer& cur, const cbXMTBUFF& leg) const; + void fromLegacy(NativeTransmitBufferLocal& cur, const cbXMTBUFFLOCAL& leg) const; + void fromLegacy(::cbPKT_SPK& cur, const cbPKT_SPK& leg) const; + void fromLegacy(NativeSpikeCache& cur, const cbSPKCACHE& leg) const; + void fromLegacy(NativeSpikeBuffer& cur, const cbSPKBUFF& leg) const; + + void toLegacy(cbPKT_HEADER& leg, const ::cbPKT_HEADER& cur) const; + void toLegacy(cbPKT_SYSINFO& leg, const ::cbPKT_SYSINFO& cur) const; + void toLegacy(cbPKT_PROCINFO& leg, const ::cbPKT_PROCINFO& cur) const; + void toLegacy(cbPKT_BANKINFO& leg, const ::cbPKT_BANKINFO& cur) const; + void toLegacy(cbPKT_GROUPINFO& leg, const ::cbPKT_GROUPINFO& cur) const; + void toLegacy(cbPKT_FILTINFO& leg, const ::cbPKT_FILTINFO& cur) const; + void toLegacy(cbPKT_ADAPTFILTINFO& leg, const ::cbPKT_ADAPTFILTINFO& cur) const; + void toLegacy(cbPKT_REFELECFILTINFO& leg, const ::cbPKT_REFELECFILTINFO& cur) const; + void toLegacy(cbSCALING& leg, const ::cbSCALING& cur) const; + void toLegacy(cbFILTDESC& leg, const ::cbFILTDESC& cur) const; + void toLegacy(cbMANUALUNITMAPPING& leg, const ::cbMANUALUNITMAPPING& cur) const; + void toLegacy(cbHOOP& leg, const ::cbHOOP& cur) const; + void toLegacy(cbPKT_CHANINFO& leg, const ::cbPKT_CHANINFO& cur) const; + void toLegacy(cbPKT_FS_BASIS& leg, const ::cbPKT_FS_BASIS& cur) const; + void toLegacy(cbPKT_SS_MODELSET& leg, const ::cbPKT_SS_MODELSET& cur) const; + void toLegacy(cbPKT_SS_DETECT& leg, const ::cbPKT_SS_DETECT& cur) const; + void toLegacy(cbPKT_SS_ARTIF_REJECT& leg, const ::cbPKT_SS_ARTIF_REJECT& cur) const; + void toLegacy(cbPKT_SS_NOISE_BOUNDARY& leg, const ::cbPKT_SS_NOISE_BOUNDARY& cur) const; + void toLegacy(cbPKT_SS_STATISTICS& leg, const ::cbPKT_SS_STATISTICS& cur) const; + void toLegacy(cbAdaptControl& leg, const ::cbAdaptControl& cur) const; + void toLegacy(cbPKT_SS_STATUS& leg, const ::cbPKT_SS_STATUS& cur) const; + void toLegacy(cbSPIKE_SORTING& leg, const ::cbproto::SpikeSorting& cur) const; + void toLegacy(cbPKT_NTRODEINFO& leg, const ::cbPKT_NTRODEINFO& cur) const; + void toLegacy(cbWaveformData& leg, const ::cbWaveformData& cur) const; + void toLegacy(cbPKT_AOUT_WAVEFORM& leg, const ::cbPKT_AOUT_WAVEFORM& cur) const; + void toLegacy(cbPKT_LNC& leg, const ::cbPKT_LNC& cur) const; + void toLegacy(cbPKT_NPLAY& leg, const ::cbPKT_NPLAY& cur) const; + void toLegacy(cbVIDEOSOURCE& leg, const ::cbVIDEOSOURCE& cur) const; + void toLegacy(cbTRACKOBJ& leg, const ::cbTRACKOBJ& cur) const; + void toLegacy(cbPKT_FILECFG& leg, const ::cbPKT_FILECFG& cur) const; + void toLegacy(NSPStatus& leg, const NativeNSPStatus& cur) const; + void toLegacy(cbPKT_UNIT_SELECTION& leg, const ::cbPKT_UNIT_SELECTION& cur) const; + void toLegacy(cbRECBUFF& leg, const NativeReceiveBuffer& cur) const; + void toLegacy(cbXMTBUFF& leg, const NativeTransmitBuffer& cur) const; + void toLegacy(cbXMTBUFFLOCAL& leg, const NativeTransmitBufferLocal& cur) const; + void toLegacy(cbPKT_SPK& leg, const ::cbPKT_SPK& cur) const; + +public: + explicit Adapter(const CentralAdapterArgs& args); + ~Adapter() = default; + + // Receive buffer access + uint32_t& getRecReceived() override; + uint64_t getRecLasttime() override; + void setRecLasttime(uint64_t lasttime) override; + uint32_t& getRecHeadwrapPtr() override; + uint32_t& getRecHeadindexPtr() override; + uint32_t* getRecBufferPtr() override; + + // Transmit buffer access + uint32_t& getXmtTransmittedPtr() override; + uint32_t& getXmtHeadindexPtr() override; + uint32_t& getXmtTailindexPtr() override; + uint32_t& getXmtLastValidIndexPtr() override; + uint32_t& getXmtBufferlenPtr() override; + uint32_t* getXmtBufferPtr() override; + + uint32_t& getLocalXmtTransmittedPtr() override; + uint32_t& getLocalXmtHeadindexPtr() override; + uint32_t& getLocalXmtTailindexPtr() override; + uint32_t& getLocalXmtLastValidIndexPtr() override; + uint32_t& getLocalXmtBufferlenPtr() override; + uint32_t* getLocalXmtBufferPtr() override; + + /// Config read operations + cbutil::Result getProcInfo(::cbPKT_PROCINFO& buf) const override; + cbutil::Result getBankInfo(::cbPKT_BANKINFO& buf, uint32_t bank_num) const override; + cbutil::Result getFilterInfo(::cbPKT_FILTINFO& buf, uint32_t filter_num) const override; + cbutil::Result getChanInfo(::cbPKT_CHANINFO& buf, uint32_t channel_idx) const override; + cbutil::Result getSysInfo(::cbPKT_SYSINFO& buf) const override; + cbutil::Result getGroupInfo(::cbPKT_GROUPINFO& buf, uint32_t group_idx) const override; + cbutil::Result getConfigBuffer(NativeConfigBuffer& buf) const override; + cbutil::Result getPcStatus(NativePCStatus& buf) const override; + cbutil::Result getSpikeCache(NativeSpikeCache& buf, uint32_t channel_idx) const override; + + /// Config write operations + cbutil::Result setProcInfo(const ::cbPKT_PROCINFO& info) override; + cbutil::Result setBankInfo(uint32_t bank_num, const ::cbPKT_BANKINFO& info) override; + cbutil::Result setFilterInfo(uint32_t filter_num, const ::cbPKT_FILTINFO& info) override; + cbutil::Result setChanInfo(uint32_t channel_idx, const ::cbPKT_CHANINFO& info) override; + cbutil::Result setSysInfo(const ::cbPKT_SYSINFO& info) override; + cbutil::Result setGroupInfo(uint32_t group_idx, const ::cbPKT_GROUPINFO& info) override; + cbutil::Result setNspStatus(const NativeNSPStatus& status) const override; + cbutil::Result setGeminiSystem(bool is_gemini) const override; +}; + +} // namespace central_v7_0 + +} // namespace cbshm + +#endif // CBSHM_CENTRAL_ADAPTERS_V7_0_H diff --git a/src/cbshm/include/cbshm/central_adapters/v7_5.h b/src/cbshm/include/cbshm/central_adapters/v7_5.h new file mode 100644 index 00000000..93fb22e7 --- /dev/null +++ b/src/cbshm/include/cbshm/central_adapters/v7_5.h @@ -0,0 +1,188 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////// +/// @file v7_5.h +/// @author Caden Shmookler +/// @date 2026-05-22 +/// +/// @brief Adapter for Central-compatible shared memory access +/// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef CBSHM_CENTRAL_ADAPTERS_V7_5_H +#define CBSHM_CENTRAL_ADAPTERS_V7_5_H + +#include +#include + +namespace cbshm { + +namespace central_v7_5 { + +/// +/// @brief Adapter that provides information for fetching pointers to Central's shared memory +/// +class BootstrapAdapter : public ::cbshm::CentralBootstrapAdapterBase { +public: + // Max instrument count + uint32_t getMaxProcs() const override; + + // Buffer sizes + size_t getConfigBufferSize() const override; + size_t getReceiveBufferSize() const override; + size_t getTransmitBufferSize() const override; + size_t getTransmitBufferLocalSize() const override; + size_t getStatusBufferSize() const override; + size_t getSpikeBufferSize() const override; + size_t getReceiveBufferLen() const override; + size_t getReceiveReserveLen() const override; + size_t getTransmitBufferLen() const override; + size_t getTransmitBufferLocalLen() const override; + + // Adapter construction + std::unique_ptr makeAdapter(const CentralAdapterArgs& args) const override; +}; + +/// +/// @brief Adapter for Central-compatible shared memory access +/// +class Adapter : public CentralAdapterBase { +private: + uint8_t instrument_idx; + cbCFGBUFF* cfg; + cbRECBUFF* rec; + cbXMTBUFF* xmt; + cbXMTBUFFLOCAL* xmt_local; + cbPcStatus* status; + cbSPKBUFF* spike; + + void fromLegacy(::cbPKT_HEADER& cur, const cbPKT_HEADER& leg) const; + void fromLegacy(::cbPKT_SYSINFO& cur, const cbPKT_SYSINFO& leg) const; + void fromLegacy(::cbPKT_PROCINFO& cur, const cbPKT_PROCINFO& leg) const; + void fromLegacy(::cbPKT_BANKINFO& cur, const cbPKT_BANKINFO& leg) const; + void fromLegacy(::cbPKT_GROUPINFO& cur, const cbPKT_GROUPINFO& leg) const; + void fromLegacy(::cbPKT_FILTINFO& cur, const cbPKT_FILTINFO& leg) const; + void fromLegacy(::cbPKT_ADAPTFILTINFO& cur, const cbPKT_ADAPTFILTINFO& leg) const; + void fromLegacy(::cbPKT_REFELECFILTINFO& cur, const cbPKT_REFELECFILTINFO& leg) const; + void fromLegacy(::cbSCALING& cur, const cbSCALING& leg) const; + void fromLegacy(::cbFILTDESC& cur, const cbFILTDESC& leg) const; + void fromLegacy(::cbMANUALUNITMAPPING& cur, const cbMANUALUNITMAPPING& leg) const; + void fromLegacy(::cbHOOP& cur, const cbHOOP& leg) const; + void fromLegacy(::cbPKT_CHANINFO& cur, const cbPKT_CHANINFO& leg) const; + void fromLegacy(::cbPKT_FS_BASIS& cur, const cbPKT_FS_BASIS& leg) const; + void fromLegacy(::cbPKT_SS_MODELSET& cur, const cbPKT_SS_MODELSET& leg) const; + void fromLegacy(::cbPKT_SS_DETECT& cur, const cbPKT_SS_DETECT& leg) const; + void fromLegacy(::cbPKT_SS_ARTIF_REJECT& cur, const cbPKT_SS_ARTIF_REJECT& leg) const; + void fromLegacy(::cbPKT_SS_NOISE_BOUNDARY& cur, const cbPKT_SS_NOISE_BOUNDARY& leg) const; + void fromLegacy(::cbPKT_SS_STATISTICS& cur, const cbPKT_SS_STATISTICS& leg) const; + void fromLegacy(::cbAdaptControl& cur, const cbAdaptControl& leg) const; + void fromLegacy(::cbPKT_SS_STATUS& cur, const cbPKT_SS_STATUS& leg) const; + void fromLegacy(::cbproto::SpikeSorting& cur, const cbSPIKE_SORTING& leg) const; + void fromLegacy(::cbPKT_NTRODEINFO& cur, const cbPKT_NTRODEINFO& leg) const; + void fromLegacy(::cbWaveformData& cur, const cbWaveformData& leg) const; + void fromLegacy(::cbPKT_AOUT_WAVEFORM& cur, const cbPKT_AOUT_WAVEFORM& leg) const; + void fromLegacy(::cbPKT_LNC& cur, const cbPKT_LNC& leg) const; + void fromLegacy(::cbPKT_NPLAY& cur, const cbPKT_NPLAY& leg) const; + void fromLegacy(::cbVIDEOSOURCE& cur, const cbVIDEOSOURCE& leg) const; + void fromLegacy(::cbTRACKOBJ& cur, const cbTRACKOBJ& leg) const; + void fromLegacy(::cbPKT_FILECFG& cur, const cbPKT_FILECFG& leg) const; + void fromLegacy(NativeConfigBuffer& cur, const cbCFGBUFF& leg) const; + void fromLegacy(NativeNSPStatus& cur, const NSPStatus& leg) const; + void fromLegacy(::cbPKT_UNIT_SELECTION& cur, const cbPKT_UNIT_SELECTION& leg) const; + void fromLegacy(NativePCStatus& cur, const cbPcStatus& leg) const; + void fromLegacy(NativeReceiveBuffer& cur, const cbRECBUFF& leg) const; + void fromLegacy(NativeTransmitBuffer& cur, const cbXMTBUFF& leg) const; + void fromLegacy(NativeTransmitBufferLocal& cur, const cbXMTBUFFLOCAL& leg) const; + void fromLegacy(::cbPKT_SPK& cur, const cbPKT_SPK& leg) const; + void fromLegacy(NativeSpikeCache& cur, const cbSPKCACHE& leg) const; + void fromLegacy(NativeSpikeBuffer& cur, const cbSPKBUFF& leg) const; + + void toLegacy(cbPKT_HEADER& leg, const ::cbPKT_HEADER& cur) const; + void toLegacy(cbPKT_SYSINFO& leg, const ::cbPKT_SYSINFO& cur) const; + void toLegacy(cbPKT_PROCINFO& leg, const ::cbPKT_PROCINFO& cur) const; + void toLegacy(cbPKT_BANKINFO& leg, const ::cbPKT_BANKINFO& cur) const; + void toLegacy(cbPKT_GROUPINFO& leg, const ::cbPKT_GROUPINFO& cur) const; + void toLegacy(cbPKT_FILTINFO& leg, const ::cbPKT_FILTINFO& cur) const; + void toLegacy(cbPKT_ADAPTFILTINFO& leg, const ::cbPKT_ADAPTFILTINFO& cur) const; + void toLegacy(cbPKT_REFELECFILTINFO& leg, const ::cbPKT_REFELECFILTINFO& cur) const; + void toLegacy(cbSCALING& leg, const ::cbSCALING& cur) const; + void toLegacy(cbFILTDESC& leg, const ::cbFILTDESC& cur) const; + void toLegacy(cbMANUALUNITMAPPING& leg, const ::cbMANUALUNITMAPPING& cur) const; + void toLegacy(cbHOOP& leg, const ::cbHOOP& cur) const; + void toLegacy(cbPKT_CHANINFO& leg, const ::cbPKT_CHANINFO& cur) const; + void toLegacy(cbPKT_FS_BASIS& leg, const ::cbPKT_FS_BASIS& cur) const; + void toLegacy(cbPKT_SS_MODELSET& leg, const ::cbPKT_SS_MODELSET& cur) const; + void toLegacy(cbPKT_SS_DETECT& leg, const ::cbPKT_SS_DETECT& cur) const; + void toLegacy(cbPKT_SS_ARTIF_REJECT& leg, const ::cbPKT_SS_ARTIF_REJECT& cur) const; + void toLegacy(cbPKT_SS_NOISE_BOUNDARY& leg, const ::cbPKT_SS_NOISE_BOUNDARY& cur) const; + void toLegacy(cbPKT_SS_STATISTICS& leg, const ::cbPKT_SS_STATISTICS& cur) const; + void toLegacy(cbAdaptControl& leg, const ::cbAdaptControl& cur) const; + void toLegacy(cbPKT_SS_STATUS& leg, const ::cbPKT_SS_STATUS& cur) const; + void toLegacy(cbSPIKE_SORTING& leg, const ::cbproto::SpikeSorting& cur) const; + void toLegacy(cbPKT_NTRODEINFO& leg, const ::cbPKT_NTRODEINFO& cur) const; + void toLegacy(cbWaveformData& leg, const ::cbWaveformData& cur) const; + void toLegacy(cbPKT_AOUT_WAVEFORM& leg, const ::cbPKT_AOUT_WAVEFORM& cur) const; + void toLegacy(cbPKT_LNC& leg, const ::cbPKT_LNC& cur) const; + void toLegacy(cbPKT_NPLAY& leg, const ::cbPKT_NPLAY& cur) const; + void toLegacy(cbVIDEOSOURCE& leg, const ::cbVIDEOSOURCE& cur) const; + void toLegacy(cbTRACKOBJ& leg, const ::cbTRACKOBJ& cur) const; + void toLegacy(cbPKT_FILECFG& leg, const ::cbPKT_FILECFG& cur) const; + void toLegacy(NSPStatus& leg, const NativeNSPStatus& cur) const; + void toLegacy(cbPKT_UNIT_SELECTION& leg, const ::cbPKT_UNIT_SELECTION& cur) const; + void toLegacy(cbRECBUFF& leg, const NativeReceiveBuffer& cur) const; + void toLegacy(cbXMTBUFF& leg, const NativeTransmitBuffer& cur) const; + void toLegacy(cbXMTBUFFLOCAL& leg, const NativeTransmitBufferLocal& cur) const; + void toLegacy(cbPKT_SPK& leg, const ::cbPKT_SPK& cur) const; + +public: + explicit Adapter(const CentralAdapterArgs& args); + ~Adapter() = default; + + // Receive buffer access + uint32_t& getRecReceived() override; + uint64_t getRecLasttime() override; + void setRecLasttime(uint64_t lasttime) override; + uint32_t& getRecHeadwrapPtr() override; + uint32_t& getRecHeadindexPtr() override; + uint32_t* getRecBufferPtr() override; + + // Transmit buffer access + uint32_t& getXmtTransmittedPtr() override; + uint32_t& getXmtHeadindexPtr() override; + uint32_t& getXmtTailindexPtr() override; + uint32_t& getXmtLastValidIndexPtr() override; + uint32_t& getXmtBufferlenPtr() override; + uint32_t* getXmtBufferPtr() override; + + uint32_t& getLocalXmtTransmittedPtr() override; + uint32_t& getLocalXmtHeadindexPtr() override; + uint32_t& getLocalXmtTailindexPtr() override; + uint32_t& getLocalXmtLastValidIndexPtr() override; + uint32_t& getLocalXmtBufferlenPtr() override; + uint32_t* getLocalXmtBufferPtr() override; + + /// Config read operations + cbutil::Result getProcInfo(::cbPKT_PROCINFO& buf) const override; + cbutil::Result getBankInfo(::cbPKT_BANKINFO& buf, uint32_t bank_num) const override; + cbutil::Result getFilterInfo(::cbPKT_FILTINFO& buf, uint32_t filter_num) const override; + cbutil::Result getChanInfo(::cbPKT_CHANINFO& buf, uint32_t channel_idx) const override; + cbutil::Result getSysInfo(::cbPKT_SYSINFO& buf) const override; + cbutil::Result getGroupInfo(::cbPKT_GROUPINFO& buf, uint32_t group_idx) const override; + cbutil::Result getConfigBuffer(NativeConfigBuffer& buf) const override; + cbutil::Result getPcStatus(NativePCStatus& buf) const override; + cbutil::Result getSpikeCache(NativeSpikeCache& buf, uint32_t channel_idx) const override; + + /// Config write operations + cbutil::Result setProcInfo(const ::cbPKT_PROCINFO& info) override; + cbutil::Result setBankInfo(uint32_t bank_num, const ::cbPKT_BANKINFO& info) override; + cbutil::Result setFilterInfo(uint32_t filter_num, const ::cbPKT_FILTINFO& info) override; + cbutil::Result setChanInfo(uint32_t channel_idx, const ::cbPKT_CHANINFO& info) override; + cbutil::Result setSysInfo(const ::cbPKT_SYSINFO& info) override; + cbutil::Result setGroupInfo(uint32_t group_idx, const ::cbPKT_GROUPINFO& info) override; + cbutil::Result setNspStatus(const NativeNSPStatus& status) const override; + cbutil::Result setGeminiSystem(bool is_gemini) const override; +}; + +} // namespace central_v7_5 + +} // namespace cbshm + +#endif // CBSHM_CENTRAL_ADAPTERS_V7_5_H diff --git a/src/cbshm/include/cbshm/central_adapters/v7_6.h b/src/cbshm/include/cbshm/central_adapters/v7_6.h new file mode 100644 index 00000000..69e352df --- /dev/null +++ b/src/cbshm/include/cbshm/central_adapters/v7_6.h @@ -0,0 +1,188 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////// +/// @file v7_6.h +/// @author Caden Shmookler +/// @date 2026-05-22 +/// +/// @brief Adapter for Central-compatible shared memory access +/// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef CBSHM_CENTRAL_ADAPTERS_V7_6_H +#define CBSHM_CENTRAL_ADAPTERS_V7_6_H + +#include +#include + +namespace cbshm { + +namespace central_v7_6 { + +/// +/// @brief Adapter that provides information for fetching pointers to Central's shared memory +/// +class BootstrapAdapter : public ::cbshm::CentralBootstrapAdapterBase { +public: + // Max instrument count + uint32_t getMaxProcs() const override; + + // Buffer sizes + size_t getConfigBufferSize() const override; + size_t getReceiveBufferSize() const override; + size_t getTransmitBufferSize() const override; + size_t getTransmitBufferLocalSize() const override; + size_t getStatusBufferSize() const override; + size_t getSpikeBufferSize() const override; + size_t getReceiveBufferLen() const override; + size_t getReceiveReserveLen() const override; + size_t getTransmitBufferLen() const override; + size_t getTransmitBufferLocalLen() const override; + + // Adapter construction + std::unique_ptr makeAdapter(const CentralAdapterArgs& args) const override; +}; + +/// +/// @brief Adapter for Central-compatible shared memory access +/// +class Adapter : public CentralAdapterBase { +private: + uint8_t instrument_idx; + cbCFGBUFF* cfg; + cbRECBUFF* rec; + cbXMTBUFF* xmt; + cbXMTBUFFLOCAL* xmt_local; + cbPcStatus* status; + cbSPKBUFF* spike; + + void fromLegacy(::cbPKT_HEADER& cur, const cbPKT_HEADER& leg) const; + void fromLegacy(::cbPKT_SYSINFO& cur, const cbPKT_SYSINFO& leg) const; + void fromLegacy(::cbPKT_PROCINFO& cur, const cbPKT_PROCINFO& leg) const; + void fromLegacy(::cbPKT_BANKINFO& cur, const cbPKT_BANKINFO& leg) const; + void fromLegacy(::cbPKT_GROUPINFO& cur, const cbPKT_GROUPINFO& leg) const; + void fromLegacy(::cbPKT_FILTINFO& cur, const cbPKT_FILTINFO& leg) const; + void fromLegacy(::cbPKT_ADAPTFILTINFO& cur, const cbPKT_ADAPTFILTINFO& leg) const; + void fromLegacy(::cbPKT_REFELECFILTINFO& cur, const cbPKT_REFELECFILTINFO& leg) const; + void fromLegacy(::cbSCALING& cur, const cbSCALING& leg) const; + void fromLegacy(::cbFILTDESC& cur, const cbFILTDESC& leg) const; + void fromLegacy(::cbMANUALUNITMAPPING& cur, const cbMANUALUNITMAPPING& leg) const; + void fromLegacy(::cbHOOP& cur, const cbHOOP& leg) const; + void fromLegacy(::cbPKT_CHANINFO& cur, const cbPKT_CHANINFO& leg) const; + void fromLegacy(::cbPKT_FS_BASIS& cur, const cbPKT_FS_BASIS& leg) const; + void fromLegacy(::cbPKT_SS_MODELSET& cur, const cbPKT_SS_MODELSET& leg) const; + void fromLegacy(::cbPKT_SS_DETECT& cur, const cbPKT_SS_DETECT& leg) const; + void fromLegacy(::cbPKT_SS_ARTIF_REJECT& cur, const cbPKT_SS_ARTIF_REJECT& leg) const; + void fromLegacy(::cbPKT_SS_NOISE_BOUNDARY& cur, const cbPKT_SS_NOISE_BOUNDARY& leg) const; + void fromLegacy(::cbPKT_SS_STATISTICS& cur, const cbPKT_SS_STATISTICS& leg) const; + void fromLegacy(::cbAdaptControl& cur, const cbAdaptControl& leg) const; + void fromLegacy(::cbPKT_SS_STATUS& cur, const cbPKT_SS_STATUS& leg) const; + void fromLegacy(::cbproto::SpikeSorting& cur, const cbSPIKE_SORTING& leg) const; + void fromLegacy(::cbPKT_NTRODEINFO& cur, const cbPKT_NTRODEINFO& leg) const; + void fromLegacy(::cbWaveformData& cur, const cbWaveformData& leg) const; + void fromLegacy(::cbPKT_AOUT_WAVEFORM& cur, const cbPKT_AOUT_WAVEFORM& leg) const; + void fromLegacy(::cbPKT_LNC& cur, const cbPKT_LNC& leg) const; + void fromLegacy(::cbPKT_NPLAY& cur, const cbPKT_NPLAY& leg) const; + void fromLegacy(::cbVIDEOSOURCE& cur, const cbVIDEOSOURCE& leg) const; + void fromLegacy(::cbTRACKOBJ& cur, const cbTRACKOBJ& leg) const; + void fromLegacy(::cbPKT_FILECFG& cur, const cbPKT_FILECFG& leg) const; + void fromLegacy(NativeConfigBuffer& cur, const cbCFGBUFF& leg) const; + void fromLegacy(NativeNSPStatus& cur, const NSPStatus& leg) const; + void fromLegacy(::cbPKT_UNIT_SELECTION& cur, const cbPKT_UNIT_SELECTION& leg) const; + void fromLegacy(NativePCStatus& cur, const cbPcStatus& leg) const; + void fromLegacy(NativeReceiveBuffer& cur, const cbRECBUFF& leg) const; + void fromLegacy(NativeTransmitBuffer& cur, const cbXMTBUFF& leg) const; + void fromLegacy(NativeTransmitBufferLocal& cur, const cbXMTBUFFLOCAL& leg) const; + void fromLegacy(::cbPKT_SPK& cur, const cbPKT_SPK& leg) const; + void fromLegacy(NativeSpikeCache& cur, const cbSPKCACHE& leg) const; + void fromLegacy(NativeSpikeBuffer& cur, const cbSPKBUFF& leg) const; + + void toLegacy(cbPKT_HEADER& leg, const ::cbPKT_HEADER& cur) const; + void toLegacy(cbPKT_SYSINFO& leg, const ::cbPKT_SYSINFO& cur) const; + void toLegacy(cbPKT_PROCINFO& leg, const ::cbPKT_PROCINFO& cur) const; + void toLegacy(cbPKT_BANKINFO& leg, const ::cbPKT_BANKINFO& cur) const; + void toLegacy(cbPKT_GROUPINFO& leg, const ::cbPKT_GROUPINFO& cur) const; + void toLegacy(cbPKT_FILTINFO& leg, const ::cbPKT_FILTINFO& cur) const; + void toLegacy(cbPKT_ADAPTFILTINFO& leg, const ::cbPKT_ADAPTFILTINFO& cur) const; + void toLegacy(cbPKT_REFELECFILTINFO& leg, const ::cbPKT_REFELECFILTINFO& cur) const; + void toLegacy(cbSCALING& leg, const ::cbSCALING& cur) const; + void toLegacy(cbFILTDESC& leg, const ::cbFILTDESC& cur) const; + void toLegacy(cbMANUALUNITMAPPING& leg, const ::cbMANUALUNITMAPPING& cur) const; + void toLegacy(cbHOOP& leg, const ::cbHOOP& cur) const; + void toLegacy(cbPKT_CHANINFO& leg, const ::cbPKT_CHANINFO& cur) const; + void toLegacy(cbPKT_FS_BASIS& leg, const ::cbPKT_FS_BASIS& cur) const; + void toLegacy(cbPKT_SS_MODELSET& leg, const ::cbPKT_SS_MODELSET& cur) const; + void toLegacy(cbPKT_SS_DETECT& leg, const ::cbPKT_SS_DETECT& cur) const; + void toLegacy(cbPKT_SS_ARTIF_REJECT& leg, const ::cbPKT_SS_ARTIF_REJECT& cur) const; + void toLegacy(cbPKT_SS_NOISE_BOUNDARY& leg, const ::cbPKT_SS_NOISE_BOUNDARY& cur) const; + void toLegacy(cbPKT_SS_STATISTICS& leg, const ::cbPKT_SS_STATISTICS& cur) const; + void toLegacy(cbAdaptControl& leg, const ::cbAdaptControl& cur) const; + void toLegacy(cbPKT_SS_STATUS& leg, const ::cbPKT_SS_STATUS& cur) const; + void toLegacy(cbSPIKE_SORTING& leg, const ::cbproto::SpikeSorting& cur) const; + void toLegacy(cbPKT_NTRODEINFO& leg, const ::cbPKT_NTRODEINFO& cur) const; + void toLegacy(cbWaveformData& leg, const ::cbWaveformData& cur) const; + void toLegacy(cbPKT_AOUT_WAVEFORM& leg, const ::cbPKT_AOUT_WAVEFORM& cur) const; + void toLegacy(cbPKT_LNC& leg, const ::cbPKT_LNC& cur) const; + void toLegacy(cbPKT_NPLAY& leg, const ::cbPKT_NPLAY& cur) const; + void toLegacy(cbVIDEOSOURCE& leg, const ::cbVIDEOSOURCE& cur) const; + void toLegacy(cbTRACKOBJ& leg, const ::cbTRACKOBJ& cur) const; + void toLegacy(cbPKT_FILECFG& leg, const ::cbPKT_FILECFG& cur) const; + void toLegacy(NSPStatus& leg, const NativeNSPStatus& cur) const; + void toLegacy(cbPKT_UNIT_SELECTION& leg, const ::cbPKT_UNIT_SELECTION& cur) const; + void toLegacy(cbRECBUFF& leg, const NativeReceiveBuffer& cur) const; + void toLegacy(cbXMTBUFF& leg, const NativeTransmitBuffer& cur) const; + void toLegacy(cbXMTBUFFLOCAL& leg, const NativeTransmitBufferLocal& cur) const; + void toLegacy(cbPKT_SPK& leg, const ::cbPKT_SPK& cur) const; + +public: + explicit Adapter(const CentralAdapterArgs& args); + ~Adapter() = default; + + // Receive buffer access + uint32_t& getRecReceived() override; + uint64_t getRecLasttime() override; + void setRecLasttime(uint64_t lasttime) override; + uint32_t& getRecHeadwrapPtr() override; + uint32_t& getRecHeadindexPtr() override; + uint32_t* getRecBufferPtr() override; + + // Transmit buffer access + uint32_t& getXmtTransmittedPtr() override; + uint32_t& getXmtHeadindexPtr() override; + uint32_t& getXmtTailindexPtr() override; + uint32_t& getXmtLastValidIndexPtr() override; + uint32_t& getXmtBufferlenPtr() override; + uint32_t* getXmtBufferPtr() override; + + uint32_t& getLocalXmtTransmittedPtr() override; + uint32_t& getLocalXmtHeadindexPtr() override; + uint32_t& getLocalXmtTailindexPtr() override; + uint32_t& getLocalXmtLastValidIndexPtr() override; + uint32_t& getLocalXmtBufferlenPtr() override; + uint32_t* getLocalXmtBufferPtr() override; + + /// Config read operations + cbutil::Result getProcInfo(::cbPKT_PROCINFO& buf) const override; + cbutil::Result getBankInfo(::cbPKT_BANKINFO& buf, uint32_t bank_num) const override; + cbutil::Result getFilterInfo(::cbPKT_FILTINFO& buf, uint32_t filter_num) const override; + cbutil::Result getChanInfo(::cbPKT_CHANINFO& buf, uint32_t channel_idx) const override; + cbutil::Result getSysInfo(::cbPKT_SYSINFO& buf) const override; + cbutil::Result getGroupInfo(::cbPKT_GROUPINFO& buf, uint32_t group_idx) const override; + cbutil::Result getConfigBuffer(NativeConfigBuffer& buf) const override; + cbutil::Result getPcStatus(NativePCStatus& buf) const override; + cbutil::Result getSpikeCache(NativeSpikeCache& buf, uint32_t channel_idx) const override; + + /// Config write operations + cbutil::Result setProcInfo(const ::cbPKT_PROCINFO& info) override; + cbutil::Result setBankInfo(uint32_t bank_num, const ::cbPKT_BANKINFO& info) override; + cbutil::Result setFilterInfo(uint32_t filter_num, const ::cbPKT_FILTINFO& info) override; + cbutil::Result setChanInfo(uint32_t channel_idx, const ::cbPKT_CHANINFO& info) override; + cbutil::Result setSysInfo(const ::cbPKT_SYSINFO& info) override; + cbutil::Result setGroupInfo(uint32_t group_idx, const ::cbPKT_GROUPINFO& info) override; + cbutil::Result setNspStatus(const NativeNSPStatus& status) const override; + cbutil::Result setGeminiSystem(bool is_gemini) const override; +}; + +} // namespace central_v7_6 + +} // namespace cbshm + +#endif // CBSHM_CENTRAL_ADAPTERS_V7_6_H diff --git a/src/cbshm/include/cbshm/central_adapters/v7_7.h b/src/cbshm/include/cbshm/central_adapters/v7_7.h new file mode 100644 index 00000000..b53e3d4c --- /dev/null +++ b/src/cbshm/include/cbshm/central_adapters/v7_7.h @@ -0,0 +1,188 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////// +/// @file v7_7.h +/// @author Caden Shmookler +/// @date 2026-05-22 +/// +/// @brief Adapter for Central-compatible shared memory access +/// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef CBSHM_CENTRAL_ADAPTERS_V7_7_H +#define CBSHM_CENTRAL_ADAPTERS_V7_7_H + +#include +#include + +namespace cbshm { + +namespace central_v7_7 { + +/// +/// @brief Adapter that provides information for fetching pointers to Central's shared memory +/// +class BootstrapAdapter : public ::cbshm::CentralBootstrapAdapterBase { +public: + // Max instrument count + uint32_t getMaxProcs() const override; + + // Buffer sizes + size_t getConfigBufferSize() const override; + size_t getReceiveBufferSize() const override; + size_t getTransmitBufferSize() const override; + size_t getTransmitBufferLocalSize() const override; + size_t getStatusBufferSize() const override; + size_t getSpikeBufferSize() const override; + size_t getReceiveBufferLen() const override; + size_t getReceiveReserveLen() const override; + size_t getTransmitBufferLen() const override; + size_t getTransmitBufferLocalLen() const override; + + // Adapter construction + std::unique_ptr makeAdapter(const CentralAdapterArgs& args) const override; +}; + +/// +/// @brief Adapter for Central-compatible shared memory access +/// +class Adapter : public CentralAdapterBase { +private: + uint8_t instrument_idx; + cbCFGBUFF* cfg; + cbRECBUFF* rec; + cbXMTBUFF* xmt; + cbXMTBUFFLOCAL* xmt_local; + cbPcStatus* status; + cbSPKBUFF* spike; + + void fromLegacy(::cbPKT_HEADER& cur, const cbPKT_HEADER& leg) const; + void fromLegacy(::cbPKT_SYSINFO& cur, const cbPKT_SYSINFO& leg) const; + void fromLegacy(::cbPKT_PROCINFO& cur, const cbPKT_PROCINFO& leg) const; + void fromLegacy(::cbPKT_BANKINFO& cur, const cbPKT_BANKINFO& leg) const; + void fromLegacy(::cbPKT_GROUPINFO& cur, const cbPKT_GROUPINFO& leg) const; + void fromLegacy(::cbPKT_FILTINFO& cur, const cbPKT_FILTINFO& leg) const; + void fromLegacy(::cbPKT_ADAPTFILTINFO& cur, const cbPKT_ADAPTFILTINFO& leg) const; + void fromLegacy(::cbPKT_REFELECFILTINFO& cur, const cbPKT_REFELECFILTINFO& leg) const; + void fromLegacy(::cbSCALING& cur, const cbSCALING& leg) const; + void fromLegacy(::cbFILTDESC& cur, const cbFILTDESC& leg) const; + void fromLegacy(::cbMANUALUNITMAPPING& cur, const cbMANUALUNITMAPPING& leg) const; + void fromLegacy(::cbHOOP& cur, const cbHOOP& leg) const; + void fromLegacy(::cbPKT_CHANINFO& cur, const cbPKT_CHANINFO& leg) const; + void fromLegacy(::cbPKT_FS_BASIS& cur, const cbPKT_FS_BASIS& leg) const; + void fromLegacy(::cbPKT_SS_MODELSET& cur, const cbPKT_SS_MODELSET& leg) const; + void fromLegacy(::cbPKT_SS_DETECT& cur, const cbPKT_SS_DETECT& leg) const; + void fromLegacy(::cbPKT_SS_ARTIF_REJECT& cur, const cbPKT_SS_ARTIF_REJECT& leg) const; + void fromLegacy(::cbPKT_SS_NOISE_BOUNDARY& cur, const cbPKT_SS_NOISE_BOUNDARY& leg) const; + void fromLegacy(::cbPKT_SS_STATISTICS& cur, const cbPKT_SS_STATISTICS& leg) const; + void fromLegacy(::cbAdaptControl& cur, const cbAdaptControl& leg) const; + void fromLegacy(::cbPKT_SS_STATUS& cur, const cbPKT_SS_STATUS& leg) const; + void fromLegacy(::cbproto::SpikeSorting& cur, const cbSPIKE_SORTING& leg) const; + void fromLegacy(::cbPKT_NTRODEINFO& cur, const cbPKT_NTRODEINFO& leg) const; + void fromLegacy(::cbWaveformData& cur, const cbWaveformData& leg) const; + void fromLegacy(::cbPKT_AOUT_WAVEFORM& cur, const cbPKT_AOUT_WAVEFORM& leg) const; + void fromLegacy(::cbPKT_LNC& cur, const cbPKT_LNC& leg) const; + void fromLegacy(::cbPKT_NPLAY& cur, const cbPKT_NPLAY& leg) const; + void fromLegacy(::cbVIDEOSOURCE& cur, const cbVIDEOSOURCE& leg) const; + void fromLegacy(::cbTRACKOBJ& cur, const cbTRACKOBJ& leg) const; + void fromLegacy(::cbPKT_FILECFG& cur, const cbPKT_FILECFG& leg) const; + void fromLegacy(NativeConfigBuffer& cur, const cbCFGBUFF& leg) const; + void fromLegacy(NativeNSPStatus& cur, const NSPStatus& leg) const; + void fromLegacy(::cbPKT_UNIT_SELECTION& cur, const cbPKT_UNIT_SELECTION& leg) const; + void fromLegacy(NativePCStatus& cur, const cbPcStatus& leg) const; + void fromLegacy(NativeReceiveBuffer& cur, const cbRECBUFF& leg) const; + void fromLegacy(NativeTransmitBuffer& cur, const cbXMTBUFF& leg) const; + void fromLegacy(NativeTransmitBufferLocal& cur, const cbXMTBUFFLOCAL& leg) const; + void fromLegacy(::cbPKT_SPK& cur, const cbPKT_SPK& leg) const; + void fromLegacy(NativeSpikeCache& cur, const cbSPKCACHE& leg) const; + void fromLegacy(NativeSpikeBuffer& cur, const cbSPKBUFF& leg) const; + + void toLegacy(cbPKT_HEADER& leg, const ::cbPKT_HEADER& cur) const; + void toLegacy(cbPKT_SYSINFO& leg, const ::cbPKT_SYSINFO& cur) const; + void toLegacy(cbPKT_PROCINFO& leg, const ::cbPKT_PROCINFO& cur) const; + void toLegacy(cbPKT_BANKINFO& leg, const ::cbPKT_BANKINFO& cur) const; + void toLegacy(cbPKT_GROUPINFO& leg, const ::cbPKT_GROUPINFO& cur) const; + void toLegacy(cbPKT_FILTINFO& leg, const ::cbPKT_FILTINFO& cur) const; + void toLegacy(cbPKT_ADAPTFILTINFO& leg, const ::cbPKT_ADAPTFILTINFO& cur) const; + void toLegacy(cbPKT_REFELECFILTINFO& leg, const ::cbPKT_REFELECFILTINFO& cur) const; + void toLegacy(cbSCALING& leg, const ::cbSCALING& cur) const; + void toLegacy(cbFILTDESC& leg, const ::cbFILTDESC& cur) const; + void toLegacy(cbMANUALUNITMAPPING& leg, const ::cbMANUALUNITMAPPING& cur) const; + void toLegacy(cbHOOP& leg, const ::cbHOOP& cur) const; + void toLegacy(cbPKT_CHANINFO& leg, const ::cbPKT_CHANINFO& cur) const; + void toLegacy(cbPKT_FS_BASIS& leg, const ::cbPKT_FS_BASIS& cur) const; + void toLegacy(cbPKT_SS_MODELSET& leg, const ::cbPKT_SS_MODELSET& cur) const; + void toLegacy(cbPKT_SS_DETECT& leg, const ::cbPKT_SS_DETECT& cur) const; + void toLegacy(cbPKT_SS_ARTIF_REJECT& leg, const ::cbPKT_SS_ARTIF_REJECT& cur) const; + void toLegacy(cbPKT_SS_NOISE_BOUNDARY& leg, const ::cbPKT_SS_NOISE_BOUNDARY& cur) const; + void toLegacy(cbPKT_SS_STATISTICS& leg, const ::cbPKT_SS_STATISTICS& cur) const; + void toLegacy(cbAdaptControl& leg, const ::cbAdaptControl& cur) const; + void toLegacy(cbPKT_SS_STATUS& leg, const ::cbPKT_SS_STATUS& cur) const; + void toLegacy(cbSPIKE_SORTING& leg, const ::cbproto::SpikeSorting& cur) const; + void toLegacy(cbPKT_NTRODEINFO& leg, const ::cbPKT_NTRODEINFO& cur) const; + void toLegacy(cbWaveformData& leg, const ::cbWaveformData& cur) const; + void toLegacy(cbPKT_AOUT_WAVEFORM& leg, const ::cbPKT_AOUT_WAVEFORM& cur) const; + void toLegacy(cbPKT_LNC& leg, const ::cbPKT_LNC& cur) const; + void toLegacy(cbPKT_NPLAY& leg, const ::cbPKT_NPLAY& cur) const; + void toLegacy(cbVIDEOSOURCE& leg, const ::cbVIDEOSOURCE& cur) const; + void toLegacy(cbTRACKOBJ& leg, const ::cbTRACKOBJ& cur) const; + void toLegacy(cbPKT_FILECFG& leg, const ::cbPKT_FILECFG& cur) const; + void toLegacy(NSPStatus& leg, const NativeNSPStatus& cur) const; + void toLegacy(cbPKT_UNIT_SELECTION& leg, const ::cbPKT_UNIT_SELECTION& cur) const; + void toLegacy(cbRECBUFF& leg, const NativeReceiveBuffer& cur) const; + void toLegacy(cbXMTBUFF& leg, const NativeTransmitBuffer& cur) const; + void toLegacy(cbXMTBUFFLOCAL& leg, const NativeTransmitBufferLocal& cur) const; + void toLegacy(cbPKT_SPK& leg, const ::cbPKT_SPK& cur) const; + +public: + explicit Adapter(const CentralAdapterArgs& args); + ~Adapter() = default; + + // Receive buffer access + uint32_t& getRecReceived() override; + uint64_t getRecLasttime() override; + void setRecLasttime(uint64_t lasttime) override; + uint32_t& getRecHeadwrapPtr() override; + uint32_t& getRecHeadindexPtr() override; + uint32_t* getRecBufferPtr() override; + + // Transmit buffer access + uint32_t& getXmtTransmittedPtr() override; + uint32_t& getXmtHeadindexPtr() override; + uint32_t& getXmtTailindexPtr() override; + uint32_t& getXmtLastValidIndexPtr() override; + uint32_t& getXmtBufferlenPtr() override; + uint32_t* getXmtBufferPtr() override; + + uint32_t& getLocalXmtTransmittedPtr() override; + uint32_t& getLocalXmtHeadindexPtr() override; + uint32_t& getLocalXmtTailindexPtr() override; + uint32_t& getLocalXmtLastValidIndexPtr() override; + uint32_t& getLocalXmtBufferlenPtr() override; + uint32_t* getLocalXmtBufferPtr() override; + + /// Config read operations + cbutil::Result getProcInfo(::cbPKT_PROCINFO& buf) const override; + cbutil::Result getBankInfo(::cbPKT_BANKINFO& buf, uint32_t bank_num) const override; + cbutil::Result