diff --git a/manifest.in b/manifest.in new file mode 100644 index 0000000..21ada19 --- /dev/null +++ b/manifest.in @@ -0,0 +1,2 @@ +include pythonequipmentdrivers/* +recursive-include pythonequipmentdrivers *.py \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 3a5e6ab..c7ac601 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", diff --git a/pythonequipmentdrivers/resource_collections.py b/pythonequipmentdrivers/resource_collections.py index 6594e71..1f2ed07 100644 --- a/pythonequipmentdrivers/resource_collections.py +++ b/pythonequipmentdrivers/resource_collections.py @@ -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 @@ -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) @@ -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"')) diff --git a/pythonequipmentdrivers/sink/Chroma_63600.py b/pythonequipmentdrivers/sink/Chroma_63600.py index 3b14fb5..85ae341 100644 --- a/pythonequipmentdrivers/sink/Chroma_63600.py +++ b/pythonequipmentdrivers/sink/Chroma_63600.py @@ -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) diff --git a/pythonequipmentdrivers/source/BKPrecision_9140.py b/pythonequipmentdrivers/source/BKPrecision_9140.py new file mode 100644 index 0000000..ebb95a0 --- /dev/null +++ b/pythonequipmentdrivers/source/BKPrecision_9140.py @@ -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) diff --git a/pythonequipmentdrivers/source/Chroma_62000P.py b/pythonequipmentdrivers/source/Chroma_62000P.py index bec0aaa..64bcbd4 100644 --- a/pythonequipmentdrivers/source/Chroma_62000P.py +++ b/pythonequipmentdrivers/source/Chroma_62000P.py @@ -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) diff --git a/pythonequipmentdrivers/temperaturecontroller/Koolance_EXC900.py b/pythonequipmentdrivers/temperaturecontroller/Koolance_EXC900.py index 0ba80fc..ff7796e 100644 --- a/pythonequipmentdrivers/temperaturecontroller/Koolance_EXC900.py +++ b/pythonequipmentdrivers/temperaturecontroller/Koolance_EXC900.py @@ -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 @@ -94,7 +94,7 @@ 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() @@ -102,11 +102,10 @@ def read_settings(self) -> dict[str, float]: 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] diff --git a/tox.ini b/tox.ini index 485912a..29a6064 100644 --- a/tox.ini +++ b/tox.ini @@ -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 @@ -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 .