Native OTB device sources (Muovi/Muovi+ & Quattrocento)#6
Open
RaulSimpetru wants to merge 28 commits into
Open
Native OTB device sources (Muovi/Muovi+ & Quattrocento)#6RaulSimpetru wants to merge 28 commits into
RaulSimpetru wants to merge 28 commits into
Conversation
The Rust lsl-dummy-stream (Zarr-replay tool from the separate lsl-recording-toolbox repo) is not a MyoGestic dependency — nothing in the package, examples, tests, or docs references it. MyoGestic's EMG simulator is the cross-platform Python myogestic.tools.emg_generator. Ignore the path so the platform-specific binary can't be committed by accident.
Pure-Python (no Qt) Source implementations for OT Bioelettronica devices, re-adding pre-2.0 hardware connectivity. Captures the exact wire protocol (socket roles, config bytes, frame formats, endianness, conversion factors, Quattrocento CRC-8) extracted from biosignal-device-interface, plus the shared-base architecture, data contract, GUI integration, and a phased hardware-validated test plan.
- Conversion factor: use the physically-correct gain->resolution mapping (gain-8 = 286.1 nV finer, gain-4 = 572.2 nV); bdi's dict had these swapped. OTB's protocol PDF is not public, but the 8:4 gain ratio settles it. - Promote an in-GUI device-config panel into scope: a source-agnostic config_spec()/set_config() optional Protocol extension + a generic device_config ImGui widget (Apply & Connect via reconnect()), plus a minimal manual-connect acquire-loop change behind a per-stream flag.
Cross-checked §5.1 against OTB Muovi TCP Protocol v2.4, SyncStation TCP Protocol v2.8, and MuoviPro User Manual v5.1: - Replace bdi's control-byte formula with the OFFICIAL layout (emg_eeg<<3 | mode<<1 | go); bdi encoded it differently. - Conversion factors confirmed (gain-8 286.1 nV, gain-4 572.2 nV) + input ranges + firmware HPF (Fsamp/190). - Socket role confirmed (PC = TCP server :54321; AP/DHCP detail; ~1400-byte TCP chunks). - Name the 6 aux channels (IMU quaternion WXYZ, buffer usage, sample counter). - Add §11 SyncStation multi-probe path (PC=client 192.168.76.1:54320, CRC-framed command strings) as a future addition on the same base. - Flag Quattrocento (§5.2) as bdi-sourced/provisional pending its OTB PDF, and note TEST-mode ramps as the first-connect endianness/byte-layout check.
MuoviLite User Manual v1.1 closes the last open items: - Byte order EXPLICITLY confirmed big-endian (§8.1.2) for both 16/24-bit; no longer an open question. - LSB = ADC_RANGE/2^24 = 286.1 nV confirms the gain-8 factor; 16-bit EMG keeps that LSB (range 18.75 mV). - All 38 channels (bio + 6 aux) share the active resolution (16-bit EMG / 24-bit EEG) -> decoder reads one uniform width per frame. - Channel map: 33-36 IMU quaternion WXYZ (BNO055 NDOF, 100 Hz held), 37 buffer usage + trigger, 38 sample counter; emit aux unscaled when include_aux. - Note OTB MATLAB demo code as the canonical decode reference.
Cross-checked §5.1/§11 against OTB MATLAB Read_muovi.m v3.0, Read_muoviAP.m v2.0, and CRC8.m: - Command = EMG*8 + Mode*2 + 1 and stop = Command-1 confirm the official control byte (bdi superseded); ConvFact 0.000286, tcpserver big-endian. - Mode table corrected: channel count varies by mode (NumChanVsMode=[38 22 38 38]); Mode 1 = "16-ch" in MATLAB v3.0 vs "gain 4" in PDF v2.4 -> firmware- dependent, avoid in v1. StreamInfo.n_channels must derive from (device, mode). - ch37 trigger/buffer unpacking (bit15 trigger, low 15 buffer); ch38 counter. - SyncStation framing confirmed: START=(SIZE<<1)|GO, per-device (DEV<<4)|(EMG<<3)|(Mode<<1)|EN, CRC8 trailer, stop=[0,CRC8]. - Plan to vendor the .m references into docs/reference/otb/ during build.
Reference-only (not imported/shipped) manufacturer example scripts that the native OTB sources will mirror: Read_muovi.m (direct probe), Read_muoviAP.m (SyncStation), CRC8.m, plus a README distilling the verified protocol facts.
Cross-checked §5.2 against Quattrocento Configuration Protocol v1.7 and OTB MATLAB Read_Quattrocento.m v5.0 (+ CRC8.m, identical poly 0x8C): - ACQ_SETT bit layout confirmed; per-input 3-byte config (CONF0 muscle / CONF1 sensor+adapter / CONF2 side|hpf|lpf|mode) with 0-based bit values (mode 00=mono/01=diff/10=bipolar; default CONF2=0x14). - TCP client 169.254.1.10:23456, little-endian int16 confirmed. - Channel counts 120/216/312/408; accessory channels are the last 8 (counter @ n-7, trigger @ n-6, buffer @ n-4); conversion factors confirmed (bio 5.086e-4 mV, AUX IN 1.526e-4 V, accessory raw). - Stop = byte0 0x80 + recomputed CRC (always stop before reconnect). - §5.2/§10 promoted from provisional to manufacturer-verified. - Vendor Read_Quattrocento.m, FourMapsQuattrocento.m, ReadFromOTBiolabLightQuattrocento.m into docs/reference/otb/.
TDD, bite-sized tasks: CRC8, pure decoders (big/little-endian, int24), device constants + command builders, shared _OTBSource base (socket buffering/framing/timestamps), MuoviSource (TCP server) + Quattrocento (TCP client), loopback tests over real sockets, example + how-to docs. GUI device-config, SyncStation, and Sessantaquattro deferred to follow-up plans.
Wrap the fake-probe thread body so sendall/recv after the source closes the socket no longer print a spurious traceback. No change to assertions.
Code-quality review fix: the crc8 re-export was imported mid-file with a suppressed E402; _crc is a leaf module with no circular-import risk, so it belongs with the top-of-file imports.
…ames) - H2: empty recv (peer close) now drops the socket after flushing buffered frames, so the acquire loop no longer spins on a dead connection. - H3: connect()/accept_and_start() clean up the socket if _send_start raises. - H1: fix off-by-one in Quattrocento accessory channel names (counter/trigger/ buffer were one position too high vs Read_Quattrocento.m RampChan/BuffChan). - M2/M4: close any stale server/client socket on re-listen / reconnect. - M1/L1: fix misleading loopback test comment; move example import to top. - Add regression tests for the peer-close and start-failure paths.
…g (Codex) Independent Codex review caught two real bugs prior reviews missed: - Quattrocento default treated the 16 AUX IN channels as biosignal and scaled them with the EMG mV factor. Default bio is now per-mode 96/192/288/384 (= streamed - 16 AUX - 8 accessory); include_aux scales AUX IN to V and emits the 8 accessory channels raw. Channel names label bio/aux/accessory correctly. - _base.read() recv() OSError now drops the socket (and flushes buffered frames) instead of polling a dead connection forever. Tests: add Quattrocento partition + AUX-scaling + biosignal-only cases, an OSError-drop case, and assert the Muovi start control byte (0x09) in the loopback. Docs updated with default biosignal counts. 25 OTB tests pass.
Muovi+ is identical to Muovi but with 64 biosignal channels (same 6 aux, control byte, and big-endian format) -> 70 total / 38 for the 32-ch mode. The 68/36 figures only arise via the SyncStation path (§11), which packs channels differently. Resolves the prior "direct Muovi+ geometry unverified" note; implementation already matched (muovi_geometry(plus=True) -> 64+6=70).
c45bdb3 to
b15baa9
Compare
Merged main (PR #8: library reorg, naming standardization, ruff D / ty-clean, Windows session fix, doc/example test harness) into the OTB device-sources branch and brought the PR up to date: - API: window_seconds= -> window_ms= in the OTB example + connect-otb-devices.md (StreamInfo/Source usage was unchanged, so the sources needed no edits). - ruff (new D + rules now apply to OTB code): package docstring for sources/otb, D400 period fix in _constants, disconnect() docstring in muovi; gave tests the same E402 import-placement latitude examples already have; dropped an unused import. - ty (repo is ty-clean now): narrowed the socket/StreamInfo Optionals in _base/muovi/quattrocento (local-bind in read(); asserts before use) — no behaviour change. - test_docs harness: handle code blocks nested in list items (indented closing fence + dedent) so connect-otb-devices.md parses. ruff + ty clean, 337 tests, docs 0 warnings. Version stays 2.0.2.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Re-adds the ability to connect to OT Bioelettronica EMG hardware directly from MyoGestic 2.0 — a capability that existed pre-2.0 (via the Qt
biosignal-device-interfacelibrary) but was dropped in the ground-up rewrite.Implemented as native, pure-Python (no Qt) acquisition
Sourceclasses undermyogestic/sources/otb/:MuoviSource(Muovi / Muovi+) — PC is the TCP server on:54321; big-endian int16 (EMG) / int24 (EEG); 286.1 nV/LSB. Muovi+ = identical to Muovi with 64 biosignal channels (same 6 aux) → 70 total.QuattrocentoSource— PC is the TCP client to169.254.1.10:23456; little-endian int16; 40-byte CRC-8 config; 120/216/312/408 channels (96/192/288/384 biosignal).Both fit the existing pull-based
Sourceprotocol (connect → StreamInfo, non-blockingread → (data, ts),disconnect), so they drop straight into aStream. No new runtime dependencies (stdlibsocket+ numpy).Protocol verification
Cross-checked against manufacturer PDFs and OT Bioelettronica's own MATLAB reference code (vendored, reference-only, under
docs/reference/otb/):Read_muovi.m,Read_Quattrocento.m,CRC8.m.(EMG<<3)|(mode<<1)|GO.Review
recv()error-path socket-cleanup gap.Tests
test_interfaces.py/test_public_api.pyare unrelated (stale since v2.0.0) and fixed in Fix stale VHI interface + public-API tests (pre-existing, since v2.0.0) #7.Notes
chore: gitignore stray lsl-dummy-stream binarycommit that rode along from the same working session.docs/how-to/connect-otb-devices.md, exampleexamples/otb/muovi_emg.py, design spec + plan underdocs/superpowers/.