Skip to content

Commit

Permalink
feat: Custom LAN Device Name for TCPIP Connections (#267)
Browse files Browse the repository at this point in the history
* feat: added optional lan_device_name to DeviceConfigEntry. This field is only used when connecting to TCPIP. If connection type is TCPIP, default is to use 'inst0';

* test: updated unit test for lan_device_name to DeviceConfigEntry.

* docs: updated configuration.md to add documentation for lan_device_name in the config;

* docs: updated docstring of lan_device_name to include default.

* docs: updated CHANGELOG.md

* feat: removed lan device name enum to allow users to specify custom/any lan device name (this follows NI-MAX UI)

* fix: updated docstring of add_device to include default value of lan_device_name.

* docs: capitalized connection type in configuration.md documentation.

* refactor: set lan_device_name to default in get_visa_resource_expression rather than post init.

* refactor: set gpib board number to default in get_visa_resource_expression rather than post init.

* refactor: simplified gpib validation change.

* ci: Don't bother checking for the absolute latest node version when just testing code and linting

* ci: Don't install node as a part of running tox

---------

Co-authored-by: Felt, Nicholas <nicholas.felt@tektronix.com>
  • Loading branch information
lekevin678 and nfelt14 authored Aug 7, 2024
1 parent b9f6f96 commit 42c47e6
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 27 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/test-code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: latest
check-latest: true
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ Valid subsections within a version are:

Things to be included in the next release go here.

### Changed

- Changed `DeviceConfigEntry` dataclass by adding an optional `lan_device_name` field, which allows connecting to instruments through TCPIP on LAN device names other than `inst0`.

---

## v2.2.0 (2024-08-02)
Expand Down
30 changes: 26 additions & 4 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,24 @@ devices:
alias: <alias>
connection_type: <connection_type>
device_type: <device_type>
# usb connection
# TCPIP connection
- address: <ip_address_or_hostname>
alias: <alias>
connection_type: TCPIP
device_type: <device_type>
lan_device_name: <lan_device_name>
# USB connection
- address: <model>-<serial_number>
alias: <alias>
connection_type: USB
device_type: <device_type>
# socket connection
# SOCKET connection
- address: <ip_address_or_hostname>
alias: <alias>
connection_type: SOCKET
device_type: <device_type>
lan_port: <port_number>
# serial connection
# SERIAL connection
- address: <serial_port>
alias: <alias>
connection_type: SERIAL
Expand All @@ -101,7 +107,7 @@ devices:
flow_control: xon_xoff
parity: none
end_input: none
# gpib connection
# GPIB connection
- address: <gpib_address>
gpib_board_number: <gpib_board_number>
alias: <alias>
Expand Down Expand Up @@ -171,6 +177,9 @@ devices:
- The open port, a number in the range 0 to 65535.
- Required when the `<connection_type>` is `SOCKET`.
- Optional when `<connection_type>` is `REST_API`
- `lan_device_name`
- The LAN device name to connect on when `<connection_type>` is `TCPIP`.
- If no LAN device name is provided in the configuration, `inst0` is used.
- `serial_config`
- Configuration data for `SERIAL` connection type, which VISA documentation
commonly refers to as ASRL.
Expand Down Expand Up @@ -263,6 +272,11 @@ devices:
alias: my_smu_by_ip
connection_type: TCPIP
device_type: SMU
# TCPIP connection to SCOPE using the High Speed LAN Instrument Protocol (HiSLIP)
- address: MSO54-123456
alias: my_scope_on_hislip0
device_type: SCOPE
lan_device_name: hislip0
# SOCKET connection to SCOPE types needs lan_port number
- address: 123.45.67.255
alias: my_scope_port4000
Expand Down Expand Up @@ -321,6 +335,14 @@ alias = "my_smu_by_ip"
connection_type = "TCPIP"
device_type = "SMU"
# TCPIP connection to SCOPE using the High Speed LAN Instrument Protocol (HiSLIP)
[[devices]]
address = "MSO54-123456"
alias = "my_scope_by_hostname"
connection_type = "TCPIP"
device_type = "SCOPE"
lan_device_name = "hislip0"
# SOCKET connection to SCOPE types needs lan_port number
[[devices]]
address = "123.45.67.255"
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -432,11 +432,12 @@ package = wheel
install_command = python -I -m pip install --upgrade --upgrade-strategy=eager {opts} {packages}
deps =
poetry
passenv =
PYRIGHT_PYTHON_GLOBAL_NODE # If set outside of tox, this will cause python-pyright to use nodeenv to install node rather than use the system node
setenv =
DOC_PYTHON_VERSION = python3.11 # Keep this in sync with .readthedocs.yml and any CI scripts
# Skip pre-commit checks that are not needed
SKIP = file-contents-sorter
PYRIGHT_PYTHON_GLOBAL_NODE = False # This will cause python-pyright to use nodeenv to install node rather than use the system node
commands_pre =
poetry install --no-root --without=main
commands =
Expand Down
4 changes: 4 additions & 0 deletions src/tm_devices/components/dm_config_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ def add_device( # noqa: PLR0913
connection_type: Union[ConnectionTypes, str] = ConnectionTypes.TCPIP,
alias: Optional[str] = None,
lan_port: Optional[int] = None,
lan_device_name: Optional[str] = None,
serial_config: Optional[SerialConfig] = None,
device_driver: Optional[str] = None,
gpib_board_number: Optional[int] = None,
Expand All @@ -168,6 +169,8 @@ def add_device( # noqa: PLR0913
connection_type: The specific type of connection defined in the config entry.
alias: An optional key/name used to retrieve this device from the DeviceManager.
lan_port: The port number to connect on, used for SOCKET/REST_API connections.
lan_device_name: The LAN device name to connect on, only used for TCPIP connections.
The default is 'inst0'.
serial_config: A dataclass for holding serial connection info.
device_driver: A string indicating the specific Python device driver to use.
gpib_board_number: The GPIB board number (also referred to as a controller), only used
Expand All @@ -188,6 +191,7 @@ def add_device( # noqa: PLR0913
connection_type=connection_type,
alias=alias,
lan_port=lan_port,
lan_device_name=lan_device_name,
serial_config=serial_config,
device_driver=device_driver,
gpib_board_number=gpib_board_number,
Expand Down
47 changes: 29 additions & 18 deletions src/tm_devices/helpers/constants_and_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
AsDictionaryMixin,
AsDictionaryUseEnumNameUseCustEnumStrValueMixin,
)
from tm_devices.helpers.enums import ConnectionTypes, DeviceTypes, LoadImpedanceAFG, SupportedModels
from tm_devices.helpers.enums import (
ConnectionTypes,
DeviceTypes,
LoadImpedanceAFG,
SupportedModels,
)
from tm_devices.helpers.standalone_functions import validate_address


Expand Down Expand Up @@ -185,6 +190,8 @@ class DeviceConfigEntry(AsDictionaryUseEnumNameUseCustEnumStrValueMixin, _Config
"""An optional key/name used to retrieve this devices from the DeviceManager."""
lan_port: Optional[int] = None
"""The port number to connect on, used for SOCKET/REST_API connections."""
lan_device_name: Optional[str] = None
"""The LAN device name to connect on, used for TCPIP connections (defaults to 'inst0')."""
serial_config: Optional[SerialConfig] = None
"""Serial configuration properties for connecting to a device over SERIAL (ASRL)."""
device_driver: Optional[str] = None
Expand All @@ -193,7 +200,7 @@ class DeviceConfigEntry(AsDictionaryUseEnumNameUseCustEnumStrValueMixin, _Config
"""The GPIB board number (also referred to as a controller) to be used when making a GPIB connection (defaults to 0).""" # noqa: E501

# pylint: disable=too-many-branches
def __post_init__(self) -> None: # noqa: PLR0912,C901,PLR0915
def __post_init__(self) -> None: # noqa: PLR0912, C901
"""Validate data after creation.
Raises:
Expand All @@ -219,20 +226,21 @@ def __post_init__(self) -> None: # noqa: PLR0912,C901,PLR0915
# this is from an invalid enum name
raise TypeError(*error.args) # noqa: TRY200,B904

# Set the GPIB board number to 0 if it is not provided
if self.connection_type == ConnectionTypes.GPIB:
if self.gpib_board_number is None:
object.__setattr__(self, "gpib_board_number", 0)
if not isinstance(self.gpib_board_number, int):
try:
# noinspection PyTypeChecker
object.__setattr__(self, "gpib_board_number", int(self.gpib_board_number)) # pyright: ignore[reportArgumentType]
except ValueError:
msg = (
f'"{self.gpib_board_number}" is not a valid GPIB board number, '
f"valid board numbers must be integers."
)
raise ValueError(msg) # noqa: B904
# Validate the GPIB board number
if (
self.connection_type == ConnectionTypes.GPIB
and not isinstance(self.gpib_board_number, int)
and self.gpib_board_number is not None
):
try:
# noinspection PyTypeChecker
object.__setattr__(self, "gpib_board_number", int(self.gpib_board_number))
except ValueError:
msg = (
f'"{self.gpib_board_number}" is not a valid GPIB board number, '
f"valid board numbers must be integers."
)
raise ValueError(msg) # noqa: B904

if (
self.gpib_board_number is not None
Expand Down Expand Up @@ -395,13 +403,16 @@ def get_visa_resource_expression(self) -> str:
elif self.connection_type == ConnectionTypes.SOCKET:
resource_expr = f"TCPIP0::{self.address}::{self.lan_port}::SOCKET"
elif self.connection_type == ConnectionTypes.TCPIP:
resource_expr = f"TCPIP0::{self.address}::inst0::INSTR"
# Set the LAN device name to "inst0" if one is not provided/
lan_device_name = "inst0" if self.lan_device_name is None else self.lan_device_name
resource_expr = f"TCPIP0::{self.address}::{lan_device_name}::INSTR"
elif self.connection_type == ConnectionTypes.SERIAL:
resource_expr = f"ASRL{self.address}::INSTR"
elif self.connection_type == ConnectionTypes.MOCK: # pragma: no cover
resource_expr = f"MOCK0::{self.address}::INSTR"
elif self.connection_type == ConnectionTypes.GPIB:
resource_expr = f"GPIB{self.gpib_board_number}::{self.address}::INSTR"
gpib_board_number = 0 if self.gpib_board_number is None else self.gpib_board_number
resource_expr = f"GPIB{gpib_board_number}::{self.address}::INSTR"
else:
msg = (
f"{self.connection_type.value} is not a valid connection "
Expand Down
1 change: 1 addition & 0 deletions tests/samples/sample_devices.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
address = "MSO54-123456"
connection_type = "TCPIP"
device_type = "SCOPE"
lan_device_name = "hislip0"

[[devices]]
address = "789.56.4.123"
Expand Down
1 change: 1 addition & 0 deletions tests/samples/sample_devices.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ devices:
- address: MSO54-123456
connection_type: TCPIP
device_type: SCOPE
lan_device_name: hislip0
- address: 789.56.4.123
alias: foo
connection_type: SOCKET
Expand Down
6 changes: 5 additions & 1 deletion tests/test_config_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,14 @@ def test_nested_config_prefix_mapping() -> None:
def test_environment_variable_config(capsys: pytest.CaptureFixture[str]) -> None:
"""Test the environment variable config method."""
options = ["STANDALONE"]
expected_device_string = "address=MSO54-123456,connection_type=TCPIP,device_type=SCOPE"
expected_device_string = (
"address=MSO54-123456,connection_type=TCPIP,device_type=SCOPE,lan_device_name=hislip0"
)
expected_device = DeviceConfigEntry(
address="MSO54-123456",
connection_type=ConnectionTypes.TCPIP,
device_type=DeviceTypes.SCOPE,
lan_device_name="hislip0",
)
expected_entry = MappingProxyType({"SCOPE 1": expected_device})

Expand Down Expand Up @@ -207,6 +210,7 @@ def test_file_config_non_default_path(
address="MSO54-123456",
connection_type=ConnectionTypes.TCPIP,
device_type=DeviceTypes.SCOPE,
lan_device_name="hislip0",
),
"SCOPE 2": DeviceConfigEntry(
address="789.56.4.123",
Expand Down

0 comments on commit 42c47e6

Please sign in to comment.