Skip to content

Implement automatic Enum conversion for device initialization. Add BK 9140 triple output source. Fix type hints to be compatible with python 3.8. #30

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

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
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
2 changes: 2 additions & 0 deletions manifest.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include pythonequipmentdrivers/*
recursive-include pythonequipmentdrivers *.py
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ authors = [
]
description = "A library of software drivers to interface with various pieces of test instrumentation"
readme = "README.md"
requires-python = ">=3.9"
requires-python = ">=3.8"
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Console",
Expand Down
24 changes: 23 additions & 1 deletion pythonequipmentdrivers/resource_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from pathlib import Path
from types import SimpleNamespace
from typing import Dict, Iterator, Tuple, Union
from enum import Enum

from pyvisa import VisaIOError

Expand Down Expand Up @@ -358,6 +359,20 @@ def get_callable_methods(instance) -> Tuple:
return tuple(cmds)


def convert_to_enum(instance, value):
"""
Attempt to convert a string value to an Enum value if a matching Enum exists in the instance.
"""
for attr_name in dir(instance):
attr = getattr(instance, attr_name)
if isinstance(attr, type) and issubclass(attr, Enum):
try:
return attr[value.upper()]
except KeyError:
pass
return value


def initiaize_device(instance, sequence) -> None:
"""
initiaize_device(instance, sequence)
Expand Down Expand Up @@ -385,9 +400,16 @@ def initiaize_device(instance, sequence) -> None:
if method_name in valid_cmds:
try:
func = getattr(instance, method_name)
func(**method_kwargs)
# Convert string values to Enum values where possible
converted_kwargs = {
key: convert_to_enum(instance, value) if isinstance(value, str) else value
for key, value in method_kwargs.items()
}
func(**converted_kwargs)

except TypeError as error: # invalid kwargs
print(error_msg_template.format(method_name, error))
except ValueError as error: # invalid Enum value
print(error_msg_template.format(method_name, error))
else:
print(error_msg_template.format(method_name, '"unknown method"'))
3 changes: 2 additions & 1 deletion pythonequipmentdrivers/sink/Chroma_63600.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,8 @@ def set_dynamic_current_time(self, on_time: float, level: int = 0) -> None:
else:
self.write_resource(f"CURR:DYN:T{level} {on_time}")

def get_dynamic_current_time(self, level: int) -> Union[float, Tuple[float]]:
def get_dynamic_current_time(self,
level: int) -> Union[float, Tuple[float]]:
"""
get_dynamic_current_time(level)

Expand Down
231 changes: 231 additions & 0 deletions pythonequipmentdrivers/source/BKPrecision_9140.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
from .Keithley_2231A import Keithley_2231A


# acts as an alias of Keithley_2231A
class BKPrecision_9140(Keithley_2231A):
"""
BKPrecision_9140(address)

address : str, address of the connected power supply

object for accessing basic functionallity of the B&K Precision 9140 DC supply
"""

def set_access_remote(self, mode: str) -> None:
"""
set_access_remote(mode)

mode: str, interface method either 'remote' or 'RWLock' or 'local'
note that 'RWLock' will lock the front panel keys.

set access to the device inferface to 'remote' or 'RWLock' or 'local'
"""

if mode.lower() == "remote":
self.write_resource("SYSTem:REMote")
elif mode.lower() == "rwlock":
self.write_resource("SYSTem:RWLock")
elif mode.lower() == "local":
self.write_resource("SYSTem:LOCal")
else:
raise ValueError(
'Unknown option for arg "mode", should be "remote" or "local"'
)

def set_channel(self, channel: int) -> None:
"""
set_channel(channel)

channel: int, index of the channel to control.
valid options are 0-2 coresponding to 1-3

Selects the specified Channel to use for software control
"""

self.write_resource(f"INST:SEL {channel}")

def _update_channel(self, override_channel):
"""Handles updating the device channel setting"""

if override_channel is not None:
self.set_channel(override_channel - 1)
elif self.channel is not None:
self.set_channel(self.channel - 1)
else:
raise TypeError(
"Channel number must be provided if it is not provided during"
+ "initialization"
)
return

def get_channel(self) -> int:
"""
get_channel()

Get current selected Channel

returns: int
"""

response = self.query_resource("INST:SEL?")
return int(response) + 1

def set_state(self, state: bool, channel: int = None) -> None:
"""
set_state(state, channel)

Enables/disables the output of the supply

Args:
state (bool): Supply state (True == enabled, False == disabled)
channel (int): Index of the channel to control. valid options
are 1-3
"""

self._update_channel(channel)
self.write_resource(f"OUTP:STAT {1 if state else 0}")

def get_state(self, channel: int = None) -> bool:
"""
get_state(channel)

Retrives the current state of the output of the supply.

Args:
channel (int): index of the channel to control. Valid options
are 1-3

Returns:
bool: Supply state (True == enabled, False == disabled)
"""

self._update_channel(channel)
response = self.query_resource("OUTP:STAT?")
if response not in ("ON", "1"):
return False
return True

def all_get_state(self) -> bool:
"""
all_get_state(channel)

Retrives the current state of the output of the supply.

Args:
None

Returns:
bool: Supply state (True == enabled, False == disabled)
"""

response = self.query_resource("OUTP:ALL?")
if response not in ("ON", "1"):
return False
return True

def all_on(self) -> None:
"""
all_on()

Enables the relay for ALL the power supply's outputs equivalent to
set_state(True).

Args:
None
"""

self.write_resource(f"OUTP:ALL {1}")

def all_off(self) -> None:
"""
all_off()

Disables the relay for ALL the power supply's outputs equivalent to
set_state(False).

Args:
None
"""

self.write_resource(f"OUTP:ALL {0}")

def all_toggle(self) -> None:
"""
all_toggle()

Reverses the current state of ALL the Supply's outputs

Args:
None
"""

if self.all_get_state():
self.all_off()
else:
self.all_on()

def set_slewrate(self, slewrate: float, channel: int = None) -> None:
"""
set_slewrate(current)

slewrate: float/int, slew rate setpoint in volts per second. Valid options are 0.001 to 3200.0 V/s.

channel: int=None, the index of the channel to set. Valid options are 1,2,3.

sets the slew rate setting for the power supply in V/s
"""
if 0.001 < slewrate:
slewrate = 0.001
elif slewrate > 3200:
slewrate = 3200

self._update_channel(channel)
self.write_resource(f"VOLT:SLOP {slewrate}")

def get_slewrate(self, channel: int = None) -> float:
"""
get_slewrate()

channel: int=None, the index of the channel to get. Valid options are 1,2,3.

gets the slew rate setting for the power supply in V/s

returns: float
"""

self._update_channel(channel)
response = self.query_resource("VOLT:SLOP?")
return float(response)

def set_current_slewrate(self, slewrate: float, channel: int = None) -> None:
"""
set_current_slewrate(current)

slewrate: float/int, slew rate setpoint in Amps per second. Valid options are 1 to 800.0 A/s.

channel: int=None, the index of the channel to set. Valid options are 1,2,3.

sets the current slew rate setting for the power supply in A/s
"""
if 1 < slewrate:
slewrate = 1
elif slewrate > 800:
slewrate = 800

self._update_channel(channel)
self.write_resource(f"CURR:SLOP {slewrate}")

def get_current_slewrate(self, channel: int = None) -> float:
"""
get_current_slewrate()

channel: int=None, the index of the channel to get. Valid options are 1,2,3.

gets the current slew rate setting for the power supply in A/s

returns: float
"""

self._update_channel(channel)
response = self.query_resource("VOLT:SLOP?")
return float(response)
12 changes: 12 additions & 0 deletions pythonequipmentdrivers/source/Chroma_62000P.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ class Chroma_62000P(VisaResource):
object for accessing basic functionallity of the Chroma_62000P DC supply
"""

def set_access_remote(self, mode: bool = True) -> None:
"""
set_access_remote(mode)

mode: str, interface method either 'remote' or 'local'

set access to the device inferface to 'remote' or 'local'
"""

self.write_resource(
f"""'CONFigure:REMote {'ON' if mode else 'OFF'}'""")

def set_state(self, state: bool) -> None:
"""
set_state(state)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
from dataclasses import dataclass
from time import time
from typing import Literal
from typing import Literal, Dict

from ..core import VisaResource

Expand Down Expand Up @@ -94,19 +94,18 @@ def _write_data(self, data: bytes) -> None:
self._last_read_data_time = None # trigger a refresh on the next read
self.write_resource_raw(data)

def read_settings(self) -> dict[str, float]:
def read_settings(self) -> Dict[str, float]:
"""
read_settings()

Read data from the device and output values of supported parameters
in a "key: value" format

Returns:
dict[str, float]: dict of parameter names and their values
Dict[str, float]: dict of parameter names and their values
"""

data = self._read_data()
out_dict: dict[str, float] = {}
out_dict: Dict[str, float] = {}

for name, reg in self.DATA_REGISTER_MAP.items():
value = data[reg.offset : reg.offset + reg.n_bytes]
Expand Down
8 changes: 5 additions & 3 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
[tox]
env_list =
format
py38
py39
py310
py311
py312

[testenv]
base_python =
py38: python3.8-64
py39: python3.9-64
py310: python3.10-64
py311: python3.11-64
Expand All @@ -22,11 +24,11 @@ commands =

[testenv:format]
description = install black in a virtual environment and invoke it on the current folder
deps =
deps =
black
isort
skip_install = true
commands =
commands =
black .
isort .