hardwarelibrary 2.0.0__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/.claude/skills/pyhardwarelibrary/SKILL.md +20 -7
- hardwarelibrary-2.2.0/CHANGELOG.md +476 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/CLAUDE.md +21 -4
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/PKG-INFO +223 -43
- {hardwarelibrary-2.0.0/docs/source → hardwarelibrary-2.2.0}/README.md +222 -42
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0/docs/source}/README.md +222 -42
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/__main__.py +30 -0
- hardwarelibrary-2.2.0/hardwarelibrary/capabilities.py +1366 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/debugport.py +81 -1
- hardwarelibrary-2.2.0/hardwarelibrary/communication/protocol.py +1425 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/usbport.py +72 -4
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/daq/labjackdevice.py +21 -21
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/daq/sr830device.py +25 -25
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/motion/linearmotiondevice.py +16 -35
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/motion/rotationdevice.py +14 -11
- hardwarelibrary-2.2.0/hardwarelibrary/motion/sutterdevice.py +185 -0
- hardwarelibrary-2.2.0/hardwarelibrary/physicaldevice.py +494 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/powermeters/powermeterdevice.py +7 -4
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/powerstrips/pwrusb.py +0 -8
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/spectrometers/base.py +31 -5
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/spectrometers/oceaninsight.py +2 -2
- hardwarelibrary-2.2.0/hardwarelibrary/tests/testCapabilities.py +591 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testFieldMasterDevice.py +2 -2
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testOISpectrometer.py +5 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testPhysicalDevice.py +8 -2
- hardwarelibrary-2.2.0/hardwarelibrary/tests/testProtocolDebugPort.py +167 -0
- hardwarelibrary-2.2.0/hardwarelibrary/tests/testProtocolPrototype.py +879 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testSR830.py +20 -15
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testSutterDevice.py +11 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testSutterSerialPort.py +9 -4
- hardwarelibrary-2.2.0/hardwarelibrary/tests/testValidation.py +119 -0
- hardwarelibrary-2.2.0/hardwarelibrary/validation.py +110 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary.egg-info/PKG-INFO +223 -43
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary.egg-info/SOURCES.txt +6 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary.egg-info/scm_file_list.json +172 -166
- hardwarelibrary-2.2.0/hardwarelibrary.egg-info/scm_version.json +8 -0
- hardwarelibrary-2.0.0/CHANGELOG.md +0 -255
- hardwarelibrary-2.0.0/hardwarelibrary/capabilities.py +0 -677
- hardwarelibrary-2.0.0/hardwarelibrary/motion/sutterdevice.py +0 -145
- hardwarelibrary-2.0.0/hardwarelibrary/physicaldevice.py +0 -264
- hardwarelibrary-2.0.0/hardwarelibrary.egg-info/scm_version.json +0 -8
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/.gitattributes +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/.github/workflows/publish.yml +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/.github/workflows/tests.yml +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/.gitignore +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/.readthedocs.yaml +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README-1-USB.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README-2-RS232.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README-3-USB-Cameras.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README-4-New-device-coding-example.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README-5-Communication-ports.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README-6-DebugPort.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README-7-Sutter-ROE-200.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README.assets/Am.png +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README.assets/asiZwo.png +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README.assets/automation.png +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README.assets/image-20210415195013040.png +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README.assets/image-20210415195905341.png +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README.assets/image-20210417140020797.png +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README.assets/image-20210423112830851.png +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README.assets/image-20210423112945687.png +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README.assets/image-20210423113821265.png +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README.assets/image-20210423113954823.png +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README.assets/integra.png +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README.assets/integra.png.sb-90e26077-lWIRgO +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/README.assets/preview.png +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/SCPI.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/Makefile +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/make.bat +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/README-1-USB.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/README-2-RS232.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/README-3-USB-Cameras.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/README-4-New-device-coding-example.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/README-5-Communication-ports.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/README-6-DebugPort.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/README-7-Sutter-ROE-200.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/_static/.gitkeep +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/api/cameras.rst +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/api/communication.rst +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/api/core.rst +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/api/daq.rst +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/api/motion.rst +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/api/oscilloscope.rst +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/api/powermeters.rst +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/api/sources.rst +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/api/spectrometers.rst +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/conf.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/docs/source/index.rst +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/examples/1-simple/cobolt.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/examples/2-class/cobolt.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/examples/3-class+debugPort/cobolt.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/examples/verdig/README.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/examples/verdig/status_monitor_dll.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/examples/verdig/temperature_monitor_native.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/.idea/inspectionProfiles/profiles_settings.xml +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/.idea/vcs.xml +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/.idea/workspace.xml +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/__init__.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/cameras/__init__.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/cameras/camera.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/.idea/.gitignore +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/.idea/communication.iml +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/.idea/inspectionProfiles/profiles_settings.xml +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/.idea/misc.xml +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/.idea/modules.xml +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/.idea/vcs.xml +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/__init__.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/commands.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/communicationport.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/diagnostics.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/hidport.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/labviewtcpport.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/libusb/Darwin/libusb-1.0.0.dylib +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/libusb/MS32/libusb-1.0.dll +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/libusb/MS64/libusb-1.0.dll +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/prologixgpibport.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/serialport.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/communication/tcpport.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/daq/__init__.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/devicecontroller.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/devicemanager.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/echodevice.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/Cobolt-owners-manual-0401-D0105-A.pdf +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/Coherent-HOPS-1-Overview-and-Runbook.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/Coherent-HOPS-2-USB-and-DLL-Protocol.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/Coherent-HOPS-3-I2C-Circuit.png +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/Coherent-HOPS-3-I2C-Circuit.svg +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/Coherent-HOPS-3-I2C-Wire-Protocol.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/Coherent_FieldMaster_GS_Manual_20199913940.pdf +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/HR2000+-OEM-Data-Sheet.pdf +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/MPC-325_OpMan.pdf +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/Matisse Programmer's Guide v2.4.8.pdf +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/Mightex CCD Line Camera Din8 Connector Description.pdf +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/Mightex CCD Linear Camera User Manual.pdf +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/Sirah-Matisse-Serial-Commands.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/Spectra-Physics-Millennia-eV-Serial-Commands.md +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/TDS 1000B and TDS 2000B Manual.pdf +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/Tektronix TDS-1002 Programming manual.pdf +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/USB Primer.pdf +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/USB2000+-OEM-Data-Sheet.pdf +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/USB2000-OEM-Data-Sheet.pdf +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/User_Manual_Integra_V3.0.pdf +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/manuals/usb4000-oem-data-sheet.pdf +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/motion/__init__.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/motion/intellidrivedevice.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/motion/thorlabs.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/oscilloscope/__init__.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/oscilloscope/oscilloscopedevice.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/powermeters/__init__.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/powermeters/fieldmasterdevice.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/powermeters/integradevice.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/powerstrips/__init__.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/powerstrips/powerstripdevice.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/sources/__init__.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/sources/cobolt.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/sources/hopsdll.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/sources/hopsnative.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/sources/lasersourcedevice.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/sources/matisse.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/sources/millennia.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/sources/verdig.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/spectrometers/__init__.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/spectrometers/darkbulb.png +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/spectrometers/intelhexreader.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/spectrometers/lightbulb.png +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/spectrometers/stellarnet.zip +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/spectrometers/viewer.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/deprecated/cobolt.json +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/deprecated/socketclient.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/deprecated/socketmain.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/deprecated/testCobolt.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/deprecated/testCoboltDevice.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/deprecated/testCoboltSerial.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/deprecated/testIntegraSerialPort.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/deprecated/testJSON.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/deprecated/usbFinder.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/env.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testCobolt.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testCommandRecognition.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testCommunicationPorts.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testConnectSutter.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testDeviceController.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testDeviceManager.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testGenericSerialConverter.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testIntegraDevice.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testIntellidrive.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testLabjackU3.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testLabviewTCPPort.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testLaserCapabilities.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testLinearMotionDevice.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testMapPositionsFunction.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testMatisse.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testMillennia.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testModulePyFTDI.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testModulePySerial.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testNotificationCenter.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testOptotuneDevice.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testOscilloscope.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testPwrUSB.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testPyUSB.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testSerialHardwareFLow.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testSerialPortConverters.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testTCPPort.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testTableDrivenDebugPort.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testThorlabs.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/testVerdiG.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/tests/test_pylablib_kinesis.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary/utils.py +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary.egg-info/dependency_links.txt +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary.egg-info/requires.txt +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/hardwarelibrary.egg-info/top_level.txt +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/pyproject.toml +0 -0
- {hardwarelibrary-2.0.0 → hardwarelibrary-2.2.0}/setup.cfg +0 -0
|
@@ -122,7 +122,7 @@ rot.shutdownDevice()
|
|
|
122
122
|
|
|
123
123
|
### Spectrometers — Ocean Insight USB2000 / USB2000+ / USB4000 / USB650 / SAS
|
|
124
124
|
|
|
125
|
-
Base: `Spectrometer`.
|
|
125
|
+
Base: `Spectrometer`. Drivers implement `doGetSpectrum` / `doGetSerialNumber`; `getSpectrum()` forwards any keyword argument to the hook.
|
|
126
126
|
|
|
127
127
|
```python
|
|
128
128
|
from hardwarelibrary.spectrometers import Spectrometer
|
|
@@ -252,20 +252,33 @@ Devices post Cocoa-style notifications (state changes, measurements, moves) inst
|
|
|
252
252
|
requiring polling. Observe them from anywhere:
|
|
253
253
|
|
|
254
254
|
```python
|
|
255
|
-
from
|
|
255
|
+
from notificationcenter import NotificationCenter
|
|
256
256
|
from hardwarelibrary.powermeters.powermeterdevice import PowerMeterNotification
|
|
257
257
|
|
|
258
258
|
def onMeasure(notification):
|
|
259
|
-
|
|
259
|
+
if notification.user_info["error"] is None:
|
|
260
|
+
print("power:", notification.user_info["result"])
|
|
260
261
|
|
|
261
|
-
NotificationCenter().
|
|
262
|
-
# ... measurements now call onMeasure
|
|
263
|
-
NotificationCenter().
|
|
262
|
+
NotificationCenter().add_observer(self, onMeasure, PowerMeterNotification.didGetAbsolutePower)
|
|
263
|
+
# ... measurements now call onMeasure
|
|
264
|
+
NotificationCenter().remove_observer(self)
|
|
264
265
|
```
|
|
265
266
|
|
|
267
|
+
Every capability posts notifications too, named after the hook: an operation that
|
|
268
|
+
changes the instrument posts `will<Stem>` then `did<Stem>`, a read posts `did<Stem>`
|
|
269
|
+
only, and the `did` is posted even when the driver raised, with the exception under
|
|
270
|
+
`user_info["error"]` (the exception itself still propagates to the caller). The
|
|
271
|
+
payload also carries the method's arguments by name and the return value under
|
|
272
|
+
`"result"`. Each capability names its enum in its `notification` attribute, and
|
|
273
|
+
capabilities related by inheritance share one enum, so `AnalogNotification` covers
|
|
274
|
+
analog input, output, IO and streaming alike. `python -m hardwarelibrary
|
|
275
|
+
--capabilities` lists them all.
|
|
276
|
+
|
|
266
277
|
Useful notification enums: `PhysicalDeviceNotification` (will/did initialize/shutdown,
|
|
267
278
|
status), `LinearMotionNotification` (willMove/didMove/didGetPosition),
|
|
268
|
-
`
|
|
279
|
+
`RotationMotionNotification` (willMove/didMove/didGetOrientation),
|
|
280
|
+
`PowerMeterNotification.didGetAbsolutePower`, `SpectrometerNotification`
|
|
281
|
+
(willGetSpectrum/didGetSpectrum/didGetSerialNumber).
|
|
269
282
|
|
|
270
283
|
## Headless / GUI apps: DeviceController
|
|
271
284
|
|
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes to PyHardwareLibrary, loosely following
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/). Read this before upgrading:
|
|
5
|
+
API changes can land even when the minor version is unchanged.
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **A protocol is described, not performed** (`communication/protocol.py`). A
|
|
11
|
+
`Frame` turns named values into the bytes to write and the bytes read back into
|
|
12
|
+
named values; it owns no port and stores nothing of what happened. `TextFrame`
|
|
13
|
+
states a `str.format` template and the regular expression that reads it back;
|
|
14
|
+
`BinaryFrame` states one struct format, whose constants are written on the way
|
|
15
|
+
out and *required* on the way in. A `Command` pairs two frames, a
|
|
16
|
+
`CommandDictionary` holds a device's commands and reads them from JSON. Nothing
|
|
17
|
+
is inferred anywhere: a byte order must be explicit, and so must both notations
|
|
18
|
+
of a text line.
|
|
19
|
+
- Both directions come out of the same objects. A driver writes the request and
|
|
20
|
+
reads the reply; a debug port reads the request and writes the reply. That is
|
|
21
|
+
what the old `DataCommand` needed a second encoder and decoder for, and those
|
|
22
|
+
could -- and did -- drift apart.
|
|
23
|
+
- `CommandDictionary.usage()` prints every command, its arguments and what it
|
|
24
|
+
answers, with types read off the description rather than a comment.
|
|
25
|
+
- `CommandDictionary.validate()` refuses a description that is wrong about
|
|
26
|
+
itself: a template and an expression that describe different lines, a struct
|
|
27
|
+
that packs a different number of values than it names, a command whose request
|
|
28
|
+
another command answers to first. It writes each command out with specimen
|
|
29
|
+
values made from the declared types and reads it straight back.
|
|
30
|
+
- **`ProtocolDebugPort`** (`communication/debugport.py`), a debug port with no code
|
|
31
|
+
of its own: hand it a `CommandDictionary` and it stands in for the instrument.
|
|
32
|
+
Whatever a request carries is remembered by name and whatever a reply carries is
|
|
33
|
+
answered from that memory. A command may add a `"sets"` clause for a state change
|
|
34
|
+
its bytes cannot express -- `HOME` carries nothing and still moves a stage.
|
|
35
|
+
- **`PhysicalDevice.protocol`** (default `None`) and **`PhysicalDevice.performTransaction()`**,
|
|
36
|
+
which performs one command of that protocol under the port's `transactionLock`,
|
|
37
|
+
reading by `readLength` for a binary reply and up to the terminator for a line.
|
|
38
|
+
A driver that sets `protocol` needs no send-and-receive code of its own.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
- **`SutterDevice` speaks through a description.** Its protocol is data, it sends
|
|
42
|
+
through `performTransaction`, and it no longer checks acknowledgements by hand:
|
|
43
|
+
the description says `MOVE`, `HOME` and `WORK` answer `b"\r"`. Its nested
|
|
44
|
+
`DebugSerialPort` is gone; `serialNumber="debug"` now gets a `ProtocolDebugPort`.
|
|
45
|
+
Callers building the debug port themselves should use
|
|
46
|
+
`ProtocolDebugPort(SutterDevice.protocol)`.
|
|
47
|
+
|
|
48
|
+
### Removed
|
|
49
|
+
- **`PhysicalDevice.sendCommand()`**. It looked a command up in `self.commands`,
|
|
50
|
+
sent it through `self.port` and handed the `Command` object back so a caller
|
|
51
|
+
could read the reply off it -- the pattern the description above exists to
|
|
52
|
+
replace. No driver in the library called it; the two tests that did now send
|
|
53
|
+
through the `Command` itself, which is all it ever did. Use
|
|
54
|
+
`performTransaction()` with a `protocol`, or a `Command`'s own `send()` while a
|
|
55
|
+
device still carries a `commands` dict.
|
|
56
|
+
|
|
57
|
+
## [2.1.0] - 2026-08-04
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
- **Notifications on every capability.** Each capability owns a
|
|
61
|
+
`<Capability>Notification` enum, reachable as its `notification` attribute, and
|
|
62
|
+
every public method is wrapped in the new `@notifies(will=..., did=...)`
|
|
63
|
+
decorator, so a driver gets notifications by implementing hooks and writing no
|
|
64
|
+
notification code at all. An operation that changes the instrument posts
|
|
65
|
+
`will<Stem>` then `did<Stem>`; a read (`doGet*`, `doReadStream`) posts only
|
|
66
|
+
`did<Stem>`, to keep a voltage sampled in a loop at one notification instead of
|
|
67
|
+
two. `Spectrometer` gets `SpectrometerNotification`, whose `getSpectrum` keeps a
|
|
68
|
+
`will` because an acquisition takes an integration time.
|
|
69
|
+
- `did*` is posted whether the operation succeeded or not, so a `will*` is always
|
|
70
|
+
followed by its `did*` and there is no separate failure member to pair up. The
|
|
71
|
+
exception is still **re-raised untouched**, since a driver's exception type is
|
|
72
|
+
part of its contract, so a caller sees it exactly as before while an observer
|
|
73
|
+
decides what to do from the payload.
|
|
74
|
+
- `user_info` is a dict of the public method's arguments by name, plus `"result"`
|
|
75
|
+
and `"error"`, exactly one of which is non-None.
|
|
76
|
+
- Capabilities related by inheritance share one enum, so `notification` is the
|
|
77
|
+
same object on all of them and the members are interchangeable:
|
|
78
|
+
`AnalogInputCapability`, `AnalogOutputCapability`, `AnalogIOCapability` and
|
|
79
|
+
`AnalogInputStreamCapability` all post `AnalogNotification`, and the digital
|
|
80
|
+
trio posts `DigitalNotification` (17 enums for 22 capabilities). Members are
|
|
81
|
+
keyed by identity, so without sharing an observer would have to know which
|
|
82
|
+
variant a device mixed in.
|
|
83
|
+
- Measured overhead on a read with no observer is ~1.6 us per call (~2.0 us with
|
|
84
|
+
one observer), against millisecond-scale device I/O.
|
|
85
|
+
- Removes the unused nested `AnalogInputStreamCapability.Notification`
|
|
86
|
+
(`willAcquire` / `didAcquire`), which was never posted; the equivalent members
|
|
87
|
+
are now `AnalogInputStreamNotification.willAcquireWaveform` / `didAcquireWaveform`.
|
|
88
|
+
- The family bases that already posted notifications now follow the same scheme,
|
|
89
|
+
through the same `@notifies` decorator, and name their enum in a `notification`
|
|
90
|
+
attribute like the capabilities do. **Breaking for observers**:
|
|
91
|
+
- `PowerMeterNotification.didMeasure` is now `didGetAbsolutePower`, named after
|
|
92
|
+
its hook like everywhere else.
|
|
93
|
+
- `LinearMotionNotification` and `RotationMotionNotification` keep their grouped
|
|
94
|
+
`willMove` / `didMove` (`moveTo`, `moveBy` and `home` are one operation to an
|
|
95
|
+
observer), but the payload changed: `user_info` is now a dict carrying the
|
|
96
|
+
method's arguments by name plus `"result"` and `"error"`, where it used to be
|
|
97
|
+
the bare position, displacement or angle. A handler reading
|
|
98
|
+
`notification.user_info` as a tuple must now read `user_info["position"]`,
|
|
99
|
+
`user_info["displacement"]` or `user_info["angle"]`.
|
|
100
|
+
- Every one of them now also reports failures: the `did*` is posted even when the
|
|
101
|
+
driver raised, with the exception under `user_info["error"]`.
|
|
102
|
+
- `Spectrometer` gained the `notification` attribute it was missing.
|
|
103
|
+
- `CameraDeviceNotification` is left alone: a capture session is a different
|
|
104
|
+
shape (`imageCaptured` fires per frame), not a will/did pair around one hook.
|
|
105
|
+
- **Contract-level argument validation on the capability public methods**, through a
|
|
106
|
+
new `validate=` parameter on `@notifies` and a new `hardwarelibrary/validation.py`
|
|
107
|
+
holding the shared `require*` checks. The validator runs before anything is posted,
|
|
108
|
+
so a refused call announces nothing and a will/did pair still means the driver was
|
|
109
|
+
invoked. **Breaking**: calls that used to be accepted silently now raise.
|
|
110
|
+
- `acquireWaveform(sampleCount=0)` returned an empty acquisition; now `ValueError`.
|
|
111
|
+
An empty `channels` list failed with `min() iterable argument is empty`; now it
|
|
112
|
+
names the parameter.
|
|
113
|
+
- `configureStream(sampleRate=-100)` was accepted; a rate must be positive, or
|
|
114
|
+
`None` to say the clock is external. `sampleRate=0` no longer means "external":
|
|
115
|
+
pass `None`, which is what the docstring always said.
|
|
116
|
+
- `setDigitalValue("yes", channel)` set the line True; a logic level must be a bool
|
|
117
|
+
(0 and 1 accepted). `setAnalogVoltage("2.5", channel)` now raises `TypeError`.
|
|
118
|
+
- `setSensitivity(-1)` and `setTimeConstant(0)` were silently snapped to a step;
|
|
119
|
+
both must be positive.
|
|
120
|
+
- `setWavelength` and `setDispersion` are checked against the range the driver
|
|
121
|
+
reports, so `matisse.setWavelength(50.0)` no longer drives the birefringent
|
|
122
|
+
filter outside the installed optics' 700-1000 nm.
|
|
123
|
+
- Outlets are checked against `doGetOutletCount()` in `OutletSwitchingCapability`
|
|
124
|
+
and `DefaultOutletCapability`, so `PwrUSBDevice._validateOutlet` is gone and every
|
|
125
|
+
future strip inherits the rule.
|
|
126
|
+
- `setInputSource` and `setTriggerSource` accept anything their enum accepts
|
|
127
|
+
(`setInputSource("Differential")`) and hand the driver a member.
|
|
128
|
+
- Instrument-specific limits stay in the drivers, unchanged.
|
|
129
|
+
- **Every notified operation now requires an initialized device.** `@notifies` calls
|
|
130
|
+
`validateReady()` before anything else, raising `PhysicalDevice.NotInitialized`
|
|
131
|
+
unless the device is `Ready` and naming the operation, the class and the actual
|
|
132
|
+
state. Previously such a call either failed deep inside the driver with
|
|
133
|
+
`AttributeError: 'NoneType' object has no attribute ...` on a port that was never
|
|
134
|
+
opened, or -- on a debug device -- answered as though the hardware had done it and
|
|
135
|
+
posted a `did*` claiming success. The check runs before the `will` is posted, so a
|
|
136
|
+
refused call announces nothing. `validateReady` is defined once, on
|
|
137
|
+
`PhysicalDevice`, where the device lifecycle belongs; `capabilities.py` only calls
|
|
138
|
+
it, so a class mixing in a capability without being a `PhysicalDevice` answers for
|
|
139
|
+
readiness itself rather than silently skipping the check. Methods that only report what a model supports
|
|
140
|
+
(`supportedInputSources`, `supportedSensitivities`, `supportedTimeConstants`,
|
|
141
|
+
`supportedTriggerSources`, `outletCount`) are exempt via `requiresReady=False`,
|
|
142
|
+
since a UI populates its menus before connecting.
|
|
143
|
+
- `allCapabilities()` in `hardwarelibrary/capabilities.py`: returns every capability
|
|
144
|
+
mixin the library defines, in declaration order. It answers the library-wide
|
|
145
|
+
question ("what can be expressed?"), where `PhysicalDevice.capabilities()` answers
|
|
146
|
+
the per-device one ("what does this instrument support?"). Enumerating the module
|
|
147
|
+
rather than walking `Capability.__subclasses__()` keeps the answer independent of
|
|
148
|
+
which device modules happen to be imported, and excludes the drivers, which are
|
|
149
|
+
`Capability` subclasses themselves.
|
|
150
|
+
- `capabilityInterface()` in `hardwarelibrary/capabilities.py`: describes one capability
|
|
151
|
+
as `extends` / `publicAPI` / `hooks` lists of `CapabilityMember(name, signature,
|
|
152
|
+
isAbstract)` tuples. The `do` prefix is what separates a hook from the public API,
|
|
153
|
+
not abstractness: a hook that is optional, or that defaults to a composition of the
|
|
154
|
+
others, is concrete. Members a parent capability declares are left to that parent.
|
|
155
|
+
- `python -m hardwarelibrary --capabilities` (`-c`): prints every capability with the
|
|
156
|
+
methods it defines and the hooks a driver must implement, so the list never has to
|
|
157
|
+
be maintained by hand.
|
|
158
|
+
- `hardwarelibrary/tests/testCapabilities.py`: covers `allCapabilities()` and
|
|
159
|
+
`capabilityInterface()`, and enforces the invariant that every capability mixin is
|
|
160
|
+
declared in `capabilities.py`, by comparing the module listing against a full walk
|
|
161
|
+
of the `Capability` subclass graph.
|
|
162
|
+
|
|
163
|
+
### Changed
|
|
164
|
+
- **The public/`do*` template method pattern is now uniform across every capability.**
|
|
165
|
+
The DAQ, lock-in and trigger capabilities used to declare their public method
|
|
166
|
+
itself as the `@abstractmethod`; they now follow the same rule as every other
|
|
167
|
+
family: `getXxx()` is concrete and calls `doGetXxx()`, and only the hook is
|
|
168
|
+
abstract. This keeps the public method free for the argument validation,
|
|
169
|
+
notifications and error handling to be added there. Affected:
|
|
170
|
+
`AnalogInputCapability`, `AnalogOutputCapability`, `AnalogIOCapability`,
|
|
171
|
+
`AnalogInputStreamCapability`, `PhaseLockedDetectionCapability`,
|
|
172
|
+
`TriggerCapability`, `DigitalInputCapability`, `DigitalOutputCapability`,
|
|
173
|
+
`DigitalIOCapability`.
|
|
174
|
+
- **Callers are unaffected**: every public name and signature is unchanged.
|
|
175
|
+
- **Driver authors must rename their implementations** to the `do*` hook, e.g.
|
|
176
|
+
`getAnalogVoltage` -> `doGetAnalogVoltage`, `setDigitalValue` ->
|
|
177
|
+
`doSetDigitalValue`, `configureStream` -> `doConfigureStream`,
|
|
178
|
+
`softwareTrigger` -> `doSoftwareTrigger`, `supportedSensitivities` ->
|
|
179
|
+
`doGetSupportedSensitivities`. A driver that misses one fails loudly at
|
|
180
|
+
instantiation with `TypeError`, naming the missing hook. `LabjackDevice` and
|
|
181
|
+
`SR830Device` (and their debug counterparts) were migrated.
|
|
182
|
+
- `configureStream(channels, sampleRate=None, **parameters)` forwards extra
|
|
183
|
+
keyword arguments to `doConfigureStream`, so instrument-specific options
|
|
184
|
+
(the SR830's `sampleClock`, the LabJack's deprecated `scanRate`) still reach
|
|
185
|
+
the driver through the shared public method.
|
|
186
|
+
- **`Spectrometer` follows the same pattern**: `getSpectrum()` and
|
|
187
|
+
`getSerialNumber()` are now concrete and delegate to the abstract
|
|
188
|
+
`doGetSpectrum()` / `doGetSerialNumber()`, which is the last place in the
|
|
189
|
+
library where the public method was itself the hook. `getSpectrum(**parameters)`
|
|
190
|
+
forwards keywords to the driver, so `getSpectrum(maxRequests=2, maxWait=0.05)`
|
|
191
|
+
still reaches `OISpectrometer`. `OISpectrometer` was migrated; `DebugSpectro`
|
|
192
|
+
is unaffected because it does not subclass `Spectrometer`.
|
|
193
|
+
- **ACTION REQUIRED for the licenced StellarNet driver**, which is distributed
|
|
194
|
+
encrypted and is not in this repository: rename its `getSpectrum` and
|
|
195
|
+
`getSerialNumber` to `doGetSpectrum` and `doGetSerialNumber`. Until then
|
|
196
|
+
`StellarNet()` raises `TypeError` for the missing hooks.
|
|
197
|
+
- `hardwarelibrary/tests/testCapabilities.py` now also asserts that no
|
|
198
|
+
`PhysicalDevice` subclass in the library declares an abstract method outside its
|
|
199
|
+
`do*` hooks, so the pattern is enforced for family base classes, not just mixins.
|
|
200
|
+
`HOPSInterface` (`sources/verdig.py`) is deliberately exempt: it is a transport
|
|
201
|
+
strategy behind `VerdiGDevice`, closer to `CommunicationPort` than to a device API.
|
|
202
|
+
- README: the supported-hardware table now lists every driver in the tree. It was
|
|
203
|
+
missing `VerdiGDevice`, `FieldMasterDevice`, `SR830Device`, `PwrUSBDevice` and
|
|
204
|
+
`StellarNet`, and carried the Millennia without its USB identity
|
|
205
|
+
(`0x0483:0x5740`). Added a "Capabilities" section explaining the mixin pattern
|
|
206
|
+
from first principles, and refreshed the class-hierarchy diagram, which was
|
|
207
|
+
stale in the same way.
|
|
208
|
+
|
|
209
|
+
## [2.0.0] - 2026-07-24
|
|
210
|
+
|
|
211
|
+
Recorded after the fact: this release was tagged without a changelog entry.
|
|
212
|
+
|
|
213
|
+
### Changed
|
|
214
|
+
- **`NotificationCenter` now comes from the standalone `notifcenter` package** on
|
|
215
|
+
PyPI, added as a dependency, rather than living in the library.
|
|
216
|
+
`hardwarelibrary/notificationcenter.py` is deleted, and the 19 imports across the
|
|
217
|
+
tree point at `from notificationcenter import ...`. `hardwarelibrary/__init__.py`
|
|
218
|
+
re-exports the external package, so `from hardwarelibrary import
|
|
219
|
+
NotificationCenter` keeps working.
|
|
220
|
+
- **The notification API is snake_case**, a clean break with no aliases, across
|
|
221
|
+
roughly 286 call sites: `addObserver` -> `add_observer`, `postNotification` ->
|
|
222
|
+
`post_notification`, `removeObserver` -> `remove_observer`, `observersCount` ->
|
|
223
|
+
`observers_count`, and the keyword arguments and attributes `notificationName`,
|
|
224
|
+
`observedObject`, `userInfo`, `notifyingObject` -> `notification_name`,
|
|
225
|
+
`observed_object`, `user_info`, `notifying_object`. Any code observing a device
|
|
226
|
+
must be updated; this is what made the release a major one. Note that the rest of
|
|
227
|
+
the library remains camelCase -- the snake_case is the external package's
|
|
228
|
+
convention, not a change of style here.
|
|
229
|
+
|
|
230
|
+
## [1.5.0] - 2026-07-22
|
|
231
|
+
|
|
232
|
+
### Added
|
|
233
|
+
- PowerStrip device family (`hardwarelibrary/powerstrips/`) plus its first driver
|
|
234
|
+
`PwrUSBDevice` (and `DebugPwrUSBDevice`) for the PwrUSB / PowerUSB controllable
|
|
235
|
+
power strip (USB HID `04d8:003f`, enumerates as "Simple HID Device Demo"). The
|
|
236
|
+
family follows the interface-segregated capability-mixin pattern used by
|
|
237
|
+
`sources/` and `daq/`: `PowerStripDevice` is a thin marker base over
|
|
238
|
+
`PhysicalDevice`, and behaviour comes from `OutletSwitchingCapability`
|
|
239
|
+
(`turnOutletOn`/`turnOutletOff`/`setOutletState`/`isOutletOn`/`outletCount`,
|
|
240
|
+
outlets 1-based), `DefaultOutletCapability` (per-outlet power-on default
|
|
241
|
+
state), and `CurrentMeteringCapability` (`current()` in A, `accumulatedCharge()`
|
|
242
|
+
in Ah, `resetAccumulatedCharge()`), all in the shared
|
|
243
|
+
`hardwarelibrary/capabilities.py`. The strip speaks a single-byte HID report protocol
|
|
244
|
+
driven through a `HIDPort`; the protocol was reverse-engineered publicly and
|
|
245
|
+
cross-checked against aarossig/pwrusbctl (Apache-2.0) and pwrusb.com, but the
|
|
246
|
+
implementation is our own. Outlet state is cached on write because live
|
|
247
|
+
readback is unreliable on this firmware.
|
|
248
|
+
- `HIDPort` (`hardwarelibrary/communication/hidport.py`): a `CommunicationPort`
|
|
249
|
+
over a USB HID device, backed by hidapi (IOKit on macOS), alongside
|
|
250
|
+
`SerialPort` and `USBPort`. Needed because an HID device the OS claims has no
|
|
251
|
+
`/dev` node (so `SerialPort` cannot reach it) and cannot be claimed by libusb
|
|
252
|
+
(so `USBPort` cannot either) -- notably on macOS, where `IOHIDFamily` owns the
|
|
253
|
+
interface. hidapi is an optional dependency; install it with the new `pwrusb`
|
|
254
|
+
extra (`pip install -e .[pwrusb]`), required to drive the strip.
|
|
255
|
+
- `VerdiGDevice` (and `DebugVerdiGDevice`): a laser-source driver for the Coherent
|
|
256
|
+
"HOPS" (High Output Power Supply) laser -- Genesis heads / Verdi G-C, e.g. the
|
|
257
|
+
lab Genesis CX-Vis (head `G532`). A HOPS supply is not a serial device: its
|
|
258
|
+
FTDI FT2232 (`0x0403:0x6010`) is driven as bit-banged I2C, with power DAC, ADC,
|
|
259
|
+
shutter/enable GPIO, and the head identity/calibration EEPROM all on one I2C
|
|
260
|
+
bus (see `manuals/Coherent-HOPS-*`). `VerdiGDevice` combines `OnOffCapability`,
|
|
261
|
+
`ShutterCapability`, `PowerCapability`, and `InterlockCapability`, and drives the bus
|
|
262
|
+
through an interchangeable `HOPSInterface`:
|
|
263
|
+
- `HOPSNativeInterface` (`sources/hopsnative.py`): **pure-Python** pyftdi I2C,
|
|
264
|
+
no DLL (macOS/Linux). Hardware-confirmed end to end on the lab unit
|
|
265
|
+
(identity, on/off, shutter, remote, power setpoint, temperature). Its
|
|
266
|
+
`interlock()`/`faults()` raise `HOPSInterface.NotSupported` until the `?FF`
|
|
267
|
+
decode is reverse-engineered.
|
|
268
|
+
- `HOPSDLLInterface` (`sources/hopsdll.py`): Coherent's `CohrHOPS.dll` (ASCII
|
|
269
|
+
command set; Windows/Linux). Read + `REM`/`PCMD` write paths hardware-
|
|
270
|
+
confirmed; `KSWCMD`/`SHCMD` per the DLL spec, not yet exercised.
|
|
271
|
+
Selection: `VerdiGDevice(interface="auto")` tries native first, then the DLL;
|
|
272
|
+
pass `"native"`/`"dll"`/an interface instance to force one. Protocol and I2C
|
|
273
|
+
decode in `manuals/Coherent-HOPS-2-USB-and-DLL-Protocol.md` and
|
|
274
|
+
`manuals/Coherent-HOPS-3-I2C-Wire-Protocol.md`.
|
|
275
|
+
|
|
276
|
+
### Changed
|
|
277
|
+
- **Breaking:** capability mixins across all families now use a uniform
|
|
278
|
+
`*Capability` suffix, reserving `*Device` for instantiable hardware drivers.
|
|
279
|
+
Public methods and behavior are unchanged; only the mixin class names change.
|
|
280
|
+
Drivers subclassing these must update their base-class lists and imports.
|
|
281
|
+
- DAQ: `AnalogInputDevice` -> `AnalogInputCapability`, `AnalogOutputDevice` ->
|
|
282
|
+
`AnalogOutputCapability`, `AnalogIODevice` -> `AnalogIOCapability`,
|
|
283
|
+
`AnalogInputStreamDevice` -> `AnalogInputStreamCapability`,
|
|
284
|
+
`DigitalInputDevice` -> `DigitalInputCapability`, `DigitalOutputDevice` ->
|
|
285
|
+
`DigitalOutputCapability`, `DigitalIODevice` -> `DigitalIOCapability`,
|
|
286
|
+
`PhaseLockedDetectionDevice` -> `PhaseLockedDetectionCapability`,
|
|
287
|
+
`TriggerableDevice` -> `TriggerCapability`.
|
|
288
|
+
- Laser sources: `OnOffControl` -> `OnOffCapability`, `ShutterControl` ->
|
|
289
|
+
`ShutterCapability`, `PowerControl` -> `PowerCapability`, `InterlockControl`
|
|
290
|
+
-> `InterlockCapability`, `AutostartControl` -> `AutostartCapability`,
|
|
291
|
+
`WavelengthControl` -> `WavelengthCapability`, `DispersionControl` ->
|
|
292
|
+
`DispersionCapability`.
|
|
293
|
+
- Power meters: `WavelengthCalibratable` -> `WavelengthCalibrationCapability`,
|
|
294
|
+
`AutoScalable` -> `AutoScaleCapability`, `ScaleAdjustable` ->
|
|
295
|
+
`ScaleCapability`.
|
|
296
|
+
- **Breaking:** all capability mixins are consolidated into a single module,
|
|
297
|
+
`hardwarelibrary/capabilities.py`, and share one `Capability` base class (the
|
|
298
|
+
per-family `sources/capabilities.py`, `powermeters/capabilities.py`, and
|
|
299
|
+
`daq/daqdevice.py` are removed; the DAQ enums `InputSource`, `TriggerSource`,
|
|
300
|
+
`SampleClock` move there too, and the acquisition notification enum is now
|
|
301
|
+
nested as `AnalogInputStreamCapability.Notification`). Imports must point at
|
|
302
|
+
`hardwarelibrary.capabilities` (the family package `__init__`s still re-export
|
|
303
|
+
their own mixins, so `from hardwarelibrary.daq import AnalogIOCapability` and
|
|
304
|
+
the like keep working). `capabilities()` / `hasCapability()` are hoisted onto
|
|
305
|
+
`PhysicalDevice`, so every device -- including DAQ drivers -- now supports
|
|
306
|
+
capability introspection; the duplicated methods on `LaserSourceDevice` and
|
|
307
|
+
`PowerMeterDevice` are gone (`LaserSourceDevice` is now a pure marker).
|
|
308
|
+
|
|
309
|
+
## [1.4.0] - 2026-07-08
|
|
310
|
+
|
|
311
|
+
### Added
|
|
312
|
+
- `SR830Device` (and `DebugSR830Device`): Stanford Research SR830 DSP lock-in
|
|
313
|
+
amplifier over a Prologix GPIB-USB controller. It combines several capabilities:
|
|
314
|
+
`AnalogInputStreamDevice` (the four rear-panel Aux A/D inputs via `OAUX?`, plus
|
|
315
|
+
hardware-timed buffered acquisition of the demodulated outputs from the internal
|
|
316
|
+
data buffer), `AnalogOutputDevice` (the four rear-panel Aux D/A outputs via
|
|
317
|
+
`AUXV`), `PhaseLockedDetectionDevice` (X/Y/R/theta, reference frequency, signal
|
|
318
|
+
input source, sensitivity, and time constant), and `TriggerableDevice` (the
|
|
319
|
+
rear-panel TRIG IN). Enums: `AuxInput`, `AuxOutput`, `StreamChannel`,
|
|
320
|
+
`InputSource`. `doInitializeDevice` self-discovers the Prologix among the
|
|
321
|
+
connected FTDI adaptors by confirming `*IDN?`, and pins the adaptor's serial.
|
|
322
|
+
- `PrologixGPIBPort` (`hardwarelibrary/communication/`): a `SerialPort` subclass
|
|
323
|
+
that speaks the Prologix GPIB-USB controller `++` protocol. GPIB instruments
|
|
324
|
+
talk to it with the ordinary `readString`/`writeString` primitives; the `++read
|
|
325
|
+
eoi` handshake is encapsulated in its `readString`.
|
|
326
|
+
- New DAQ capability contracts in `daq/daqdevice.py`: `PhaseLockedDetectionDevice`
|
|
327
|
+
(lock-in / phase-locked detection), `TriggerableDevice` with the `TriggerSource`
|
|
328
|
+
enum, and the `SampleClock` enum for stream sample clocking.
|
|
329
|
+
|
|
330
|
+
### Changed
|
|
331
|
+
- **Breaking:** `AnalogInputStreamDevice`: the sample-rate parameter of
|
|
332
|
+
`configureStream`/`acquireWaveform` is renamed `scanRate` -> `sampleRate`.
|
|
333
|
+
Callers passing it positionally are unaffected; callers passing `scanRate=` by
|
|
334
|
+
keyword must switch to `sampleRate=`. `LabjackDevice.configureStream` keeps
|
|
335
|
+
`scanRate` as a temporary deprecated synonym, so LabJack callers are unaffected
|
|
336
|
+
for now.
|
|
337
|
+
- `LabjackDevice` now imports `u3` (LabJackPython) lazily at point of use, so
|
|
338
|
+
`import hardwarelibrary.daq` (and the new SR830 driver) works on hosts that do
|
|
339
|
+
not have LabJackPython installed.
|
|
340
|
+
|
|
341
|
+
## [1.3.3] - 2026-07-08
|
|
342
|
+
|
|
343
|
+
### Changed
|
|
344
|
+
- Heavy third-party modules are now imported lazily, at their point of use,
|
|
345
|
+
instead of at module load. `import hardwarelibrary` no longer pulls in
|
|
346
|
+
`matplotlib` or `numpy` (import time drops from ~336 ms to ~59 ms); they load
|
|
347
|
+
only when a plot is drawn or a spectrum is acquired. Affected: the
|
|
348
|
+
spectrometers package (`SpectraViewer` deferred into `display()`/`displayAny()`,
|
|
349
|
+
`numpy` into the methods that use it; `base.py` uses
|
|
350
|
+
`from __future__ import annotations` for its `-> np.array` hint),
|
|
351
|
+
`OscilloscopeDevice.displayWaveforms`, and the cameras module (`cv2`). Public
|
|
352
|
+
APIs are unchanged. Two behavioral notes: importing `hardwarelibrary.cameras`
|
|
353
|
+
no longer prints a warning when OpenCV is absent — a missing `cv2` now raises
|
|
354
|
+
`ModuleNotFoundError` when a camera operation is invoked; and the unused
|
|
355
|
+
matplotlib import block in `oceaninsight.py` was removed.
|
|
356
|
+
|
|
357
|
+
### Removed
|
|
358
|
+
- Dead `from pyftdi.ftdi import Ftdi` imports in `SutterDevice` and `EchoDevice`
|
|
359
|
+
(both were unused and flagged `# FIXME: should not be here`). FTDI access still
|
|
360
|
+
goes through `SerialPort`, which owns the `pyftdi` dependency.
|
|
361
|
+
|
|
362
|
+
## [1.3.2] - 2026-07-07
|
|
363
|
+
|
|
364
|
+
### Added
|
|
365
|
+
- `MillenniaEv25Device` (and its `MillenniaDevice` alias) now discovers its port
|
|
366
|
+
by USB identity when constructed without a `portPath`. The class carries the
|
|
367
|
+
STM32 Virtual COM Port identity `classIdVendor = 0x0483` /
|
|
368
|
+
`classIdProduct = 0x5740`, and `doInitializeDevice` matches it over pyserial's
|
|
369
|
+
ports, raising `UnableToInitialize` naming the identity when none is found. An
|
|
370
|
+
explicit `portPath` still takes precedence, and a `serialNumber` narrows
|
|
371
|
+
discovery when several STM32 USB-CDC ports are present. Note: `0x0483:0x5740`
|
|
372
|
+
is STMicro's generic STM32 VCP identity shared by unrelated STM32 boards, so
|
|
373
|
+
pin `portPath` on a host that has more than one.
|
|
374
|
+
|
|
375
|
+
## [1.3.1] - 2026-07-06
|
|
376
|
+
|
|
377
|
+
### Added
|
|
378
|
+
- Power-meter capability mixins (`powermeters/capabilities.py`), mirroring the
|
|
379
|
+
laser-source `Capability` structure: `WavelengthCalibratable`
|
|
380
|
+
(`getCalibrationWavelength` / `setCalibrationWavelength`), `AutoScalable`
|
|
381
|
+
(`autoScaleIsOn` / `turnAutoScaleOn` / `turnAutoScaleOff`), and
|
|
382
|
+
`ScaleAdjustable` (`getScale` / `setScale` / `availableScales`), each
|
|
383
|
+
delegating to `do*` hooks the driver implements. `PowerMeterDevice` gains
|
|
384
|
+
`capabilities()` and `hasCapability(capabilityClass)` for introspection.
|
|
385
|
+
|
|
386
|
+
### Changed
|
|
387
|
+
- The wavelength-calibration hooks (`doGetCalibrationWavelength`,
|
|
388
|
+
`doSetCalibrationWavelength`) and their public methods move off
|
|
389
|
+
`PowerMeterDevice` into the new `WavelengthCalibratable` mixin. The base now
|
|
390
|
+
requires only `doGetAbsolutePower`. `IntegraDevice` and `FieldMasterDevice`
|
|
391
|
+
declare `WavelengthCalibratable`, so their public API is unchanged; a new
|
|
392
|
+
power meter that calibrates by wavelength must now mix in
|
|
393
|
+
`WavelengthCalibratable` to expose those methods.
|
|
394
|
+
- `PhysicalDevice.__init__` is now a cooperative base: it calls
|
|
395
|
+
`super().__init__()` after consuming the device-identity arguments, so a
|
|
396
|
+
capability mixin combined with a device (e.g.
|
|
397
|
+
`IntegraDevice(PowerMeterDevice, WavelengthCalibratable)`) has its `__init__`
|
|
398
|
+
run instead of being skipped by the MRO. A mixin `__init__` must therefore
|
|
399
|
+
take no required arguments and call `super().__init__()` itself. No existing
|
|
400
|
+
device changes behavior.
|
|
401
|
+
|
|
402
|
+
## [1.3.0] - 2026-07-06
|
|
403
|
+
|
|
404
|
+
### Added
|
|
405
|
+
- `FieldMasterDevice` and `DebugFieldMasterDevice` (`powermeters/`): a driver
|
|
406
|
+
for the Coherent FieldMaster GS laser power/energy meter over RS-232 via an
|
|
407
|
+
FTDI adaptor (9600 8N1, LF terminator, `pw?`/`en?`/`wv?`/`v` commands). The
|
|
408
|
+
meter has no USB identity of its own, so `classIdVendor`/`classIdProduct` are
|
|
409
|
+
the generic FTDI values (0x0403/0x6001); disambiguate multiple FTDI adaptors
|
|
410
|
+
with the adaptor `serialNumber` or an explicit `portPath`. The message
|
|
411
|
+
terminator is a front-panel Menu setting (LF/CR/CR-LF); `initializeDevice`
|
|
412
|
+
probes with the configured `terminator` (default LF) and falls through the
|
|
413
|
+
other combinations until one replies, so a mismatched Menu self-heals. Note:
|
|
414
|
+
the meter only answers RS-232 while on its Home or Trend screen, and
|
|
415
|
+
`initializeDevice` raises with that hint when none of the terminators reply.
|
|
416
|
+
- `SerialPort.genericSerialConverterPorts()` and `isGenericSerialConverter()`,
|
|
417
|
+
plus the `genericSerialConverterVendors` table: discover connected ports that
|
|
418
|
+
come from a generic USB/RS-232 converter chip (FTDI, Prolific, Silicon Labs
|
|
419
|
+
CP210x, WCH CH34x), so an instrument with no USB identity of its own can be
|
|
420
|
+
located and disambiguated by the adaptor's serial number.
|
|
421
|
+
- `PhysicalDevice.usesGenericSerialConverter` flag (default `False`). A device
|
|
422
|
+
behind a generic converter matches any converter vendor (its `vidpids()`
|
|
423
|
+
expands to the whole table, product id wildcarded), and
|
|
424
|
+
`DeviceManager.candidateClassesForAutoDiscovery()` excludes such classes from
|
|
425
|
+
automatic probing, since their VID/PID identifies only the cable. FieldMaster,
|
|
426
|
+
oscilloscope, Echo and IntelliDrive are flagged and must be constructed
|
|
427
|
+
explicitly; Thorlabs (custom-EEPROM FTDI PID) stays a specific identity.
|
|
428
|
+
|
|
429
|
+
### Changed
|
|
430
|
+
- `PhysicalDevice.isCompatibleWith` treats a `None` product id in a `vidpids()`
|
|
431
|
+
pair as a wildcard that matches any product from that vendor. Concrete
|
|
432
|
+
`(vendor, product)` pairs are unaffected.
|
|
433
|
+
|
|
434
|
+
### Fixed
|
|
435
|
+
- `OISpectrometer.getSpectrum` no longer hangs. The wait for the "spectrum
|
|
436
|
+
ready" flag is now bounded (`maxRequests`/`maxWait`) and raises
|
|
437
|
+
`SpectrumRequestTimeoutError` instead of re-requesting a spectrum forever on a
|
|
438
|
+
transient USB glitch; transient `usb.core.USBError` (incl. `USBTimeoutError`)
|
|
439
|
+
during polling is absorbed and retried. `integrationTime` remains the first
|
|
440
|
+
optional argument, so all existing callers are unaffected.
|
|
441
|
+
|
|
442
|
+
## [1.1.0] - 2026-05-29
|
|
443
|
+
|
|
444
|
+
### Changed
|
|
445
|
+
- `CommunicationPort`: the optional matching-method argument `alternatePattern`
|
|
446
|
+
is renamed to `errorPattern` on `writeStringExpectMatchingString`,
|
|
447
|
+
`writeStringReadMatchingGroups`, `writeStringReadFirstMatchingGroup`, and
|
|
448
|
+
`readMatchingGroups`, and it now actually works on all of them (it was
|
|
449
|
+
inverted in one method and silently ignored in the others). A reply matching
|
|
450
|
+
`errorPattern` raises `CommunicationReadError` carrying that pattern's capture
|
|
451
|
+
groups; a reply matching neither pattern still raises
|
|
452
|
+
`CommunicationReadNoMatch`. Callers that passed the argument positionally are
|
|
453
|
+
unaffected; callers passing `alternatePattern=` by keyword must switch to
|
|
454
|
+
`errorPattern=`.
|
|
455
|
+
- `CommunicationReadError.__init__` now takes `(reply, groups)` instead of a
|
|
456
|
+
single argument. Catching the exception is unaffected; only code that
|
|
457
|
+
constructs or raises it directly must update.
|
|
458
|
+
- `MatisseDevice.queryString` is renamed to `query`. The high-level API
|
|
459
|
+
(`wavelength`, `setWavelength`, the BiFi/thin-etalon/piezo/scan get/set/lock
|
|
460
|
+
methods, and `sendSetting`) is unchanged.
|
|
461
|
+
|
|
462
|
+
### Added
|
|
463
|
+
- `CommunicationReadError` exception (replaces `CommunicationReadAlternateMatch`).
|
|
464
|
+
- `CommunicationPort.writeStringReadMatch` and `CommunicationPort.matchReply`,
|
|
465
|
+
the shared write-then-read-then-match and pure-match helpers the matching
|
|
466
|
+
methods now delegate to.
|
|
467
|
+
- `DebugMatissePort`, a debug port that speaks the Matisse reply grammar so
|
|
468
|
+
`DebugMatisseDevice` runs the same port code path as the real device.
|
|
469
|
+
|
|
470
|
+
### Removed
|
|
471
|
+
- `MatisseDevice.parseReply`. Its job is now expressed as the port's
|
|
472
|
+
`replyPattern`/`errorPattern`, with errors mapped to `MatisseCommanderError`.
|
|
473
|
+
|
|
474
|
+
### Deprecated
|
|
475
|
+
- `CommunicationReadAlternateMatch` is kept as an alias for
|
|
476
|
+
`CommunicationReadError` and will be removed in a future release.
|
|
@@ -51,19 +51,36 @@ Each family has an **abstract base class**. A driver subclasses it and implement
|
|
|
51
51
|
| Linear motion | `motion/` | `LinearMotionDevice` | `doMoveTo`, `doMoveBy`, `doGetPosition`, `doHome` |
|
|
52
52
|
| Rotation | `motion/` | `RotationDevice` | `doMoveTo`, `doMoveBy`, `doGetOrientation`, `doHome` |
|
|
53
53
|
| Power meter | `powermeters/` | `PowerMeterDevice` | `doGetAbsolutePower`, `doGetCalibrationWavelength`, `doSetCalibrationWavelength` |
|
|
54
|
-
| Spectrometer | `spectrometers/` | `Spectrometer` (`base.py`) | `
|
|
54
|
+
| Spectrometer | `spectrometers/` | `Spectrometer` (`base.py`) | `doGetSpectrum`, `doGetSerialNumber` |
|
|
55
55
|
| Oscilloscope | `oscilloscope/` | `OscilloscopeDevice` | instantiated directly (Tektronix), no family/driver split; per-instrument SCPI methods |
|
|
56
56
|
| Camera | `cameras/` | `CameraDevice` | `doCaptureFrame` |
|
|
57
57
|
| Laser source | `sources/` | `LaserSourceDevice` (marker) + capability mixins (see below) | the `do*` hooks of whichever mixins the driver declares |
|
|
58
|
-
| DAQ | `daq/` | capability mixins (see below) |
|
|
58
|
+
| DAQ | `daq/` | capability mixins (see below) | the `do*` hooks of whichever mixins the driver declares |
|
|
59
59
|
|
|
60
60
|
**Thorlabs linear motion** is a backend dispatcher: `ThorlabsDevice` (in `motion/thorlabs.py`) routes to `ThorlabsKinesisDevice`, which drives the stage through `pylablib`'s Kinesis support. The old FTDI path (`ThorlabsFTDIDevice`) was dropped. Install with the `thorlabs` extra (`pip install -e .[thorlabs]`).
|
|
61
61
|
|
|
62
62
|
All families **use interface-segregated capability mixins** instead of one fat base class, because a device may implement any subset of the capabilities. Every mixin lives in the single module `hardwarelibrary/capabilities.py` and subclasses the one `Capability` base there; mixins carry the `*Capability` suffix, and only instantiable hardware drivers are named `*Device`. `PhysicalDevice` provides `capabilities()` / `hasCapability(cls)`, which introspect any device by walking its MRO for `Capability` subclasses — so every family gets capability introspection for free.
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
**Every capability follows the same template**, without exception: the public method is concrete and delegates to a `do*` hook, `getXxx()` calling `doGetXxx()`, and only the hook is `@abstractmethod`. A driver implements hooks and never overrides the public method, which is where argument validation and notifications live. Hooks that are optional, or that default to a composition of the other hooks (`doAcquireWaveform`, `doGetDemodulatedValues`, `doGetSupported*`), are concrete but still carry the `do` prefix. `hardwarelibrary/tests/testCapabilities.py` enforces this: no public method on any capability may be abstract.
|
|
65
|
+
|
|
66
|
+
**Every capability posts notifications.** Each names its enum in its `notification` attribute, and every public method is wrapped in `@notifies(will=..., did=...)` from `capabilities.py`, so a driver gets notifications for free by implementing hooks. 17 enums cover the 22 capabilities. The rules, all enforced by `testCapabilities.py`:
|
|
67
|
+
|
|
68
|
+
- An operation that changes the instrument posts `will<Stem>` before and `did<Stem>` after; a **read (`doGet*`, `doReadStream`) posts only `did<Stem>`**, to keep hot paths (a voltage sampled in a loop) at one notification instead of two. `Spectrometer.getSpectrum` is the one read that keeps a `will`, because acquiring takes an integration time.
|
|
69
|
+
- **`did*` is posted whether the operation succeeded or not**, so a `will*` is always followed by its `did*` and there is no separate failure member to pair up. The **exception is still re-raised untouched** — a driver's exception type is part of its contract (`SR830Device` raises `ValueError` for an out-of-range Aux voltage, and callers rely on it). An observer decides what to do from the payload; a caller still sees the exception.
|
|
70
|
+
- `user_info` is a dict of the public method's arguments by name, plus `"result"` and `"error"`, exactly one of which is non-None. Test `user_info["error"]`, not the member, to tell success from failure.
|
|
71
|
+
- **Contract-level argument checks live in the capability**, in a `_validateXxx` method handed to `@notifies` as `validate=`, built from the `require*` helpers in `hardwarelibrary/validation.py` (`requireRealNumber`, `requireBool`, `requireAtLeast`, `requirePositive`, `requireWithinRange`, `requireNonEmpty`, `requireMember`). They raise `TypeError` for the wrong kind of value and `ValueError` for one out of bounds. The validator runs **before** anything is posted, so a refused call announces nothing: a will/did pair means the driver really was invoked. Two rules: limits that vary by model (the SR830's ±10.5 V, the Millennia's 0.05–25 W) stay in the driver's hook, next to the numbers they come from; and a validator calls `do*` hooks, never public methods, so validating never posts a notification of its own. Channel identity is deliberately unchecked — an SR830 addresses outputs with `AuxOutput` members where a LabJack uses bare ints.
|
|
72
|
+
- **The device must be initialized.** `@notifies` calls `validateReady()` first, which raises `PhysicalDevice.NotInitialized` unless the state is `Ready`, naming the operation, the class and the actual state. It runs *before* the `will` is posted, since nothing was attempted, so a guarded call posts nothing at all. `validateReady` is defined once, on `PhysicalDevice`, where the lifecycle lives; `capabilities.py` only calls it. A class that mixes in a capability without being a `PhysicalDevice` must therefore answer for readiness itself — the test stubs that stand in for a device do exactly that. Methods that only report what a model supports (`supported*`, `outletCount`) pass `requiresReady=False`, because a UI populates its menus before connecting.
|
|
73
|
+
- **Capabilities related by inheritance share one enum**, so `notification` is literally the same object on all of them and their members are interchangeable: `AnalogInputCapability`, `AnalogOutputCapability`, `AnalogIOCapability` and `AnalogInputStreamCapability` all post `AnalogNotification`; the digital trio posts `DigitalNotification`. This matters because members are keyed by identity — two same-named members in two enums would never cross-fire, so an observer would otherwise have to know whether a device mixed in the combined capability or the plain one.
|
|
74
|
+
- Members are keyed by enum identity, not by their string value, so same-named members in two enums never cross-fire — which is exactly why the sharing above is necessary rather than cosmetic.
|
|
75
|
+
- Composed hooks nest: `acquireWaveform` posts its own pair plus the pairs of the `configureStream` / `startStream` / `readStream` / `stopStream` calls its default implementation makes.
|
|
76
|
+
|
|
77
|
+
- DAQ: `AnalogInputCapability` (`getAnalogVoltage`), `AnalogOutputCapability` (`setAnalogVoltage`), `DigitalInputCapability` (`getDigitalValue`), `DigitalOutputCapability` (`setDigitalValue`), plus `AnalogIOCapability` / `DigitalIOCapability` that combine each pair, and `AnalogInputStreamCapability` for hardware-timed acquisition. Drivers implement `doGetAnalogVoltage` / `doSetAnalogVoltage` / `doGetDigitalValue` / `doSetDigitalValue` and the streaming hooks; the `doConfigure*` and `do*Direction` hooks are optional no-ops. `configureStream(channels, sampleRate, **parameters)` forwards any extra keyword to `doConfigureStream`, which is how instrument-specific options reach a driver (the SR830's `sampleClock`). Example: `class LabjackDevice(PhysicalDevice, AnalogIOCapability, DigitalIOCapability, AnalogInputStreamCapability)`.
|
|
78
|
+
- Laser sources: `OnOffCapability` (`turnOn`/`turnOff`/`isLaserOn`), `ShutterCapability` (`openShutter`/`closeShutter`/`isShutterOpen`), `PowerCapability` (`setPower`/`power`), `InterlockCapability` (`interlock`), `AutostartCapability`, `WavelengthCapability` (`setWavelength`/`wavelength`), `DispersionCapability`. `LaserSourceDevice` is a thin marker base; the behavior comes from the mixins. Examples: `class CoboltDevice(LaserSourceDevice, OnOffCapability, PowerCapability, ...)`, `class MillenniaEv25Device(LaserSourceDevice, OnOffCapability, ShutterCapability, PowerCapability)`, `class MatisseDevice(PhysicalDevice, WavelengthCapability)`.
|
|
66
79
|
- Power meters: `WavelengthCalibrationCapability`, `AutoScaleCapability`, `ScaleCapability` (on top of the base `doGetAbsolutePower` every meter implements).
|
|
80
|
+
- Lock-in / triggering: `PhaseLockedDetectionCapability`, `TriggerCapability` (`SR830Device`).
|
|
81
|
+
- Power strips: `OutletSwitchingCapability`, `DefaultOutletCapability`, `CurrentMeteringCapability` (`PwrUSBDevice`).
|
|
82
|
+
|
|
83
|
+
To see every capability with the methods it defines and the hooks it requires: `python -m hardwarelibrary --capabilities`, or `allCapabilities()` / `capabilityInterface()` from `hardwarelibrary/capabilities.py`.
|
|
67
84
|
|
|
68
85
|
## Adding a new device
|
|
69
86
|
|