Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Update the function that detects a VISA resource expression to work properly for SOCKET resource expressions. #134

Merged
merged 2 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ Things to be included in the next release go here.
- Updated the version of `python-semantic-release` that is used to avoid needing to store a copy of the previous changelog in the repo.
- Pinned the linters (ruff, pyright, pylint, docformatter) to specific versions to reduce failures when updates are released that add new rules or break existing rules.

### Fixed

- Fixed the code that detects VISA resource expressions to be able to detect SOCKET resource expressions properly.

______________________________________________________________________

## v1.1.0 (2023-12-07)
Expand Down
6 changes: 6 additions & 0 deletions src/tm_devices/device_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,12 @@ def _add_device( # noqa: PLR0913
if (visa_resource_parts := detect_visa_resource_expression(address)) is not None:
connection_type = visa_resource_parts[0]
address = visa_resource_parts[1]
if (
connection_type == ConnectionTypes.SOCKET.value
and len(address_parts := address.split(":", maxsplit=1)) > 1
):
address = address_parts[0]
port = int(address_parts[1])

# Device Manager uses all caps for key mappings to device drivers and aliases
config_dict: dict[str, Optional[Union[str, int, SerialConfig]]] = {
Expand Down
2 changes: 1 addition & 1 deletion src/tm_devices/helpers/constants_and_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ def __str__(self) -> str:
"""str: Constant string with the name of this package."""

VISA_RESOURCE_EXPRESSION_REGEX: Final = re.compile(
r"^(\w+)(?:::0X\w+)?::([-.\w]+)(?:::(\w+))?(?:::INST0?)?::INSTR?$"
r"^(\w+)(?:::0X\w+)?::([-.\w]+)(?:::(\w+))?(?:::INST0?)?::(INSTR?|SOCKET)$"
)
"""re.Pattern[str]: A regex pattern used to capture pieces of VISA resource expressions."""

Expand Down
9 changes: 4 additions & 5 deletions src/tm_devices/helpers/enums.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Module containing Enums for the tm_devices package."""
from enum import Enum
from types import DynamicClassAttribute
from typing import cast, List


Expand All @@ -10,13 +9,13 @@ class CustomStrEnum(Enum):
This class provides better type hinting for the value property.
"""

@DynamicClassAttribute
def name(self) -> str: # pylint: disable=function-redefined
@property
def name(self) -> str: # pylint: disable=function-redefined,invalid-overridden-method
"""Return the name of the Enum member."""
return self._name_ # pylint: disable=no-member

@DynamicClassAttribute
def value(self) -> str:
@property
def value(self) -> str: # pylint: disable=invalid-overridden-method
"""Return the value of the Enum member."""
return cast(str, self._value_) # pylint: disable=no-member

Expand Down
22 changes: 14 additions & 8 deletions src/tm_devices/helpers/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,8 @@ def detect_visa_resource_expression(input_str: str) -> Optional[Tuple[str, str]]

The pieces consist of:
- The connection type, e.g. TCPIP.
- The address of the device, an IP address, hostname, or
- The address of the device, an IP address
(with port separated by a colon for SOCKET connections), hostname, or
string in the format ``model-serial``.

Args:
Expand All @@ -276,11 +277,11 @@ def detect_visa_resource_expression(input_str: str) -> Optional[Tuple[str, str]]
"""
retval: Optional[Tuple[str, str]] = None
if input_str.upper().startswith("ASRL"):
retval = ("SERIAL", input_str[4:].split("::", 1)[0])
retval = (ConnectionTypes.SERIAL.value, input_str[4:].split("::", 1)[0])
elif (match := VISA_RESOURCE_EXPRESSION_REGEX.search(input_str.upper())) is not None:
match_groups_list = list(filter(None, match.groups()))
for unneeded_part in ("INST", "INST0"):
if unneeded_part in match_groups_list:
for unneeded_part in ("INST", "INST0", "INSTR"):
while unneeded_part in match_groups_list:
match_groups_list.remove(unneeded_part)
# Check if the model is in the USB model lookup
filtered_usb_model_keys = [
Expand All @@ -291,12 +292,18 @@ def detect_visa_resource_expression(input_str: str) -> Optional[Tuple[str, str]]
if filtered_usb_model_keys:
# SMU and PSU need to be removed from the string to prevent issues
match_groups_list[1] = filtered_usb_model_keys[0].replace("SMU", "").replace("PSU", "")
retval = (match_groups_list[0].rstrip("0"), "-".join(match_groups_list[1:]).lstrip("0X"))
if match_groups_list[-1] == ConnectionTypes.SOCKET.value:
retval = (match_groups_list[-1], ":".join(match_groups_list[1:3]))
else:
retval = (
match_groups_list[0].rstrip("0"),
"-".join(match_groups_list[1:]).lstrip("0X"),
)
return retval


# pylint: disable=too-many-branches
def get_model_series(model: str) -> str: # noqa: PLR0912,C901,PLR0915
def get_model_series(model: str) -> str: # noqa: PLR0912,C901
"""Get the series string from the full model number.

Args:
Expand Down Expand Up @@ -331,10 +338,9 @@ def get_model_series(model: str) -> str: # noqa: PLR0912,C901,PLR0915
model_beginning = ""
model_end = ""
if re.search("[0-9]", model): # if the model contains numbers
model_end = re.split(r"\d+", model)[-1] # split on the occurence of each number
model_end = re.split(r"\d+", model)[-1] # split on the occurrence of each number
if len(model_end) == 1 and model_end not in valid_model_endings:
model_end = ""
model_beginning = ""
if model_numbers := re.findall(r"\d+", model):
model_number = int(model_numbers[0])
if model.startswith("MODEL") or all(x.isdigit() for x in model.rstrip(model_end)):
Expand Down
6 changes: 6 additions & 0 deletions tests/test_alternative_connection_addresses.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@
None,
("SERIAL", "SMU2614B", "Model 2614B", "4498311"),
),
(
"TCPIP::AFG3KC-HOSTNAME::10001::SOCKET",
"AFG",
None,
("SOCKET", "AFG3KC", "AFG3252C", "SERIAL1"),
),
],
)
def test_alternative_connection_methods(
Expand Down
4 changes: 4 additions & 0 deletions tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,10 @@ def test_get_visa_backend() -> None:
("TCPIP0::127.0.0.9::inst0::INSTR", ("TCPIP", "127.0.0.9")),
("TCPIP::127.0.0.9::inst::INST", ("TCPIP", "127.0.0.9")),
("USB0::0x0699::0x0522::SERIAL1::INSTR", ("USB", "MSO5-SERIAL1")),
("TCPIP0::127.0.0.9::4000::SOCKET", ("SOCKET", "127.0.0.9:4000")),
("GPIB0::1::INSTR", ("GPIB", "1")),
("ASRL1::INSTR", ("SERIAL", "1")),
("MOCK0::127.0.0.9::INSTR", ("MOCK", "127.0.0.9")),
],
)
def test_detect_visa_resource_expression(
Expand Down
Loading