Basic Usage¶
A collection of examples showing the basics of how to use tm_devices in a
project.
List available VISA devices¶
This will print the available VISA devices to the console when run from a shell terminal.
$ list-visa-resources
[
"TCPIP0::192.168.0.1::inst0::INSTR",
"ASRL4::INSTR"
]
Adding devices¶
Configure device connections as needed using the config file, an environment variable, or via Python code (shown here). See the Configuration guide for more information on how to configure devices to connect with.
"""An example of adding devices via Python code."""
from tm_devices import DeviceManager
from tm_devices.drivers import AWG5K, MSO5, MSO6B, PSU2200, SMU2470, TMT4
from tm_devices.helpers import (
DMConfigOptions,
PYVISA_PY_BACKEND,
SerialConfig,
SYSTEM_DEFAULT_VISA_BACKEND,
)
# Specific config options can optionally be passed in when creating
# the DeviceManager via a dataclass, they are used to update any existing
# configuration options from a config file.
CONFIG_OPTIONS = DMConfigOptions(
setup_cleanup=True, # update the value for this option, all other options will remain untouched
)
# Create the DeviceManager, turning on verbosity and passing in some specific configuration values.
with DeviceManager(
verbose=True, # optional argument
config_options=CONFIG_OPTIONS, # optional argument
) as device_manager:
# Explicitly specify to use the system VISA backend, this is the default,
# **this code is not required** to use the system default.
device_manager.visa_library = SYSTEM_DEFAULT_VISA_BACKEND
# Enable resetting the devices when connecting and closing
device_manager.setup_cleanup_enabled = True
device_manager.teardown_cleanup_enabled = True
# Note: USB and GPIB connections are not supported with PyVISA-py backend
psu: PSU2200 = device_manager.add_psu("MODEL-SERIAL", connection_type="USB")
# Use the PyVISA-py backend
device_manager.visa_library = PYVISA_PY_BACKEND
# Add a device using a hostname
scope: MSO5 = device_manager.add_scope("MSO56-100083")
print(scope)
# Add a device using an IP address, specific LAN device endpoint, and optional alias
awg: AWG5K = device_manager.add_awg("192.168.0.1", lan_device_endpoint="inst0", alias="AWG5k")
print(awg)
# Add a device using a VISA resource address string,
# it auto-detects the connection type is TCPIP.
scope_2: MSO6B = device_manager.add_scope("TCPIP0::192.168.0.3::inst0::INSTR")
# Add a device using a VISA resource address string involving a socket connection
mf_1 = device_manager.add_mf("TCPIP0::192.168.0.4::4000::SOCKET")
print(mf_1)
# Add a device using an IP address and optional alias and socket port
mt: TMT4 = device_manager.add_mt("192.168.0.2", "TMT4", alias="margin tester", port=5000)
# Add a device using a serial connection, define a SerialConfig for serial settings
serial_settings = SerialConfig(
baud_rate=9600,
data_bits=8,
flow_control=SerialConfig.FlowControl.xon_xoff,
parity=SerialConfig.Parity.none,
stop_bits=SerialConfig.StopBits.one,
end_input=SerialConfig.Termination.none,
)
smu: SMU2470 = device_manager.add_smu(
"1", connection_type="SERIAL", serial_config=serial_settings
)
# Remove devices
device_manager.remove_all_devices()
Access a module in a mainframe¶
The DeviceManager can be used to connect to a Mainframe, and then its modules’ commands can be accessed via the Mainframe driver object.
"""Add a mainframe to the Device Manager and access a PSU module through the mainframe."""
from typing import cast, TYPE_CHECKING
from tm_devices import DeviceManager
from tm_devices.drivers import MP5103
if TYPE_CHECKING:
from tm_devices.commands import MPSU50_2STCommands
with DeviceManager(verbose=True) as device_manager:
# Add a mainframe to the device manager and access its commands.
mainframe: MP5103 = device_manager.add_mf("192.168.0.1")
# Some examples demonstrating the usage of mainframe level commands.
mf_model = mainframe.commands.localnode.model
value = mainframe.commands.eventlog.count
# Get access to the psu module command object available in third slot of the mainframe.
modular_psu = cast("MPSU50_2STCommands", mainframe.get_module_commands_psu(slot=3))
# Some examples demonstrating the usage of module level commands.
# Get the psu model and version
psu_model = modular_psu.model
psu_version = modular_psu.version
modular_psu.firmware.verify()
# Some examples demonstrating the usage of channel level commands.
# Set the measurement aperture in seconds
modular_psu.psu[1].measure.count = 5
# Enable the source output
modular_psu.psu[2].source.output = 1
# Set the offset value used for voltage measurements
rel_value = modular_psu.psu[1].measure.rel.levelv
# Create a reference to the default buffer
my_buffer = modular_psu.psu[1].defbuffer1
# Read the value in the specified reading buffer
# Measure the voltage on channel 1 of the PSU
voltage_value = modular_psu.psu[1].measure.v()
VISA backend selection¶
The DeviceManager can be configured to use VISA backends from different VISA implementations.
"""An example script to choose visa from different visa resources."""
from tm_devices import DeviceManager
from tm_devices.drivers import MSO4B
from tm_devices.helpers import PYVISA_PY_BACKEND, SYSTEM_DEFAULT_VISA_BACKEND
with DeviceManager(verbose=True) as device_manager:
# Explicitly specify to use the system VISA backend, this is the default,
# **this code is not required** to use the system default.
device_manager.visa_library = SYSTEM_DEFAULT_VISA_BACKEND
# The above code can also be replaced by:
device_manager.visa_library = "@ivi"
# To use the PyVISA-py backend
device_manager.visa_library = PYVISA_PY_BACKEND
# The above code can also be replaced by:
device_manager.visa_library = "@py"
scope: MSO4B = device_manager.add_scope("127.0.0.1")
print(scope) # This prints basic information of the connected scope.
Alias usage¶
Devices can be given custom alias names and can be referenced by that alias.
"""An example of alias usage."""
from tm_devices import DeviceManager
from tm_devices.drivers import AWG5200, MSO5
with DeviceManager(verbose=True) as dm:
# Add a scope and give an optional alias
dm.add_scope("MSO56-100083", alias="BOB")
# Add an awg using an IP address and optional alias
dm.add_awg("192.168.0.1", alias="JILL")
# Get the scope with the BOB alias from device manager
bobs_scope: MSO5 = dm.get_scope("BOB")
# Get the awg with the JILL alias from device manager
jills_awg: AWG5200 = dm.get_awg("JILL")
Adding devices with environment variables¶
Device configuration information can be defined in an environment variable, usually done outside the Python code for ease of automation (shown inside the Python code here for demonstration purposes).
"""An example of using devices which are defined via environment variables.
These environment variables are settable outside Python code, usually they are set in the shell used
to execute the Python script.
"""
import os
from tm_devices import DeviceManager
from tm_devices.drivers import AFG31K, MSO2, SMU2601B
# Indicate to use the PyVISA-py backend rather than any installed VISA backends.
os.environ["TM_OPTIONS"] = "STANDALONE"
# Define some devices.
os.environ["TM_DEVICES"] = (
"device_type=SCOPE,address=<IP or hostname>" # Define a scope
"~~~device_type=AFG,address=<IP or hostname>" # Define an AFG
"~~~device_type=SMU,address=<IP or hostname>" # Define a SMU
)
# Create Tektronix Devices
with DeviceManager(verbose=True) as dm:
# Scope
scope: MSO2 = dm.get_scope(1)
print(scope.query("*IDN?"))
# Set horizontal scale and verify success
scope.set_and_check(":HORIZONTAL:SCALE", 400e-9)
scope.expect_esr(0)
# AFG
afg: AFG31K = dm.get_afg(1)
print(afg.idn_string)
# Turn on AFG and verify success
afg.set_and_check(":OUTPUT1:STATE", "1")
# SMU
smu: SMU2601B = dm.get_smu(1)
# Get device information
print(smu.query("print(localnode.model)"))
print(smu.query("print(localnode.serialno)"))
print(smu.query("print(localnode.version)"))
Customize logging and console output¶
The amount of console output and logging saved to the log file can be customized as needed. This
configuration can be done in the Python code itself as demonstrated here, or by using the
config file or
environment variable. See the
configure_logging() API documentation for more
details about logging configuration.
Important
If any configuration is performed in the Python code prior to instantiating the
DeviceManager, all other logging configuration methods
(config file, env var) will be ignored.
"""The console output and level of logging outputs in the log file can be configured as needed."""
from tm_devices import configure_logging, DeviceManager, LoggingLevels
from tm_devices.drivers import MSO6B
# NOTE: This configuration will prevent any logging config options from a config file or
# environment variable from being used.
configure_logging(
log_console_level=LoggingLevels.NONE, # completely disable console logging
log_file_level=LoggingLevels.DEBUG, # log everything to the file
log_file_directory="./log_files", # save the log file in the "./log_files" directory
log_file_name="custom_log_filename.log", # customize the filename
log_pyvisa_messages=True, # include all the pyvisa debug messages in the same log file
log_uncaught_exceptions=True, # log uncaught exceptions (this is the default behavior)
)
with DeviceManager(verbose=False) as dm:
scope: MSO6B = dm.add_scope("192.168.0.1")
scope.curve_query(1)
scope.check_port_connection(4000)
scope.check_network_connection()
scope.check_visa_connection()
Disable command checking¶
This removes an extra query that verifies the property was set to the expected value. This can be disabled at the device level or disabled for all devices by disabling verification via the device manager.
"""An example showing how to disable the verification portion of the ``.set_and_check()`` method."""
from tm_devices import DeviceManager
from tm_devices.drivers import MSO5, SMU2601B
with DeviceManager(verbose=True) as dm:
# Add some devices
scope: MSO5 = dm.add_scope("192.168.0.1")
smu: SMU2601B = dm.add_smu("192.168.0.2")
#
# Set some values and use verification to verify they were set properly.
#
# using set_and_check
scope.set_and_check(":HORIZONTAL:SCALE", 100e-9)
# using force_verify on the auto-generated commands
scope.commands.horizontal.scale.write(200e-9, verify=True)
# using the command verification context manager on the auto-generated commands
with scope.command_verification():
scope.commands.horizontal.scale.write(50e-9)
# using set_and_check
smu.set_and_check("beeper.enable", 1)
# using the command verification context manager on the auto-generated commands
with smu.command_verification():
smu.commands.beeper.enable = 0
#
# Disable command verification.
#
# Disable just for the scope
scope.enable_verification = False
# Disable command verification for all devices
dm.disable_command_verification = True
#
# Set some values, but now **no verification** will happen in any of these method calls.
#
# using set_and_check
scope.set_and_check(":HORIZONTAL:SCALE", 100e-9)
# using force_verify on the auto-generated commands
scope.commands.horizontal.scale.write(200e-9, verify=True)
# using the command verification context manager on the auto-generated commands
with scope.command_verification():
scope.commands.horizontal.scale.write(50e-9)
# using set_and_check
smu.set_and_check("beeper.enable", 1)
# using the command verification context manager on the auto-generated commands
with smu.command_verification():
smu.commands.beeper.enable = 0
#
# Temporarily enable verification for a few commands
#
with scope.temporary_enable_verification(True):
# This will be verified
scope.set_and_check(":HORIZONTAL:SCALE", 500e-9)
Generate a signal using the Internal AFG¶
Use the Internal AFG to generate a 1 V, 10 MHz square wave with a 200 mV offset on CH1 of the SCOPE.
- Requires a SCOPE with a license for the Internal AFG.
- Requires the Internal AFG output to be connected to CH1 on the SCOPE
"""An example showing how to generate a signal using the scope's internal AFG."""
from tm_devices import DeviceManager
from tm_devices.drivers import MSO5
with DeviceManager(verbose=True) as dm:
# Create a connection to the scope and indicate that it is a MSO5 scope for type hinting
scope: MSO5 = dm.add_scope("192.168.0.1")
# Generate the signal using individual PI commands.
scope.commands.afg.frequency.write(10e6) # set frequency
scope.commands.afg.offset.write(0.2) # set offset
scope.commands.afg.square.duty.write(50) # set duty cycle
scope.commands.afg.function.write("SQUARE") # set function
scope.commands.afg.output.load.impedance.write("FIFTY") # set load impedance
scope.commands.ch[1].scale.write(0.5, verify=True) # set and check vertical scale on CH1
scope.commands.afg.output.state.write(1) # turn on the Internal AFG output
scope.commands.esr.query() # check for any errors
scope.commands.acquire.stopafter.write("SEQUENCE") # perform a single sequence
# Generate the same signal using a single method call.
scope.generate_function(
frequency=10e6,
offset=0.2,
amplitude=0.5,
duty_cycle=50,
function=scope.source_device_constants.functions.SQUARE,
termination="FIFTY",
)
scope.commands.ch[1].scale.write(0.5, verify=True) # set and check vertical scale on CH1
scope.commands.acquire.stopafter.write("SEQUENCE") # perform a single sequence
Save a screenshot from the device to the local machine¶
tm_devices provides the ability to save a screenshot with device drivers that inherit from the
ScreenCaptureMixin,
and then copy that screenshot to the local machine running the Python script.
"""Save a screenshot on the device and copy it to the local machine/environment."""
from tm_devices import DeviceManager
from tm_devices.drivers import MSO6B
with DeviceManager(verbose=True) as dm:
# Add a scope
scope: MSO6B = dm.add_scope("192.168.0.1")
# Send some commands
scope.add_new_math("MATH1", "CH1") # add MATH1 to CH1
scope.turn_channel_on("CH2") # turn on channel 2
scope.set_and_check(":HORIZONTAL:SCALE", 100e-9) # adjust horizontal scale
# Save a screenshot as a timestamped file. This will create a screenshot on the device,
# copy it to the current working directory on the local machine,
# and then delete the screenshot file from the device.
scope.save_screenshot()
# Save a screenshot as "example.png". This will create a screenshot on the device,
# copy it to the current working directory on the local machine,
# and then delete the screenshot file from the device.
scope.save_screenshot("example.png")
# Save a screenshot as "example.jpg". This will create a screenshot on the device
# using INVERTED colors in the "./device_folder" folder,
# copy it to "./images/example.jpg" on the local machine,
# but this time the screenshot file on the device will not be deleted.
scope.save_screenshot(
"example.jpg",
colors="INVERTED",
local_folder="./images",
device_folder="./device_folder",
keep_device_file=True,
)
Curve query saved to csv¶
Perform a curve query and save the results to a csv file.
- Requires an AFG connected to channel 1 on a SCOPE.
"""An example showing a basic curve query."""
from pathlib import Path
from tm_devices import DeviceManager
from tm_devices.drivers import AFG3KC, MSO5
EXAMPLE_CSV_FILE = Path("example_curve_query.csv")
with DeviceManager(verbose=True) as dm:
# Add a scope via hostname (use IP address if necessary)
scope: MSO5 = dm.add_scope("MSO56-100083")
# Add an AFG via IP address
afg: AFG3KC = dm.add_afg("192.168.0.1")
# Turn on AFG
afg.set_and_check(":OUTPUT1:STATE", "1")
# Perform curve query and save results to csv file
curve_returned = scope.curve_query(1, output_csv_file=EXAMPLE_CSV_FILE)
# Read in the curve query from file
with EXAMPLE_CSV_FILE.open(encoding="utf-8") as csv_content:
curve_saved = [int(i) for i in csv_content.read().split(",")]
# Verify query saved to csv is the same as the one returned from curve_query function call
assert curve_saved == curve_returned
Saving / recalling a waveform and session¶
We can save a waveform on our scope to an external file. This is useful for recalling previously saved waveforms if we ever need to use that waveform again.
The same can be done for scope sessions, sessions are essentially a snapshot of the current state of our scope.
"""An example of saving and recalling a waveform and session file."""
from tm_devices import DeviceManager
from tm_devices.drivers import MSO6B
with DeviceManager(verbose=True) as dm:
# Get a scope
scope: MSO6B = dm.add_scope("192.168.0.1")
# Send some commands
scope.add_new_math("MATH1", "CH1") # add MATH1 to CH1
scope.turn_channel_on("CH2") # turn on channel 2
scope.set_and_check(":HORIZONTAL:SCALE", 100e-9) # adjust horizontal scale
# save the session as example.tss
scope.commands.save.session.write("example.tss")
# save the waveform on CH1 as example.wfm
scope.commands.save.waveform.write('CH1,"example.wfm"')
scope.reset() # reset the scope
scope.recall_session("example.tss") # recall the saved session example.tss
scope.recall_reference("example.wfm", 1) # recall example.wfm as REF1
Configuring a measurement on a single sequence¶
A scope can be configured for a measurement on a single acquisition by setting the appropriate acquisition parameters and adding the desired measurement on the selected channel.
"""An example script for connecting and configuring scope for acquisition."""
from tm_devices import DeviceManager
from tm_devices.drivers import MSO6B
from tm_devices.helpers import PYVISA_PY_BACKEND
with DeviceManager(verbose=True) as device_manager:
# Enable resetting the devices when connecting and closing
device_manager.setup_cleanup_enabled = True
device_manager.teardown_cleanup_enabled = True
# Use the PyVISA-py backend
device_manager.visa_library = PYVISA_PY_BACKEND
# Creating Scope driver object by providing ip address.
scope: MSO6B = device_manager.add_scope("127.0.0.1")
# Turn on channel 1 and channel 2
scope.commands.display.waveview1.ch[1].state.write("ON")
scope.commands.display.waveview1.ch[2].state.write("ON")
# Set channel 1 vertical scale to 10mV
scope.commands.ch[1].scale.write(10e-3)
# Set horizontal record length to 20000
scope.commands.horizontal.recordlength.write(20000)
# Set horizontal position to 100
scope.commands.horizontal.position.write(10)
# Set trigger type to Edge
scope.commands.trigger.a.type.write("EDGE")
# Acquisition setup
scope.commands.acquire.state.write("OFF")
scope.commands.acquire.mode.write("Sample")
scope.commands.acquire.stopafter.write("Sequence")
# Adding measurements
scope.commands.measurement.addmeas.write("AMPLitude")
scope.commands.measurement.addmeas.write("PK2PK")
scope.commands.measurement.addmeas.write("MAXIMUM")
scope.commands.measurement.meas[1].source.write("CH1")
scope.commands.measurement.meas[2].source.write("CH1")
scope.commands.measurement.meas[3].source.write("CH2")
# Get the measurement values
scope.commands.acquire.state.write("ON")
if int(scope.commands.opc.query()) == 1:
scope.commands.measurement.meas[1].results.currentacq.mean.query()
scope.commands.measurement.meas[2].results.currentacq.maximum.query()
Adding DPOJET measurements and plots¶
DPOJET measurements and plots can be added on a DPO70KSX/C/7KC/DPO5KB scope.
Measurements report can be saved in a .pdf format.
"""An example of adding dpojet measurements and plots."""
from tm_devices import DeviceManager
from tm_devices.drivers import MSO70KDX
from tm_devices.helpers import PYVISA_PY_BACKEND
with DeviceManager(verbose=True) as device_manager:
# Enable resetting the devices when connecting and closing
device_manager.setup_cleanup_enabled = True
device_manager.teardown_cleanup_enabled = True
# Use the PyVISA-py backend
device_manager.visa_library = PYVISA_PY_BACKEND
# Creating one 7K/70K/SX Scope driver object by providing ip address.
scope: MSO70KDX = device_manager.add_scope("127.0.0.1")
# Starting DPOJET
scope.commands.dpojet.activate.write()
scope.commands.dpojet.version.query()
# CLear all measurements
scope.commands.dpojet.clearallmeas.write()
# Add a few DPOJET measurements
scope.commands.dpojet.addmeas.write("Period")
scope.commands.dpojet.addmeas.write("Pduty")
scope.commands.dpojet.addmeas.write("RiseTime")
scope.commands.dpojet.addmeas.write("acrms")
# Add a few DPOJET plots for the measurements
scope.commands.dpojet.addplot.write("spectrum, MEAS1")
scope.commands.dpojet.addplot.write("dataarray, MEAS2")
scope.commands.dpojet.addplot.write("TimeTrend, MEAS3")
scope.commands.dpojet.addplot.write("histogram, MEAS4")
# Start a measurement
scope.commands.dpojet.state.write("single")
# Get the measurement values for the current acquisition data
scope.commands.dpojet.meas[1].results.currentacq.max.query()
scope.commands.dpojet.meas[1].results.currentacq.population.query()
# Save all plots
scope.commands.dpojet.saveallplots.write()
# Save the report
scope.commands.dpojet.report.savewaveforms.write("1")
scope.commands.dpojet.report.write("EXECUTE")
Directly accessing the PyVISA resource object¶
The PyVISA resource object can be directly
accessed if there is a specific action that is not yet available directly through
the drivers in the tm_devices package.
"""Directly access the PyVISA resource object."""
from tm_devices import DeviceManager
from tm_devices.drivers import MSO5B
with DeviceManager() as device_manager:
# Create the scope object.
scope: MSO5B = device_manager.add_scope("192.168.0.1")
# Access the PyVISA resource object directly,
# `scope.visa_resource` returns a MessageBasedResource object from PyVISA.
scope.visa_resource.read_bytes(1024)
Dynamic reading buffers (SMUs)¶
Create and read from a dynamic buffer.
"""An example of creating and reading from a dynamic buffer."""
from tm_devices import DeviceManager
from tm_devices.drivers import SMU2601B
with DeviceManager() as device_manager:
# Create a SMU and type hint it as a 2601B
smu: SMU2601B = device_manager.add_smu("192.168.0.1")
# Create a buffer
BUFFER_NAME = "mybuffer"
smu.write(f"{BUFFER_NAME} = smua.makebuffer(100)")
smu.commands.buffer_var[BUFFER_NAME].clear()
smu.commands.buffer_var[BUFFER_NAME].collectsourcevalues = 1 # Enable source value storage
smu.commands.buffer_var[BUFFER_NAME].appendmode = 1 # Enable buffer append mode
capacity = smu.commands.buffer_var[BUFFER_NAME].capacity # Get the buffer capacity
Registering the Device Manager to be closed at program termination¶
Sometimes using the DeviceManager class as a context manager is not feasible.
In those instances there is an alternative way to enforce the device manager to
close when the Python script execution is finished without needing to explicitly
call the .close() method.
"""An example showing how to register the DeviceManager to close on program exit."""
import atexit
from tm_devices import DeviceManager
from tm_devices.drivers import MSO6B
# Create the device manager
dm = DeviceManager()
# Set up the device manager to be automatically closed when the program terminates
atexit.register(dm.close)
# Add a device
scope: MSO6B = dm.add_scope("192.168.0.1")
# Use the device
print(scope)
# The device manager will automatically close as the script exits, no code required.
Add custom device support¶
Sometimes there is a need to use a device that is not currently supported by
tm_devices. When this is the case, custom device driver classes can be created
and passed to the DeviceManager when it is
first instantiated.
In order to do this a few things will need to be created:
- A custom device class. Ideally this would inherit from one of the main device types, though a custom class representing an unsupported device type can also be created.
- A mapping of the parsed model series string to the Python class.
"""An example of external device support via a custom driver."""
from tm_devices import DeviceManager, register_additional_usbtmc_mapping
from tm_devices.driver_mixins.device_control import PIControl
from tm_devices.drivers import MSO5
from tm_devices.drivers.device import Device
from tm_devices.drivers.scopes.scope import Scope
from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813
# Custom devices that inherit from a supported device type can be defined by inheriting from the
# specific device type class. This custom class must implement all abstract methods defined by the
# abstract parent classes.
class CustomScope(PIControl, Scope):
"""Custom scope class."""
# This is an abstract method that must be implemented by the custom device driver
@cached_property
def total_channels(self) -> int:
return 4
# This is an abstract method that must be implemented by the custom device driver.
def _get_errors(self) -> tuple[int, tuple[str, ...]]:
"""Get the current errors from the device."""
# The contents of this method would need to be properly implemented,
# this is just example code. :)
return 0, ()
def custom_method(self, value: str) -> None:
"""Add a custom method to the custom driver."""
print(f"{self.name}, {value=}")
# Custom devices that do not inherit from a supported device type can be defined by inheriting from
# a parent class further up the inheritance tree as well as a control mixin class to provide the
# necessary methods for controlling the device. This custom class must also implement all abstract
# methods defined by the abstract parent classes.
class CustomDevice(PIControl, Device):
"""A custom device that is not one of the officially supported devices."""
# Custom device types not officially supported need to define what type of device they are.
@cached_property
def device_type(self) -> str:
"""Return the device type."""
return "CustomDevice"
# This is an abstract method that must be implemented by the custom device driver.
def _get_errors(self) -> tuple[int, tuple[str, ...]]:
"""Get the current errors from the device."""
# The contents of this method would need to be properly implemented,
# this is just example code. :)
return 0, ()
def custom_device_method(self, value: int) -> None:
"""Add a custom method to the custom device driver."""
print(f"{self.name}, {value=}")
# For VISA devices, the model series is based on the model that is returned from
# the ``*IDN?`` query. (See the ``tm_devices.helpers.get_model_series()`` function for details)
# For REST API devices, the model series is provided via the ``device_driver`` parameter in
# the configuration file, environment variable, or python code.
CUSTOM_DEVICE_DRIVERS = { # A mapping of custom model series strings to Python driver classes
"CustomModelSeries": CustomScope,
"CustomDeviceModelSeries": CustomDevice,
}
# 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 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".
custom_device: CustomDevice = device_manager.add_unsupported_device("192.168.0.3")
# Custom drivers inherit all methods and attributes
print(custom_scope.all_channel_names_list) # print the channel names
custom_scope.cleanup() # cleanup the custom scope
# Custom drivers can also use added methods
custom_scope.custom_method("value")
# Custom device types still inherit methods from their parent classes, though device type
# specific functionality is not defined by default
assert not custom_device.has_errors() # check for no errors
# Custom devices can also use any custom methods added to the custom class
custom_device.custom_device_method(10)