Add Stanford Research SR830 lock-in amplifier driver over Prologix GPIB#112
Merged
Conversation
Problem: The DAQ family had no capability contract for lock-in (phase-locked) detection or for triggered acquisition, so a lock-in driver would have to bolt ad-hoc methods onto one class. The stream sample-rate parameter was named scanRate, and importing hardwarelibrary.daq eagerly imported u3 (LabJackPython), so the whole package failed to import on hosts without the LabJack driver. Solution: - Add PhaseLockedDetectionDevice (X/Y/R/theta, reference frequency, input source, sensitivity, time constant, all in physical units) and TriggerableDevice (setTriggerSource/getTriggerSource/softwareTrigger) with the TriggerSource and SampleClock enums, following the interface-segregated mixin convention. - Rename the stream sample-rate parameter scanRate -> sampleRate on AnalogInputStreamDevice.configureStream/acquireWaveform. LabjackDevice keeps scanRate as a temporary deprecated synonym. - Defer the u3 import in labjackdevice to point of use so the daq package imports without LabJackPython installed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T2DmAfLHSiK1fb3PiXDyJP
Problem: The library had no way to reach a GPIB (IEEE-488) instrument. The common lab bridge is a Prologix GPIB-USB controller, which enumerates as a generic FTDI serial port and speaks a "++" controller protocol on top of the serial link. Solution: Add PrologixGPIBPort, a SerialPort subclass that reuses the FTDI discovery and read/write primitives and adds only the controller handshake in open() (++mode 1 / ++addr / ++auto 0 / ++eoi 1 / ++eos 2 / ++eot_enable 0 / ++read_tmo_ms). Manual-read mode (++auto 0) is used because auto mode addresses the instrument to talk after every command, including no-reply commands, wedging the bus; the required ++read eoi is encapsulated in readString so instruments talk to the port with the ordinary readString/writeString primitives. Export it from the communication package. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T2DmAfLHSiK1fb3PiXDyJP
Problem: The lab's SR830 DSP lock-in amplifier, reached over a Prologix GPIB-USB adaptor, had no driver in PyHardwareLibrary. Solution: Add SR830Device (and DebugSR830Device) combining PhysicalDevice, AnalogInputStreamDevice, AnalogOutputDevice, PhaseLockedDetectionDevice, and TriggerableDevice: - Aux A/D inputs (OAUX?) via getAnalogVoltage(AuxInput); Aux D/A outputs (AUXV) via setAnalogVoltage(value, AuxOutput). - Demodulated reads X/Y/R/theta (OUTP?/SNAP?), reference frequency (FREQ?), input source (ISRC), sensitivity (SENS) and time constant (OFLT) in physical units with nearest-step snapping. - Buffered acquisition of the internal data buffer (DDEF/SRAT/SEND/REST/STRT/ PAUS/SPTS?/TRCA?) via the AnalogInputStreamDevice primitives, with a StreamChannel enum and a SampleClock (internal rate vs external per-edge). - Trigger via TSTR (setTriggerSource) and TRIG (softwareTrigger). - doInitializeDevice self-discovers the Prologix among the connected FTDI adaptors by confirming *IDN? contains SR830, and pins the adaptor serial for reconnects. - DebugSR830Device swaps in a DebugPrologixGPIBPort (an in-memory CommunicationPort that follows the readData/writeData structure) so all parse/validate paths run without hardware. Verified against the lab SR830 (s/n 86552): tests/testSR830.py has 43 tests, including 8 that talk to the real instrument (reads, aux out round-trip, buffered acquisition, trigger) and skip cleanly when no SR830 is attached. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T2DmAfLHSiK1fb3PiXDyJP
Problem: several methods added in this branch lacked docstrings, and a few method-level explanatory comments read as inline "what this does" comments rather than the docstrings the project style calls for. Solution: add concise docstrings to every method and capability contract added in this branch (PrologixGPIBPort, SR830Device/DebugSR830Device, DebugPrologixGPIBPort, and the new PhaseLockedDetectionDevice/TriggerableDevice abstract hooks and touched AnalogInputStreamDevice methods). Method-level comments explaining behavior are converted to docstrings; genuine "why" comments inside method bodies are kept inline. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T2DmAfLHSiK1fb3PiXDyJP
Problem: SR830Device.doInitializeDevice and _candidateAdaptors (the FTDI adaptor probe/confirm/pin logic) were only exercised on real hardware, because DebugSR830Device overrides doInitializeDevice. Several defensive branches (unsupported-enum guards, the sensitivity clamp, base-capability defaults, and DebugPrologixGPIBPort conveniences) were also untested, and the Prologix handshake test asserted the pre-fix command set without exercising the real port. Solution: add hardware-free tests. TestSR830Discovery patches comports() and PrologixGPIBPort to cover discovery, serial pinning, serial-number narrowing, explicit portPath, and both failure raises. Add TestDebugPrologixGPIBPort for the port primitives, TestCapabilityDefaults for the base optional hooks and base getDemodulatedValues, and debug-device tests for the enum guards, the sensitivity clamp, supportedSampleRates, and the aux/frequency SNAP codes. Rewrite the handshake test to exercise the real configureController and assert the manual-read (++auto 0, ++eot_enable 0) handshake. This takes sr830device.py and prologixgpibport.py to 100% line coverage (62 tests, all passing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T2DmAfLHSiK1fb3PiXDyJP
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.
Summary
Adds a driver for the Stanford Research Systems SR830 DSP lock-in amplifier, reached over a Prologix GPIB-USB controller. Three reusable pieces result: a GPIB transport, new DAQ capability contracts, and the driver itself. Verified against the lab SR830 (s/n 86552).
What's included
PrologixGPIBPort(communication/prologixgpibport.py) — aSerialPortsubclass speaking the Prologix++controller protocol. GPIB instruments talk to it with the ordinaryreadString/writeStringprimitives; it overrides onlyopen()(controller handshake) andreadString()(the++read eoistep). Uses++auto 0+ explicit reads and++eot_enable 0because the SR830 already terminates replies with CR-LF.New DAQ capability contracts (
daq/daqdevice.py), following the interface-segregated mixin convention:PhaseLockedDetectionDevice— X/Y/R/theta, reference frequency, input source, sensitivity, time constant, all in physical units with driver-side snapping to hardware steps. Includes theInputSourceenum.TriggerableDevice—setTriggerSource/getTriggerSource/softwareTrigger, with theTriggerSourceenum.SampleClockenum for stream sample clocking (distinct from triggering).SR830Device/DebugSR830Device(daq/sr830device.py) — combinesAnalogInputStreamDevice(four rear-panel Aux inputs viaOAUX?, plus hardware-timed buffered acquisition of the demodulated outputs),AnalogOutputDevice(four Aux outputs viaAUXV),PhaseLockedDetectionDevice, andTriggerableDevice. Enums:AuxInput,AuxOutput,StreamChannel.doInitializeDeviceself-discovers the Prologix among connected FTDI adaptors by confirming*IDN?returns an SR830, then pins the adaptor serial.Changed (API-affecting)
AnalogInputStreamDevice: the sample-rate parameter ofconfigureStream/acquireWaveformis renamedscanRate->sampleRate.LabjackDevice.configureStreamkeepsscanRateas a temporary deprecated synonym.LabjackDeviceimportsu3lazily soimport hardwarelibrary.daqworks without LabJackPython.Testing
pytest hardwarelibrary/tests/testSR830.py— 43 passed (debug/contract/port tests always run; 8 hardware tests skip cleanly when no SR830 is attached, and were run green against the real instrument).🤖 Generated with Claude Code
https://claude.ai/code/session_01T2DmAfLHSiK1fb3PiXDyJP