diff --git a/CHANGELOG.md b/CHANGELOG.md index c22c3b03..b5f47138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,11 +21,14 @@ Things to be included in the next release go here. ### Added - Added a config option (`default_visa_timeout`) to specify the default VISA timeout for all initial VISA device connections. +- Added a new function, `register_additional_usbtmc_mapping()`, to enable users to add USBTMC connection information for devices that don't have native support for USBTMC connections in `tm_devices` yet. ### Changed - Switched all workflows to use the new [`tektronix/python-package-ci-cd`](https://github.com/tektronix/python-package-ci-cd) reusable workflows. - Reduced the out-of-the box `default_visa_timeout` value from 30 seconds to 5 seconds. +- _**SEMI-BREAKING CHANGE**_: Changed the `USB_MODEL_ID_LOOKUP` constant to use `SupportedModels` as keys instead of values to make the documentation clearer. +- _**SEMI-BREAKING CHANGE**_: Changed the `DEVICE_DRIVER_MODEL_MAPPING` constant to use `SupportedModels` as keys instead of values to make the documentation clearer. --- diff --git a/docs/_templates/mkdocstrings/python/readthedocs/children.html.jinja b/docs/_templates/mkdocstrings/python/readthedocs/children.html.jinja index c60bae4e..cceed38a 100644 --- a/docs/_templates/mkdocstrings/python/readthedocs/children.html.jinja +++ b/docs/_templates/mkdocstrings/python/readthedocs/children.html.jinja @@ -97,7 +97,7 @@ Context: filters=config.filters, members_list=members_list, inherited_members=config.inherited_members, - keep_no_docstrings=config.show_if_no_docstring)|selectattr("is_init_module", "equalto", false)|list|order_members(config.members_order, members_list) + keep_no_docstrings=config.show_if_no_docstring)|selectattr("is_init_module", "equalto", false)|list|order_members(config.members_order, members_list)|sort(attribute="name") %} {% if modules %} {% filter heading(heading_level, id=html_id ~ "-modules") %}Modules{% endfilter %} diff --git a/examples/miscellaneous/custom_device_driver_support.py b/examples/miscellaneous/custom_device_driver_support.py index 8dfecad3..4812bb00 100644 --- a/examples/miscellaneous/custom_device_driver_support.py +++ b/examples/miscellaneous/custom_device_driver_support.py @@ -2,7 +2,7 @@ from typing import Tuple, Union -from tm_devices import DeviceManager +from tm_devices import DeviceManager, register_additional_usbtmc_mapping from tm_devices.drivers import MSO5 from tm_devices.drivers.pi.pi_device import PIDevice from tm_devices.drivers.pi.scopes.scope import Scope @@ -74,11 +74,16 @@ def custom_device_method(self, value: int) -> None: } +# To enable USBTMC connection support for a device without native USBTMC support in tm_devices, +# simply register the USBTMC connection information for the device's model series. +register_additional_usbtmc_mapping("CustomModelSeries", model_id="0x0000", vendor_id="0x0000") + + with DeviceManager(external_device_drivers=CUSTOM_DEVICE_DRIVERS) as device_manager: # Add a scope that is currently supported by the package mso5: MSO5 = device_manager.add_scope("192.168.0.1") - # Add the custom scope - custom_scope: CustomScope = device_manager.add_scope("192.168.0.2") + # Add the custom scope with a USB connection after registering the USBTMC mapping above + custom_scope: CustomScope = device_manager.add_scope("MODEL-SERIAL", connection_type="USB") # Add the custom device that is a device type not officially supported # NOTE: If using a config file or environment variable to define a device that is unsupported, # the `device_type` key must be set to "UNSUPPORTED". diff --git a/src/tm_devices/__init__.py b/src/tm_devices/__init__.py index 59b910d6..e4e4cd00 100644 --- a/src/tm_devices/__init__.py +++ b/src/tm_devices/__init__.py @@ -20,6 +20,7 @@ SYSTEM_DEFAULT_VISA_BACKEND, ) from tm_devices.helpers.enums import SupportedModels +from tm_devices.helpers.functions import register_additional_usbtmc_mapping # Read version from installed package. __version__ = version(PACKAGE_NAME) @@ -28,6 +29,7 @@ "DeviceManager", "print_available_visa_devices", "PYVISA_PY_BACKEND", + "register_additional_usbtmc_mapping", "SupportedModels", "SYSTEM_DEFAULT_VISA_BACKEND", ] diff --git a/src/tm_devices/device_manager.py b/src/tm_devices/device_manager.py index e6fd3de8..a995c867 100644 --- a/src/tm_devices/device_manager.py +++ b/src/tm_devices/device_manager.py @@ -20,6 +20,11 @@ from tm_devices.drivers.api.rest_api.margin_testers.margin_tester import MarginTester from tm_devices.drivers.api.rest_api.rest_api_device import RESTAPIDevice from tm_devices.drivers.device import Device + +# noinspection PyProtectedMember +from tm_devices.drivers.device_driver_mapping import ( + _DEVICE_DRIVER_MODEL_STR_MAPPING, # pyright: ignore[reportPrivateUsage] +) from tm_devices.drivers.pi.data_acquisition_systems.data_acquisition_system import ( DataAcquisitionSystem, ) @@ -1260,15 +1265,12 @@ def __create_device( SystemError: Indicates something went wrong when creating the device. AssertionError: Indicates something went wrong when creating the device. """ - # pylint: disable=import-outside-toplevel - from tm_devices.drivers import DEVICE_DRIVER_MODEL_MAPPING - if self._external_device_drivers is not None: device_drivers: Mapping[str, Type[Device]] = MappingProxyType( - {**self._external_device_drivers, **DEVICE_DRIVER_MODEL_MAPPING} + {**self._external_device_drivers, **_DEVICE_DRIVER_MODEL_STR_MAPPING} ) else: - device_drivers = DEVICE_DRIVER_MODEL_MAPPING + device_drivers = _DEVICE_DRIVER_MODEL_STR_MAPPING alias_string = f' "{device_config.alias}"' if device_config.alias else "" if device_config.device_type == DeviceTypes.UNSUPPORTED: diff --git a/src/tm_devices/drivers/__init__.py b/src/tm_devices/drivers/__init__.py index b1a592cf..4e97f675 100644 --- a/src/tm_devices/drivers/__init__.py +++ b/src/tm_devices/drivers/__init__.py @@ -109,7 +109,7 @@ from tm_devices.drivers.pi.systems_switches.ss3706a import SS3706A from tm_devices.drivers.api.rest_api.margin_testers.tmt4 import TMT4 -# TODO: remove this function after TekScopeSW is fully removed +# TODO: deprecation: remove this function after TekScopeSW is fully removed if not ("--doctest-modules" in sys.argv and sys.argv[-1] == "src"): # pragma: no cover def __getattr__(name: str) -> Any: diff --git a/src/tm_devices/drivers/device_driver_mapping.py b/src/tm_devices/drivers/device_driver_mapping.py index 0f2bdcb7..8ff3a5fe 100644 --- a/src/tm_devices/drivers/device_driver_mapping.py +++ b/src/tm_devices/drivers/device_driver_mapping.py @@ -105,120 +105,136 @@ from tm_devices.drivers.pi.systems_switches.ss3706a import SS3706A from tm_devices.helpers.enums import SupportedModels -DEVICE_DRIVER_MODEL_MAPPING: Mapping[str, Type[Device]] = MappingProxyType( +# IMPORTANT: Any additions to this class which support a USBTMC connection need to be added to the +# tm_devices.helpers.constants_and_dataclasses.USB_MODEL_ID_LOOKUP constant as well. +DEVICE_DRIVER_MODEL_MAPPING: Mapping[SupportedModels, Type[Device]] = MappingProxyType( { # AFGs - SupportedModels.AFG3K.value: AFG3K, - SupportedModels.AFG3KB.value: AFG3KB, - SupportedModels.AFG3KC.value: AFG3KC, - SupportedModels.AFG31K.value: AFG31K, + SupportedModels.AFG3K: AFG3K, + SupportedModels.AFG3KB: AFG3KB, + SupportedModels.AFG3KC: AFG3KC, + SupportedModels.AFG31K: AFG31K, # AWGs - SupportedModels.AWG5200.value: AWG5200, - SupportedModels.AWG5K.value: AWG5K, - SupportedModels.AWG5KB.value: AWG5KB, - SupportedModels.AWG5KC.value: AWG5KC, - SupportedModels.AWG7K.value: AWG7K, - SupportedModels.AWG7KB.value: AWG7KB, - SupportedModels.AWG7KC.value: AWG7KC, - SupportedModels.AWG70KA.value: AWG70KA, - SupportedModels.AWG70KB.value: AWG70KB, + SupportedModels.AWG5200: AWG5200, + SupportedModels.AWG5K: AWG5K, + SupportedModels.AWG5KB: AWG5KB, + SupportedModels.AWG5KC: AWG5KC, + SupportedModels.AWG7K: AWG7K, + SupportedModels.AWG7KB: AWG7KB, + SupportedModels.AWG7KC: AWG7KC, + SupportedModels.AWG70KA: AWG70KA, + SupportedModels.AWG70KB: AWG70KB, # Scopes - SupportedModels.DPO5K.value: DPO5K, - SupportedModels.DPO5KB.value: DPO5KB, - SupportedModels.DPO7K.value: DPO7K, - SupportedModels.DPO7KC.value: DPO7KC, - SupportedModels.DPO70K.value: DPO70K, - SupportedModels.DPO70KC.value: DPO70KC, - SupportedModels.DPO70KD.value: DPO70KD, - SupportedModels.DPO70KDX.value: DPO70KDX, - SupportedModels.DPO70KSX.value: DPO70KSX, - SupportedModels.DSA70K.value: DSA70K, - SupportedModels.DSA70KC.value: DSA70KC, - SupportedModels.DSA70KD.value: DSA70KD, - SupportedModels.LPD6.value: LPD6, - SupportedModels.MSO2K.value: MSO2K, - SupportedModels.MSO2KB.value: MSO2KB, - SupportedModels.DPO2K.value: DPO2K, - SupportedModels.DPO2KB.value: DPO2KB, - SupportedModels.MDO3.value: MDO3, - SupportedModels.MDO3K.value: MDO3K, - SupportedModels.MDO4K.value: MDO4K, - SupportedModels.MDO4KB.value: MDO4KB, - SupportedModels.MDO4KC.value: MDO4KC, - SupportedModels.MSO4K.value: MSO4K, - SupportedModels.MSO4KB.value: MSO4KB, - SupportedModels.DPO4K.value: DPO4K, - SupportedModels.DPO4KB.value: DPO4KB, - SupportedModels.MSO2.value: MSO2, - SupportedModels.MSO4.value: MSO4, - SupportedModels.MSO4B.value: MSO4B, - SupportedModels.MSO5.value: MSO5, - SupportedModels.MSO5B.value: MSO5B, - SupportedModels.MSO5LP.value: MSO5LP, - SupportedModels.MSO6.value: MSO6, - SupportedModels.MSO6B.value: MSO6B, - SupportedModels.MSO5K.value: MSO5K, - SupportedModels.MSO5KB.value: MSO5KB, - SupportedModels.MSO70K.value: MSO70K, - SupportedModels.MSO70KC.value: MSO70KC, - SupportedModels.MSO70KDX.value: MSO70KDX, - SupportedModels.TEKSCOPEPC.value: TekScopePC, - SupportedModels.TSOVU.value: TSOVu, + SupportedModels.DPO5K: DPO5K, + SupportedModels.DPO5KB: DPO5KB, + SupportedModels.DPO7K: DPO7K, + SupportedModels.DPO7KC: DPO7KC, + SupportedModels.DPO70K: DPO70K, + SupportedModels.DPO70KC: DPO70KC, + SupportedModels.DPO70KD: DPO70KD, + SupportedModels.DPO70KDX: DPO70KDX, + SupportedModels.DPO70KSX: DPO70KSX, + SupportedModels.DSA70K: DSA70K, + SupportedModels.DSA70KC: DSA70KC, + SupportedModels.DSA70KD: DSA70KD, + SupportedModels.LPD6: LPD6, + SupportedModels.MSO2K: MSO2K, + SupportedModels.MSO2KB: MSO2KB, + SupportedModels.DPO2K: DPO2K, + SupportedModels.DPO2KB: DPO2KB, + SupportedModels.MDO3: MDO3, + SupportedModels.MDO3K: MDO3K, + SupportedModels.MDO4K: MDO4K, + SupportedModels.MDO4KB: MDO4KB, + SupportedModels.MDO4KC: MDO4KC, + SupportedModels.MSO4K: MSO4K, + SupportedModels.MSO4KB: MSO4KB, + SupportedModels.DPO4K: DPO4K, + SupportedModels.DPO4KB: DPO4KB, + SupportedModels.MSO2: MSO2, + SupportedModels.MSO4: MSO4, + SupportedModels.MSO4B: MSO4B, + SupportedModels.MSO5: MSO5, + SupportedModels.MSO5B: MSO5B, + SupportedModels.MSO5LP: MSO5LP, + SupportedModels.MSO6: MSO6, + SupportedModels.MSO6B: MSO6B, + SupportedModels.MSO5K: MSO5K, + SupportedModels.MSO5KB: MSO5KB, + SupportedModels.MSO70K: MSO70K, + SupportedModels.MSO70KC: MSO70KC, + SupportedModels.MSO70KDX: MSO70KDX, + SupportedModels.TEKSCOPEPC: TekScopePC, + SupportedModels.TSOVU: TSOVu, # Margin Testers - SupportedModels.TMT4.value: TMT4, + SupportedModels.TMT4: TMT4, # Source Measure Units - SupportedModels.SMU2400.value: SMU2400, - SupportedModels.SMU2401.value: SMU2401, - SupportedModels.SMU2410.value: SMU2410, - SupportedModels.SMU2450.value: SMU2450, - SupportedModels.SMU2460.value: SMU2460, - SupportedModels.SMU2461.value: SMU2461, - SupportedModels.SMU2470.value: SMU2470, - SupportedModels.SMU2601B.value: SMU2601B, - SupportedModels.SMU2601B_PULSE.value: SMU2601BPulse, - SupportedModels.SMU2602B.value: SMU2602B, - SupportedModels.SMU2604B.value: SMU2604B, - SupportedModels.SMU2606B.value: SMU2606B, - SupportedModels.SMU2611B.value: SMU2611B, - SupportedModels.SMU2612B.value: SMU2612B, - SupportedModels.SMU2614B.value: SMU2614B, - SupportedModels.SMU2634B.value: SMU2634B, - SupportedModels.SMU2635B.value: SMU2635B, - SupportedModels.SMU2636B.value: SMU2636B, - SupportedModels.SMU2651A.value: SMU2651A, - SupportedModels.SMU2657A.value: SMU2657A, - SupportedModels.SMU2601A.value: SMU2601A, - SupportedModels.SMU2602A.value: SMU2602A, - SupportedModels.SMU2604A.value: SMU2604A, - SupportedModels.SMU2611A.value: SMU2611A, - SupportedModels.SMU2612A.value: SMU2612A, - SupportedModels.SMU2614A.value: SMU2614A, - SupportedModels.SMU2634A.value: SMU2634A, - SupportedModels.SMU2635A.value: SMU2635A, - SupportedModels.SMU2636A.value: SMU2636A, - SupportedModels.SMU6430.value: SMU6430, - SupportedModels.SMU6514.value: SMU6514, - SupportedModels.SMU6517B.value: SMU6517B, + SupportedModels.SMU2400: SMU2400, + SupportedModels.SMU2401: SMU2401, + SupportedModels.SMU2410: SMU2410, + SupportedModels.SMU2450: SMU2450, + SupportedModels.SMU2460: SMU2460, + SupportedModels.SMU2461: SMU2461, + SupportedModels.SMU2470: SMU2470, + SupportedModels.SMU2601B: SMU2601B, + SupportedModels.SMU2601B_PULSE: SMU2601BPulse, + SupportedModels.SMU2602B: SMU2602B, + SupportedModels.SMU2604B: SMU2604B, + SupportedModels.SMU2606B: SMU2606B, + SupportedModels.SMU2611B: SMU2611B, + SupportedModels.SMU2612B: SMU2612B, + SupportedModels.SMU2614B: SMU2614B, + SupportedModels.SMU2634B: SMU2634B, + SupportedModels.SMU2635B: SMU2635B, + SupportedModels.SMU2636B: SMU2636B, + SupportedModels.SMU2651A: SMU2651A, + SupportedModels.SMU2657A: SMU2657A, + SupportedModels.SMU2601A: SMU2601A, + SupportedModels.SMU2602A: SMU2602A, + SupportedModels.SMU2604A: SMU2604A, + SupportedModels.SMU2611A: SMU2611A, + SupportedModels.SMU2612A: SMU2612A, + SupportedModels.SMU2614A: SMU2614A, + SupportedModels.SMU2634A: SMU2634A, + SupportedModels.SMU2635A: SMU2635A, + SupportedModels.SMU2636A: SMU2636A, + SupportedModels.SMU6430: SMU6430, + SupportedModels.SMU6514: SMU6514, + SupportedModels.SMU6517B: SMU6517B, # Power Supplies - SupportedModels.PSU2200.value: PSU2200, - SupportedModels.PSU2220.value: PSU2220, - SupportedModels.PSU2230.value: PSU2230, - SupportedModels.PSU2231.value: PSU2231, - SupportedModels.PSU2231A.value: PSU2231A, - SupportedModels.PSU2280.value: PSU2280, - SupportedModels.PSU2281.value: PSU2281, + SupportedModels.PSU2200: PSU2200, + SupportedModels.PSU2220: PSU2220, + SupportedModels.PSU2230: PSU2230, + SupportedModels.PSU2231: PSU2231, + SupportedModels.PSU2231A: PSU2231A, + SupportedModels.PSU2280: PSU2280, + SupportedModels.PSU2281: PSU2281, # Digital Multimeters - SupportedModels.DMM6500.value: DMM6500, - SupportedModels.DMM7510.value: DMM7510, - SupportedModels.DMM7512.value: DMM7512, + SupportedModels.DMM6500: DMM6500, + SupportedModels.DMM7510: DMM7510, + SupportedModels.DMM7512: DMM7512, # Data Acquisition System - SupportedModels.DAQ6510.value: DAQ6510, + SupportedModels.DAQ6510: DAQ6510, # Systems Switches - SupportedModels.SS3706A.value: SS3706A, + SupportedModels.SS3706A: SS3706A, } ) """A mapping of device model series names to their device driver objects. Any additions to this class which support a USBTMC connection need to be added to the [`tm_devices.helpers.constants_and_dataclasses.USB_MODEL_ID_LOOKUP`][] constant as well. + +!!! danger "Deprecated" + This mapping is deprecated since it is only used internally by the + [`DeviceManager`][tm_devices.DeviceManager]. """ + +#################################################################################################### +# Private Attributes +#################################################################################################### +# TODO: deprecation: Move the contents of DEVICE_DRIVER_MODEL_MAPPING into this attribute, +# remove the old DEVICE_DRIVER_MODEL_MAPPING constant, and make this entire module (file) private +# in the next major release +_DEVICE_DRIVER_MODEL_STR_MAPPING: "Mapping[str, Type[Device]]" = MappingProxyType( + {key.value: value for key, value in DEVICE_DRIVER_MODEL_MAPPING.items()} +) diff --git a/src/tm_devices/drivers/pi/_base_afg_source_channel.py b/src/tm_devices/drivers/pi/base_afg_source_channel.py similarity index 97% rename from src/tm_devices/drivers/pi/_base_afg_source_channel.py rename to src/tm_devices/drivers/pi/base_afg_source_channel.py index 64a25c4b..0dfc0bb6 100644 --- a/src/tm_devices/drivers/pi/_base_afg_source_channel.py +++ b/src/tm_devices/drivers/pi/base_afg_source_channel.py @@ -3,7 +3,7 @@ from abc import abstractmethod from typing import Literal -from tm_devices.drivers.pi._base_source_channel import BaseSourceChannel +from tm_devices.drivers.pi.base_source_channel import BaseSourceChannel from tm_devices.drivers.pi.pi_device import PIDevice from tm_devices.helpers.enums import SignalGeneratorFunctionBase diff --git a/src/tm_devices/drivers/pi/_base_source_channel.py b/src/tm_devices/drivers/pi/base_source_channel.py similarity index 100% rename from src/tm_devices/drivers/pi/_base_source_channel.py rename to src/tm_devices/drivers/pi/base_source_channel.py diff --git a/src/tm_devices/drivers/pi/scopes/tekscope/tekscope.py b/src/tm_devices/drivers/pi/scopes/tekscope/tekscope.py index d8104db8..cf574269 100644 --- a/src/tm_devices/drivers/pi/scopes/tekscope/tekscope.py +++ b/src/tm_devices/drivers/pi/scopes/tekscope/tekscope.py @@ -41,7 +41,7 @@ ) from tm_devices.driver_mixins.usb_drives_mixin import USBDrivesMixin from tm_devices.drivers.device import family_base_class -from tm_devices.drivers.pi._base_afg_source_channel import BaseAFGSourceChannel +from tm_devices.drivers.pi.base_afg_source_channel import BaseAFGSourceChannel from tm_devices.drivers.pi.scopes.scope import Scope from tm_devices.helpers import DeviceConfigEntry, LoadImpedanceAFG diff --git a/src/tm_devices/drivers/pi/scopes/tekscope/tekscopesw.py b/src/tm_devices/drivers/pi/scopes/tekscope/tekscopesw.py index f5acac39..a85ee401 100644 --- a/src/tm_devices/drivers/pi/scopes/tekscope/tekscopesw.py +++ b/src/tm_devices/drivers/pi/scopes/tekscope/tekscopesw.py @@ -1,5 +1,5 @@ """TekScopePC device driver module.""" -# TODO: rename file after TekScopeSW is fully removed +# TODO: deprecation: rename file after TekScopeSW is fully removed import pyvisa as visa diff --git a/src/tm_devices/drivers/pi/signal_generators/afgs/afg.py b/src/tm_devices/drivers/pi/signal_generators/afgs/afg.py index 25e4c129..0a9ffbda 100644 --- a/src/tm_devices/drivers/pi/signal_generators/afgs/afg.py +++ b/src/tm_devices/drivers/pi/signal_generators/afgs/afg.py @@ -13,7 +13,7 @@ SourceDeviceConstants, ) from tm_devices.drivers.device import family_base_class -from tm_devices.drivers.pi._base_afg_source_channel import BaseAFGSourceChannel +from tm_devices.drivers.pi.base_afg_source_channel import BaseAFGSourceChannel from tm_devices.drivers.pi.signal_generators.signal_generator import SignalGenerator from tm_devices.helpers import DeviceTypes, LoadImpedanceAFG diff --git a/src/tm_devices/drivers/pi/signal_generators/awgs/awg.py b/src/tm_devices/drivers/pi/signal_generators/awgs/awg.py index 3c9f77e8..dff912ef 100644 --- a/src/tm_devices/drivers/pi/signal_generators/awgs/awg.py +++ b/src/tm_devices/drivers/pi/signal_generators/awgs/awg.py @@ -12,7 +12,7 @@ SourceDeviceConstants, ) from tm_devices.drivers.device import family_base_class -from tm_devices.drivers.pi._base_source_channel import BaseSourceChannel +from tm_devices.drivers.pi.base_source_channel import BaseSourceChannel from tm_devices.drivers.pi.signal_generators.signal_generator import SignalGenerator from tm_devices.helpers import DeviceTypes, LoadImpedanceAFG diff --git a/src/tm_devices/helpers/__init__.py b/src/tm_devices/helpers/__init__.py index 6c6ded24..01ed7e9f 100644 --- a/src/tm_devices/helpers/__init__.py +++ b/src/tm_devices/helpers/__init__.py @@ -35,6 +35,7 @@ get_visa_backend, ping_address, print_with_timestamp, + register_additional_usbtmc_mapping, sanitize_enum, ) from tm_devices.helpers.read_only_cached_property import ReadOnlyCachedProperty @@ -62,6 +63,7 @@ "ping_address", "print_with_timestamp", "PYVISA_PY_BACKEND", + "register_additional_usbtmc_mapping", "sanitize_enum", "SerialConfig", "Singleton", diff --git a/src/tm_devices/helpers/constants_and_dataclasses.py b/src/tm_devices/helpers/constants_and_dataclasses.py index 00bfbf52..8fe69b3d 100644 --- a/src/tm_devices/helpers/constants_and_dataclasses.py +++ b/src/tm_devices/helpers/constants_and_dataclasses.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from types import MappingProxyType -from typing import Final, FrozenSet, List, Mapping, Optional, Tuple, Union +from typing import Dict, Final, FrozenSet, List, Mapping, Optional, Tuple, Union from pyvisa import constants as pyvisa_constants @@ -378,8 +378,12 @@ def get_visa_resource_expression(self) -> str: from tm_devices.helpers.functions import get_model_series model_series = get_model_series(full_model) + model_id_lookup = { + **_externally_registered_usbtmc_model_id_lookup, + **_USB_MODEL_ID_STR_LOOKUP, + } try: - usbtmc_config = USB_MODEL_ID_LOOKUP[model_series] + usbtmc_config = model_id_lookup[model_series] # This commented out block of code can help deal with devices where the specific # model number is the USBTMC model_id but the Python driver is shared between @@ -577,151 +581,158 @@ def __str__(self) -> str: """ # USBTMC configuration defines -_TEKTRONIX_USBTMC_VENDOR_ID: Final[str] = "0x0699" -_KEITHLEY_USBTMC_VENDOR_ID: Final[str] = "0x05E6" -USB_MODEL_ID_LOOKUP: Final[Mapping[str, USBTMCConfiguration]] = MappingProxyType( +TEKTRONIX_USBTMC_VENDOR_ID: Final[str] = "0x0699" +"""The USBTMC Vendor ID for Tektronix devices.""" +KEITHLEY_USBTMC_VENDOR_ID: Final[str] = "0x05E6" +"""The USBTMC Vendor ID for Keithley devices.""" +USB_MODEL_ID_LOOKUP: Final[Mapping[SupportedModels, USBTMCConfiguration]] = MappingProxyType( { - SupportedModels.MDO3K.value: USBTMCConfiguration( - vendor_id=_TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0408" + SupportedModels.MDO3K: USBTMCConfiguration( + vendor_id=TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0408" ), - SupportedModels.MSO2.value: USBTMCConfiguration( - vendor_id=_TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0105" + SupportedModels.MSO2: USBTMCConfiguration( + vendor_id=TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0105" ), - SupportedModels.MSO2KB.value: USBTMCConfiguration( - vendor_id=_TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x03A4" + SupportedModels.MSO2KB: USBTMCConfiguration( + vendor_id=TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x03A4" ), - SupportedModels.MSO4.value: USBTMCConfiguration( - vendor_id=_TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0527" + SupportedModels.MSO4: USBTMCConfiguration( + vendor_id=TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0527" ), - SupportedModels.MSO4B.value: USBTMCConfiguration( - vendor_id=_TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0527" + SupportedModels.MSO4B: USBTMCConfiguration( + vendor_id=TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0527" ), - SupportedModels.MSO5.value: USBTMCConfiguration( - vendor_id=_TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0522" + SupportedModels.MSO5: USBTMCConfiguration( + vendor_id=TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0522" ), - SupportedModels.MSO5B.value: USBTMCConfiguration( - vendor_id=_TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0522" + SupportedModels.MSO5B: USBTMCConfiguration( + vendor_id=TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0522" ), - SupportedModels.MSO5LP.value: USBTMCConfiguration( - vendor_id=_TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0529" + SupportedModels.MSO5LP: USBTMCConfiguration( + vendor_id=TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0529" ), - SupportedModels.MSO6.value: USBTMCConfiguration( - vendor_id=_TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0528" + SupportedModels.MSO6: USBTMCConfiguration( + vendor_id=TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0528" ), - SupportedModels.MSO6B.value: USBTMCConfiguration( - vendor_id=_TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0530" + SupportedModels.MSO6B: USBTMCConfiguration( + vendor_id=TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0530" ), - SupportedModels.LPD6.value: USBTMCConfiguration( - vendor_id=_TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x052F" + SupportedModels.LPD6: USBTMCConfiguration( + vendor_id=TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x052F" ), - SupportedModels.AFG3K.value: USBTMCConfiguration( - vendor_id=_TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0345" + SupportedModels.AFG3K: USBTMCConfiguration( + vendor_id=TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0345" ), - SupportedModels.SMU2450.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2450" + SupportedModels.SMU2450: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2450" ), - SupportedModels.SMU2460.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2460" + SupportedModels.SMU2460: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2460" ), - SupportedModels.SMU2461.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2461" + SupportedModels.SMU2461: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2461" ), - SupportedModels.SMU2470.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2470" + SupportedModels.SMU2470: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2470" ), - SupportedModels.SMU2601A.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2601" + SupportedModels.SMU2601A: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2601" ), - SupportedModels.SMU2602A.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2602" + SupportedModels.SMU2602A: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2602" ), - SupportedModels.SMU2604A.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2604" + SupportedModels.SMU2604A: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2604" ), - SupportedModels.SMU2611A.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2611" + SupportedModels.SMU2611A: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2611" ), - SupportedModels.SMU2612A.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2612" + SupportedModels.SMU2612A: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2612" ), - SupportedModels.SMU2614A.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2614" + SupportedModels.SMU2614A: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2614" ), - SupportedModels.SMU2634A.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2634" + SupportedModels.SMU2634A: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2634" ), - SupportedModels.SMU2635A.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2635" + SupportedModels.SMU2635A: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2635" ), - SupportedModels.SMU2636A.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2636" + SupportedModels.SMU2636A: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2636" ), - SupportedModels.SMU2601B.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2601" + SupportedModels.SMU2601B: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2601" ), - SupportedModels.SMU2602B.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2602" + SupportedModels.SMU2602B: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2602" ), - SupportedModels.SMU2604B.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2604" + SupportedModels.SMU2604B: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2604" ), - SupportedModels.SMU2606B.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2606" + SupportedModels.SMU2606B: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2606" ), - SupportedModels.SMU2611B.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2611" + SupportedModels.SMU2611B: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2611" ), - SupportedModels.SMU2612B.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2612" + SupportedModels.SMU2612B: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2612" ), - SupportedModels.SMU2614B.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2614" + SupportedModels.SMU2614B: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2614" ), - SupportedModels.SMU2634B.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2634" + SupportedModels.SMU2634B: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2634" ), - SupportedModels.SMU2635B.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2635" + SupportedModels.SMU2635B: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2635" ), - SupportedModels.SMU2636B.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2636" + SupportedModels.SMU2636B: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2636" ), - SupportedModels.PSU2200.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2200" + SupportedModels.PSU2200: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2200" ), - SupportedModels.PSU2220.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2220" + SupportedModels.PSU2220: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2220" ), - SupportedModels.PSU2230.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2230" + SupportedModels.PSU2230: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2230" ), - SupportedModels.PSU2231.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2231" + SupportedModels.PSU2231: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2231" ), - SupportedModels.PSU2231A.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2231" + SupportedModels.PSU2231A: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2231" ), - SupportedModels.PSU2280.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2280" + SupportedModels.PSU2280: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2280" ), - SupportedModels.PSU2281.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2281" + SupportedModels.PSU2281: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x2281" ), - SupportedModels.AWG5200.value: USBTMCConfiguration( - vendor_id=_TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0503" + SupportedModels.AWG5200: USBTMCConfiguration( + vendor_id=TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0503" ), - SupportedModels.AWG70KA.value: USBTMCConfiguration( - vendor_id=_TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0503" + SupportedModels.AWG70KA: USBTMCConfiguration( + vendor_id=TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0503" ), - SupportedModels.AWG70KB.value: USBTMCConfiguration( - vendor_id=_TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0503" + SupportedModels.AWG70KB: USBTMCConfiguration( + vendor_id=TEKTRONIX_USBTMC_VENDOR_ID, model_id="0x0503" ), - SupportedModels.SS3706A.value: USBTMCConfiguration( - vendor_id=_KEITHLEY_USBTMC_VENDOR_ID, model_id="0x3706" + SupportedModels.SS3706A: USBTMCConfiguration( + vendor_id=KEITHLEY_USBTMC_VENDOR_ID, model_id="0x3706" ), } ) -"""A mapping of model USBTMC info.""" +"""A mapping of device model series to their USBTMC connection information. + +This lists the natively supported USBTMC connections of `tm_devices`, use +[``register_additional_usbtmc_model_series()``][tm_devices.helpers.functions.register_additional_usbtmc_mapping] +to register USBTMC connection information for devices not listed here. +""" LOAD_IMPEDANCE_LOOKUP: Final[Mapping[Union[float, str], LoadImpedanceAFG]] = MappingProxyType( { @@ -733,3 +744,12 @@ def __str__(self) -> str: } ) """Conversions of literal values representing impedances to Enum values representing impedances.""" + + +#################################################################################################### +# Private Attributes +#################################################################################################### +_externally_registered_usbtmc_model_id_lookup: Dict[str, USBTMCConfiguration] = {} +_USB_MODEL_ID_STR_LOOKUP: Mapping[str, USBTMCConfiguration] = MappingProxyType( + {key.value: value for key, value in USB_MODEL_ID_LOOKUP.items()} +) diff --git a/src/tm_devices/helpers/functions.py b/src/tm_devices/helpers/functions.py index 704f1cb5..0fe81691 100644 --- a/src/tm_devices/helpers/functions.py +++ b/src/tm_devices/helpers/functions.py @@ -22,11 +22,13 @@ from packaging.version import InvalidVersion, Version from tm_devices.helpers.constants_and_dataclasses import ( + _externally_registered_usbtmc_model_id_lookup, # pyright: ignore[reportPrivateUsage] + _USB_MODEL_ID_STR_LOOKUP, # pyright: ignore[reportPrivateUsage] ConnectionTypes, DeviceConfigEntry, PACKAGE_NAME, PYVISA_PY_BACKEND, - USB_MODEL_ID_LOOKUP, + USBTMCConfiguration, VISA_RESOURCE_EXPRESSION_REGEX, ) from tm_devices.helpers.enums import CustomStrEnum, SupportedModels @@ -389,9 +391,13 @@ def detect_visa_resource_expression(input_str: str) -> Optional[Tuple[str, str]] while unneeded_part in match_groups_list: match_groups_list.remove(unneeded_part) # Check if the model is in the USB model lookup + model_id_lookup = { + **_externally_registered_usbtmc_model_id_lookup, + **_USB_MODEL_ID_STR_LOOKUP, + } filtered_usb_model_keys = [ key - for key, value in USB_MODEL_ID_LOOKUP.items() + for key, value in model_id_lookup.items() if value.model_id == match_groups_list[1].lower() ] if filtered_usb_model_keys: @@ -569,6 +575,28 @@ def print_with_timestamp(message: str, end: str = "\n") -> str: return message +def register_additional_usbtmc_mapping(model_series: str, *, model_id: str, vendor_id: str) -> None: + """Register USBTMC connection information for a device that doesn't have native `tm_devices` USBTMC support. + + This function adds an additional mapping between the given ``model_series`` and the USBTMC + connection information provided. This mapping can then be used by ``tm_devices`` to create a + USBTMC connection to the device. + + Args: + model_series: The model series string. For VISA devices, the model series is based on the + model that is returned from the ``*IDN?`` query (See the + [``get_model_series()``][tm_devices.helpers.get_model_series] function for details). For + REST API devices, the model series is provided via the + [``device_driver``][tm_devices.helpers.constants_and_dataclasses.DeviceConfigEntry.device_driver] + parameter in the configuration file, environment variable, or Python code. + model_id: The hexadecimal Model ID used to create the USBTMC resource expression. + vendor_id: The hexadecimal Vendor ID used to create the USBTMC resource expression. + """ # noqa: E501 + _externally_registered_usbtmc_model_id_lookup[model_series] = USBTMCConfiguration( + model_id=model_id, vendor_id=vendor_id + ) + + def sanitize_enum(value: str, enum_class: Type[CustomStrEnum]) -> CustomStrEnum: """Sanitize a string value into its enum value. diff --git a/tests/sim_devices/devices.yaml b/tests/sim_devices/devices.yaml index dd2b2466..b922b8d9 100644 --- a/tests/sim_devices/devices.yaml +++ b/tests/sim_devices/devices.yaml @@ -83,6 +83,9 @@ resources: TCPIP::LONGNAMEINSTRUMENT-HOSTNAME::INSTR: device: longNameInstrument filename: scope/longNameInstrument.yaml + USB0::0x0699::0x0527::NO_SERIAL::INSTR: + device: longNameInstrument + filename: scope/longNameInstrument.yaml # --- unsupported device type --- TCPIP::UNSUPPORTED-DEVICE-TYPE::INSTR: diff --git a/tests/test_scopes.py b/tests/test_scopes.py index 4debce3d..dec294d3 100644 --- a/tests/test_scopes.py +++ b/tests/test_scopes.py @@ -14,7 +14,7 @@ from packaging.version import Version -from tm_devices import DeviceManager +from tm_devices import DeviceManager, register_additional_usbtmc_mapping from tm_devices.drivers import MSO2, MSO2KB, MSO5, MSO5B, MSO6, MSO70KDX, TekScopePC from tm_devices.drivers.pi.scopes.tekscope.tekscope import ( ExtendedSourceDeviceConstants, @@ -23,6 +23,7 @@ TekScope, TekScopeChannel, ) +from tm_devices.helpers.constants_and_dataclasses import TEKTRONIX_USBTMC_VENDOR_ID from tm_devices.helpers.enums import SignalGeneratorFunctionsIAFG @@ -421,6 +422,9 @@ def test_long_device_name(device_manager: DeviceManager) -> None: Args: device_manager: The DeviceManager object. """ + register_additional_usbtmc_mapping( + "LONGNAMEINSTRUMENT", model_id="0x0527", vendor_id=TEKTRONIX_USBTMC_VENDOR_ID + ) # Custom class for testing external device drivers try: device_manager._external_device_drivers = { # noqa: SLF001 @@ -428,7 +432,7 @@ def test_long_device_name(device_manager: DeviceManager) -> None: } with pytest.warns(UserWarning): - scope = device_manager.add_scope("LONGNAMEINSTRUMENT-HOSTNAME") + scope = device_manager.add_scope("LONGNAMEINSTRUMENT-NO_SERIAL", connection_type="USB") assert not scope.total_channels assert scope.all_channel_names_list == () diff --git a/tests/test_tm_devices.py b/tests/test_tm_devices.py index 6c770bf0..a7cd5b53 100644 --- a/tests/test_tm_devices.py +++ b/tests/test_tm_devices.py @@ -155,7 +155,9 @@ def test_device_method_abstraction() -> None: def test_supported_models_in_device_driver_mapping() -> None: """Verify that all supported models are in the device driver mapping and drivers init file.""" supported_models_list = sorted(x.value for x in tm_devices.SupportedModels) - device_driver_list = sorted(tm_devices.drivers.DEVICE_DRIVER_MODEL_MAPPING) + device_driver_list = sorted( + tm_devices.drivers.device_driver_mapping._DEVICE_DRIVER_MODEL_STR_MAPPING # noqa: SLF001 + ) module_list: List[str] = list(tm_devices.drivers.__all__) # Remove a few non-driver items module_list.remove("DEVICE_DRIVER_MODEL_MAPPING")