diff --git a/.gitignore b/.gitignore index 385844d..69f8557 100644 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,6 @@ dmypy.json .vscode/ node_modules/ + +# ruff +.ruff_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8024ec5..9c98c4f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,3 +18,9 @@ repos: rev: 23.3.0 hooks: - id: black + + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.275 + hooks: + - id: ruff + args: [ "--fix-only" , "--show-fixes" ] diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/hvps/__init__.py b/src/hvps/__init__.py index b3148ce..012ceb3 100644 --- a/src/hvps/__init__.py +++ b/src/hvps/__init__.py @@ -1,2 +1,5 @@ -from .devices import Caen, Iseg -from .version import __version__ +from hvps.devices.caen.caen import Caen +from hvps.devices.iseg.iseg import Iseg +from .version import __version__ + +__all__ = ["Caen", "Iseg", "__version__"] diff --git a/src/hvps/__main__.py b/src/hvps/__main__.py index 620a674..2e05cc9 100644 --- a/src/hvps/__main__.py +++ b/src/hvps/__main__.py @@ -1,4 +1,4 @@ -from hvps import __version__ as hvps_version, Caen, Iseg +from hvps import __version__ as hvps_version, Caen from hvps.commands.caen.module import _set_module_commands, _mon_module_commands from hvps.commands.caen.channel import _set_channel_commands, _mon_channel_commands @@ -55,7 +55,7 @@ def main(): ) # iseg - iseg_parser = subparsers.add_parser("iseg", help="iseg HVPS") + subparsers.add_parser("iseg", help="iseg HVPS") args = parser.parse_args() logging.basicConfig(level=args.log.upper()) diff --git a/src/hvps/commands/caen/__init__.py b/src/hvps/commands/caen/__init__.py index d002116..890be44 100644 --- a/src/hvps/commands/caen/__init__.py +++ b/src/hvps/commands/caen/__init__.py @@ -1,15 +1,6 @@ from __future__ import annotations import re -from .module import ( - _get_mon_module_command, - _get_set_module_command, -) -from .channel import ( - _get_mon_channel_command, - _get_set_channel_command, -) - import logging import serial @@ -55,7 +46,8 @@ def _parse_response(response: bytes) -> (int, str): logger.debug(f"Response: {response}") if response == b"": raise ValueError( - "Empty response. There was no response from the device. Check that the device is connected and correctly configured (baudrate)." + "Empty response. There was no response from the device. Check that the device is connected and correctly " + "configured (baudrate)." ) try: diff --git a/src/hvps/commands/iseg/__init__.py b/src/hvps/commands/iseg/__init__.py index d97ab5f..5e5d08d 100644 --- a/src/hvps/commands/iseg/__init__.py +++ b/src/hvps/commands/iseg/__init__.py @@ -1,27 +1,18 @@ from __future__ import annotations -import re from typing import List -""" -from .module import ( - _get_mon_module_command, - _get_set_module_command, -) -""" - -from .channel import ( - _get_mon_channel_command, - _get_set_channel_command, -) - import logging import serial +import re def _write_command( - ser: serial.Serial, command: bytes, response: bool = True + ser: serial.Serial, + command: bytes, + expected_response_type: type | None, + response: bool = True, ) -> List[str] | None: - """Write a command to a device. + """Write a command to a device and parses the response. Args: ser (Serial): The serial connection to the device. @@ -41,12 +32,12 @@ def _write_command( f"Invalid handshake echo response: {response_value}. expected {command}" ) # response reading - response_value = _parse_response(ser.readline()) + response_value = _parse_response(ser.readline(), expected_response_type) return response_value -def _parse_response(response: bytes) -> List[str]: +def _parse_response(response: bytes, expected_response_type: type | None) -> List[str]: """Parse the response from a device. Args: @@ -66,4 +57,29 @@ def _parse_response(response: bytes) -> List[str]: except UnicodeDecodeError: raise ValueError(f"Invalid response: {response}") - return response.split(",") + if expected_response_type == float or expected_response_type == List[float]: + # pattern for a float in scientific notation followed or not by units + pattern = ( + r"^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?(\s*[a-zA-Z]+(/+[a-zA-Z]+)?)?$" + ) + elif expected_response_type == int or expected_response_type == List[int]: + pattern = r"^[-+]?\d+$" + elif expected_response_type == str or expected_response_type == List[str]: + pattern = r"^[\x00-\x7F]+$" + elif expected_response_type is None: + return response.split(",") + else: + raise ValueError( + f"expected value type of {response}, {expected_response_type}, is not float, int or str" + ) + + split_response = response.split(",") + + for r in split_response: + match = re.match(pattern, r) + if not match: + raise ValueError( + f"Invalid response: {response}, can't be identified as a {expected_response_type}, missmatch in RE" + ) + + return split_response diff --git a/src/hvps/commands/iseg/channel.py b/src/hvps/commands/iseg/channel.py index 4e5d6ac..37562ad 100644 --- a/src/hvps/commands/iseg/channel.py +++ b/src/hvps/commands/iseg/channel.py @@ -1,24 +1,101 @@ from __future__ import annotations +_mon_channel_commands = { + ":CONF:TRIP:ACTION": "Query the current action to be taken when a current trip occurs for the channel.", + ":CONF:TRIP:TIME": "Query the current action to be taken when a current trip occurs for the channel.", + ":CONF:INHP:ACTION": "Query the current action to be taken when a current trip occurs for the channel.", + ":CONF:OUTPUT:MODE": "Query the configured channel output mode.", + ":CONF:OUTPUT:MODE:LIST": "Query the available channel output modes as a list.", + ":CONF:OUTPUT:POL": "Query the current output polarity of the channel.", + ":CONF:OUTPUT:POL:LIST": "Query the available channel output polarities as a list.", + ":READ:VOLT": "Query the voltage set Vset in Volt.", + ":READ:VOLT:LIM": "Query the voltage limit Vlim in Volt.", + ":READ:VOLT:NOM": "Query the channel voltage nominal Vnom in Volt.", + ":READ:VOLT:ON": "Query the channel control bit 'Set On'.", + ":READ:VOLT:EMCY": "Query the channel control bit 'Set Emergency Off'.", + ":READ:CURR": "Query the current set (Iset) in Ampere.", + ":READ:CURR:LIM": "Query the current limit (Ilim) in Ampere.", + ":READ:CURR:NOM": "Query the current nominal (Inom) in Ampere.", + ":READ:CURR:MODE": "Query the configured channel current mode in Ampere.", + ":READ:CURR:MODE:LIST": "Query the available channel current modes as a list.", + ":READ:CURR:BOUNDS": "Query the channel current bounds in Ampere.", + ":READ:RAMP:CURR": "Query the channel current ramp speed in Ampere/second.", + ":READ:RAMP:VOLT": "Query the channel voltage ramp speed in Volt/second.", + ":READ:RAMP:VOLT:MIN": "Query the channel voltage ramp speed minimum in Volt/second.", + ":READ:RAMP:VOLT:MAX": "Query the channel voltage ramp speed maximum in Volt/second.", + ":READ:RAMP:CURR:MIN": "Query the channel current ramp speed minimum in Ampere/second.", + ":READ:RAMP:CURR:MAX": "Query the channel current ramp speed maximum in Ampere/second.", + ":READ:CHAN:CONTROL": "Query the Channel Control register.", + ":READ:CHAN:STATUS": "Query the Channel Status register.", + ":READ:CHAN:EVENT:MASK": "Query the Channel Event Mask register.", + ":MEAS:VOLT": "Query the measured channel voltage in Volt.", + ":MEAS:CURR": "Query the measured channel current in Ampere.", + ":CONF:RAMP:VOLT:UP": "Query the channel voltage ramp up speed in Volt/second.", + ":CONF:RAMP:VOLT:DOWN": "Query the channel voltage ramp down speed in Volt/second.", + ":CONF:RAMP:CURR:UP": "Query the channel current ramp up speed in Ampere/second.", + ":CONF:RAMP:CURR:DOWN": "Query the channel current ramp down speed in Ampere/second.", +} + +_set_channel_commands = { + ":CONF:TRIP:ACTION": "Set the action to be taken when a current trip occurs for the channel.", + ":CONF:TRIP:TIME": "Set the trip timeout with one millisecond resolution.", + ":CONF:INHP:ACTION": "Set the action to be taken when an External Inhibit event occurs for the channel.", + ":CONF:OUTPUT:MODE": "Set the channel output mode.", + ":CONF:OUTPUT:POL": "Set the output polarity of the channel.", + ":VOLT": "Set the channel voltage set.", + ":VOLT:BOUNDS": "Set the channel voltage bounds.", + ":VOLT EMCY": "Clear the channel from state emergency off.", + ":CURR": "Set the channel current set.", + ":CURR:BOUNDS": "Set the channel current bounds in Ampere.", + ":CONF:RAMP:VOLT": "Set the channel voltage ramp speed for up and down direction in Volt/second.", + ":CONF:RAMP:VOLT:UP": "Set the channel voltage ramp up speed in Volt/second.", + ":CONF:RAMP:VOLT:DOWN": "Set the channel voltage ramp down speed in Volt/second.", + ":CONF:RAMP:CURR": "Set the channel current ramp speed for up and down direction in Ampere/second.", + ":CONF:RAMP:CURR:UP": "Set the channel current ramp up speed in Ampere/second.", + ":CONF:RAMP:CURR:DOWN": "Set the channel current ramp down speed in Ampere/second.", + ":VOLT ON": "Switch on the high voltage with the configured ramp speed.", + ":VOLT OFF": "Switch off the high voltage with the configured ramp speed.", + ":VOLT EMCY OFF": "Shut down the channel high voltage (without ramp) or clear the channel from state emergency off.", + ":VOLT EMCY CLR": "Clear the channel from state emergency off. The channel goes to state off.", + ":EVENT CLEAR": "Clear the Channel Event Status register.", + ":EVENT": "Clears single bits or bit combinations in the Channel Event Status register by writing a one to the corresponding bit position.", + ":EVENT:MASK": "Clears single bits or bit combinations in the Channel Event Status register by writing a one to the corresponding bit position.", +} + def _get_mon_channel_command(channel: int, command: str) -> bytes: """ Generates a query command string for monitoring a specific channel. Args: - command (str): The base command without the query symbol. channel (int): The channel number. + command (str): The base command without the query symbol. Returns: - str: The query command string. + bytes: The query command string as bytes. + + Raises: + ValueError: If the provided channel is not a valid positive integer. + ValueError: If the provided command is not a valid command. Example: command = ":MEAS:CURR" channel = 3 - query_command = _get_mon_channel_command(command, channel) + query_command = _get_mon_channel_command(channel, command) print(query_command) - :MEAS:CURR? (@3) + b':MEAS:CURR? (@3)\r\n' """ + if channel < 0: + raise ValueError( + f"Invalid channel '{channel}'. Valid channels are positive integers." + ) + command = command.upper() + if command not in _mon_channel_commands: + valid_commands = ", ".join(_mon_channel_commands.keys()) + raise ValueError( + f"Invalid command '{command}'. Valid commands are: {valid_commands}." + ) + return f"{command.strip()}? (@{channel})\r\n".encode("ascii") @@ -36,12 +113,30 @@ def _get_set_channel_command( Returns: bytes: The order command as a bytes object. + Raises: + ValueError: If the provided channel is not a valid positive integer. + ValueError: If the provided command is not a valid command. + Example: channel = 4 command = ":VOLT" value = 200 order_command = _get_set_channel_command(channel, command, value) print(order_command) - :VOLT 200,(@4)\r\n' + b':VOLT 200,(@4);*OPC?\r\n' """ + if channel < 0: + raise ValueError( + f"Invalid channel '{channel}'. Valid channels are positive integers." + ) + command = command.upper() + if command not in _set_channel_commands: + valid_commands = ", ".join(_mon_channel_commands.keys()) + raise ValueError( + f"Invalid command '{command}'. Valid commands are: {valid_commands}." + ) + + if isinstance(value, float): + value = f"{value:E}" + return f"{command.strip()} {value},(@{channel});*OPC?\r\n".encode("ascii") diff --git a/src/hvps/commands/iseg/module.py b/src/hvps/commands/iseg/module.py new file mode 100644 index 0000000..743f1be --- /dev/null +++ b/src/hvps/commands/iseg/module.py @@ -0,0 +1,112 @@ +from __future__ import annotations + +_mon_module_commands = { + ":READ:MODULE:CHANNELNUMBER": "The number of channels in the module.", + ":READ:FIRMWARE:RELEASE": "Read out Firmware Release (XX.X)", + ":READ:MODULE:STATUS": "Read out module status register", + ":CONF:AVER": "Query the digital filter averaging steps.", + ":CONF:KILL": "Get the current value for the kill enable function.", + ":CONF:ADJUST": "Get the fine adjustment state.", + ":CONF:CAN:ADDR": "Query the module's CAN bus address.", + ":CONF:CAN:BITRATE": "Query the module's CAN bus bit rate.", + ":CONF:SERIAL:BAUD": "Query the device's serial baud rate.", + ":CONF:SERIAL:ECHO": "Check if serial echo is enabled or disabled.", + ":READ:VOLT:LIM": "Query the module's voltage limit in percent.", + ":READ:CURR:LIM": "Query the module's current limit in percent.", + ":READ:RAMP:VOLT": "Query the module's voltage ramp speed in percent/second.", + ":READ:RAMP:CURR": "Query the module's current ramp speed in percent/second.", + ":READ:MODULE:CONTROL": "Query the Module Control register", + ":READ:MODULE:EVENT:STATUS": "Query the Module Event Status register", + ":READ:MODULE:EVENT:MASK": "Query the Module Event Mask register", + ":READ:MODULE:EVENT:CHANSTAT": "Query the Module Event Channel Status register", + ":READ:MODULE:EVENT:CHANMASK": "Query the Module Event Channel Mask register", + ":READ:MODULE:SUPPLY? (@0-6)": "Query the module supply voltages", + ":READ:MODULE:SUPPLY:P24V": "Query the module supply voltage +24 Volt", + ":READ:MODULE:SUPPLY:N24V": "Query the module supply voltage -24 Volt", + ":READ:MODULE:SUPPLY:P5V": "Query the module supply voltage +5 Volt", + ":READ:MODULE:SUPPLY:P3V": "Query the module internal supply voltage +3.3 Volt", + ":READ:MODULE:SUPPLY:P12V": "Query the module internal supply voltage +12 Volt", + ":READ:MODULE:SUPPLY:N12V": "Query the module internal supply voltage -12 Volt", + ":READ:MODULE:TEMPERATURE": "Query the module temperature in degree Celsius", + ":READ:MODULE:SETVALUE": "Query the setvalue changes counter", + ":READ:FIRMWARE:NAME": "Query the module's firmware name", + ":CONF:EVENT:MASK": "Query the Module Event Mask register", + ":CONF:EVENT:CHANMASK": "Query the Module Event Channel Mask register", +} + +_set_module_commands = { + ":CONF:AVER": "Query the digital filter averaging steps.", + ":CONF:KILL": "Get the current value for the kill enable function.", + ":CONF:ADJUST": "Get the fine adjustment state.", + ":CONF:CAN:ADDR": "Query the module's CAN bus address.", + ":CONF:CAN:BITRATE": "Query the module's CAN bus bit rate.", + ":CONF:SERIAL:BAUD": "Query the device's serial baud rate.", + ":CONF:SERIAL:ECHO": "Check if serial echo is enabled or disabled.", + ":CONF:EVENT:MASK": "Set the Module Event Mask register", + ":CONF:EVENT:CHANMASK": "Set the Module Event Channel Mask register", + ":SYSTEM:USER:CONFIG": "Set the device to configuration mode to change the CAN bitrate or address", + ":CONF:EVENT CLEAR": "Reset the Module Event Status register", + ":CONF:EVENT": "Clear single bits or bit combinations in the Module Event Status register", +} + + +def _get_mon_module_command(command: str) -> bytes: + """ + Generates a query command string for monitoring a specific module. + + Args: + command (str): The base command without the query symbol. + + Returns: + bytes: The query command string as bytes. + + Raises: + ValueError: If the provided command is not a valid command. + + Example: + command = ":MEAS:CURR" + query_command = _get_mon_module_command(command) + print(query_command) + b':MEAS:CURR?\r\n' + """ + command = command.upper() + if command not in _mon_module_commands: + valid_commands = ", ".join(_mon_module_commands.keys()) + raise ValueError( + f"Invalid command '{command}'. Valid commands are: {valid_commands}." + ) + + return f"{command.strip()}?\r\n".encode("ascii") + + +def _get_set_module_command(command: str, value: str | int | float | None) -> bytes: + """ + Generates an order command as a bytes object to set a value for a specific module. + + Args: + command (str): The base command without the value and channel suffix. + value (str | int | float | None): The value to be set. Can be a string, integer, float, or None. + + Returns: + bytes: The order command as a bytes object. + + Raises: + ValueError: If the provided command is not a valid command. + + Example: + command = ":VOLT" + value = 200 + order_command = _get_set_module_command(command, value) + print(order_command) + b':VOLT 200;*OPC?\r\n' + """ + command = command.upper() + if command not in _set_module_commands: + valid_commands = ", ".join(_set_module_commands.keys()) + raise ValueError( + f"Invalid command '{command}'. Valid commands are: {valid_commands}." + ) + if isinstance(value, float): + value = f"{value:E}" + + return f"{command.strip()} {value};*OPC?\r\n".encode("ascii") diff --git a/src/hvps/devices/__init__.py b/src/hvps/devices/__init__.py index 7b81e1e..e69de29 100644 --- a/src/hvps/devices/__init__.py +++ b/src/hvps/devices/__init__.py @@ -1,2 +0,0 @@ -from .caen import Caen -from .iseg import Iseg diff --git a/src/hvps/devices/caen/__init__.py b/src/hvps/devices/caen/__init__.py index 52661a3..e69de29 100644 --- a/src/hvps/devices/caen/__init__.py +++ b/src/hvps/devices/caen/__init__.py @@ -1 +0,0 @@ -from .caen import Caen diff --git a/src/hvps/devices/caen/module.py b/src/hvps/devices/caen/module.py index 82a04a2..b884c01 100644 --- a/src/hvps/devices/caen/module.py +++ b/src/hvps/devices/caen/module.py @@ -1,5 +1,4 @@ from functools import cached_property -from typing import List from ...commands.caen.module import _get_mon_module_command, _get_set_module_command from ...commands.caen import _write_command diff --git a/src/hvps/devices/iseg/__init__.py b/src/hvps/devices/iseg/__init__.py index 1f32b3d..e69de29 100644 --- a/src/hvps/devices/iseg/__init__.py +++ b/src/hvps/devices/iseg/__init__.py @@ -1 +0,0 @@ -from .iseg import Iseg diff --git a/src/hvps/devices/iseg/channel.py b/src/hvps/devices/iseg/channel.py index 11d088e..7478050 100644 --- a/src/hvps/devices/iseg/channel.py +++ b/src/hvps/devices/iseg/channel.py @@ -1,4 +1,4 @@ -import serial +from __future__ import annotations from typing import List from ...commands.iseg.channel import ( @@ -11,156 +11,19 @@ class Channel(BaseChannel): - def set_voltage(self, vset: float): - """ - Set the channel voltage set. - - Args: - vset (float): The voltage set value to set in Volt. - """ - command = _get_set_channel_command(self._channel, ":VOLT", vset) - response = _write_command(self._serial, command) - if int(response[0]) != 1: - raise ValueError("Last command haven't been processed.") - - def switch_on_high_voltage(self): - """ - Switch on the high voltage with the configured ramp speed. - """ - command = _get_set_channel_command(self._channel, ":VOLT", "ON") - response = _write_command(self._serial, command) - if int(response[0]) != 1: - raise ValueError("Last command haven't been processed.") - - def switch_off_high_voltage(self): - """ - Switch off the high voltage with the configured ramp speed. - """ - command = _get_set_channel_command(self._channel, ":VOLT", "OFF") - response = _write_command(self._serial, command) - if int(response[0]) != 1: - raise ValueError("Last command haven't been processed.") - - def shutdown_channel_high_voltage(self): - """ - Shut down the channel high voltage (without ramp). The channel stays in Emergency Off until the command EMCY CLR is given. - """ - command = _get_set_channel_command(self._channel, ":VOLT EMCY", "OFF") - response = _write_command(self._serial, command) - if int(response[0]) != 1: - raise ValueError("Last command haven't been processed.") - - def clear_channel_emergency_off(self): - """ - Clear the channel from state emergency off. The channel goes to state off. - """ - command = _get_set_channel_command(self._channel, ":VOLT EMCY", "CLR") - response = _write_command(self._serial, command) - if int(response[0]) != 1: - raise ValueError("Last command haven't been processed.") - - def set_voltage_bounds(self, vbounds: float): - """ - Set the channel voltage bounds. - - Args: - vbounds (float): The voltage bounds value to set in Volt. - """ - command = _get_set_channel_command(self._channel, ":VOLT:BOUNDS", vbounds) - response = _write_command(self._serial, command) - if int(response[0]) != 1: - raise ValueError("Last command haven't been processed.") - - def set_current(self, iset: float): - """ - Set the channel current set. - - Args: - iset (float): The current set value to set in Ampere. - """ - command = _get_set_channel_command(self._channel, ":CURR", iset) - response = _write_command(self._serial, command) - if int(response[0]) != 1: - raise ValueError("Last command haven't been processed.") - - def set_current_bounds(self, ibounds: float): - """ - Set the channel current bounds. - - Args: - ibounds (float): The current bounds value to set in Ampere. - """ - command = _get_set_channel_command(self._channel, ":CURR:BOUNDS", ibounds) - response = _write_command(self._serial, command) - if int(response[0]) != 1: - raise ValueError("Last command haven't been processed.") - - def clear_event_status(self): - """ - Clear the Channel Event Status register. - """ - command = _get_set_channel_command(self._channel, ":EVENT CLEAR", None) - response = _write_command(self._serial, command) - if int(response[0]) != 1: - raise ValueError("Last command haven't been processed.") - - def clear_event_bits(self, bits): - """ - Clears single bits or bit combinations in the Channel Event Status register by writing a one to the corresponding bit position. - - Args: - bits (int | str): The bits or bit combinations to clear. Can be provided as an integer or a string representing the bit combination. - """ - command = _get_set_channel_command(self._channel, ":EVENT", bits) - response = _write_command(self._serial, command) - if int(response[0]) != 1: - raise ValueError("Last command haven't been processed.") - - def set_trip_timeout(self, timeout): - """ - Set the trip timeout with one millisecond resolution. - - Args: - timeout (int): The timeout value to set in milliseconds. Must be in the range 1 to 4095 ms. - """ - if not 1 <= timeout <= 4095: - raise ValueError("Timeout value must be in the range 1 to 4095 ms.") - command = _get_set_channel_command(self._channel, ":CONF:TRIP:TIME", timeout) - response = _write_command(self._serial, command) - if int(response[0]) != 1: - raise ValueError("Last command haven't been processed.") - - def set_trip_action(self, action): - """ - Set the action to be taken when a current trip occurs for the channel. - - Args: - action (int): The action value to set. Possible values are: - - 0: No action (Trip status flag will be set after timeout) - - 1: Turn off the channel with ramp - - 2: Shut down the channel without ramp - - 3: Shut down the whole module without ramp - - 4: Disable the Delayed Trip function - """ - if not 0 <= action <= 4: - raise ValueError( - "Invalid action value. Expected values are 0, 1, 2, 3, or 4." - ) - command = _get_set_channel_command(self._channel, ":CONF:TRIP:ACTION", action) - response = _write_command(self._serial, command) - if int(response[0]) != 1: - raise ValueError("Last command haven't been processed.") - + # Getters @property - def trip_action(self): + def trip_action( + self, + ) -> int: # Instruction for NHR or SHR only. Instruction for NHS """ Query the current action to be taken when a current trip occurs for the channel. Returns: - int: The current action value. + The current action value. """ command = _get_mon_channel_command(self._channel, ":CONF:TRIP:ACTION") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, int) if len(response) != 1: raise ValueError("Unexpected response. Multiple action values received.") if not 0 <= int(response[0]) <= 4: @@ -169,36 +32,34 @@ def trip_action(self): ) return int(response[0]) - def set_external_inhibit_action(self, action): + @property + def trip_timeout( + self, + ) -> int: # Instruction for NHR or SHR only. Instruction for NHS """ - Set the action to be taken when an External Inhibit event occurs for the channel. + Query the current action to be taken when a current trip occurs for the channel. - Args: - action (int): The action value to set. Possible values are: - - 0: No action (External Inhibit status flag will be set) - - 1: Turn off the channel with ramp - - 2: Shut down the channel without ramp - - 3: Shut down the whole module without ramp - - 4: Disable the External Inhibit function + Returns: + The current action value. """ - if not 0 <= action <= 4: - raise ValueError( - "Invalid action value. Expected values are 0, 1, 2, 3, or 4." - ) - command = _get_set_channel_command(self._channel, ":CONF:INHP:ACTION", action) - response = _write_command(self._serial, command) - if int(response[0]) != 1: - raise ValueError("Last command haven't been processed.") + command = _get_mon_channel_command(self._channel, ":CONF:TRIP:TIME") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Unexpected response. Multiple action values received.") + return int(response[0]) - def get_external_inhibit_action(self): + @property + def external_inhibit_action( + self, + ) -> int: # Instruction for NHR or SHR only. Instruction for NHS """ - Query the action to be taken when an External Inhibit event occurs for the channel. + Query the current action to be taken when a current trip occurs for the channel. Returns: - int: The current action value. + The current action value. """ command = _get_mon_channel_command(self._channel, ":CONF:INHP:ACTION") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, int) if len(response) != 1: raise ValueError("Unexpected response. Multiple action values received.") if not 0 <= int(response[0]) <= 4: @@ -207,38 +68,18 @@ def get_external_inhibit_action(self): ) return int(response[0]) - def set_output_mode(self, mode): - """ - Set the channel output mode. - - Args: - mode (int): The output mode value to set. - - Raises: - ValueError: If the specified mode value is not allowed. - - Output Mode allowed values: 1, 2, 3. - """ - allowed_modes = [1, 2, 3] - if mode not in allowed_modes: - raise ValueError("Invalid output mode. Allowed modes are: 1, 2, 3.") - command = _get_set_channel_command(self._channel, ":CONF:OUTPUT:MODE", mode) - response = _write_command(self._serial, command) - if int(response[0]) != 1: - raise ValueError("Last command haven't been processed.") - @property - def output_mode(self): + def output_mode(self) -> int: # Instruction for NHR or SHR only. """ Query the configured channel output mode. Returns: - int: The current output mode value. + The current output mode value. Output Mode allowed values: 1, 2, 3. """ command = _get_mon_channel_command(self._channel, ":CONF:OUTPUT:MODE") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, int) if len(response) != 1: raise ValueError("Wrong number of values were sent, one value expected") @@ -249,16 +90,16 @@ def output_mode(self): return int(response[0]) @property - def available_output_modes(self): + def available_output_modes(self) -> List[int]: # Instruction for NHR or SHR only """ Query the available channel output modes as a list. Returns: - list: The list of available output mode values. + The list of available output mode values. """ command = _get_mon_channel_command(self._channel, ":CONF:OUTPUT:MODE:LIST") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, List[int]) if 0 >= len(response) > 3: raise ValueError("Invalid number of output modes received.") @@ -277,53 +118,31 @@ def available_output_modes(self): return output_values - def set_output_polarity(self, polarity: str): - """ - Set the output polarity of the channel. - - Args: - polarity (str): The output polarity to set. Valid values are "p" for positive and "n" for negative. - - Raises: - ValueError: If an invalid polarity value is provided. - - Example: - channel.set_output_polarity("n") - """ - if polarity not in ["p", "n"]: - raise ValueError( - "Invalid polarity value. Valid values are 'p' for positive and 'n' for negative." - ) - command = _get_set_channel_command(self._channel, ":CONF:OUTPUT:POL", polarity) - response = _write_command(self._serial, command) - if int(response[0]) != 1: - raise ValueError("Not all commands before this query have been processed.") - @property - def output_polarity(self): + def output_polarity(self) -> str: # Instruction for NHR or SHR only """ Query the current output polarity of the channel. Returns: - str: The current output polarity. "p" for positive, "n" for negative. + The current output polarity. "p" for positive, "n" for negative. Example: polarity = channel.output_polarity print(polarity) # Example output: "n" """ command = _get_mon_channel_command(self._channel, ":CONF:OUTPUT:POL") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, str) if len(response) != 1: - raise ValueError("Not all commands before this query have been processed.") + raise ValueError("Last command hasn't been processed.") return response[0] @property - def available_output_polarities(self): + def available_output_polarities(self): # Instruction for NHR or SHR only """ Query the available channel output polarities as a list. Returns: - list: The list of available output polarity values. + The list of available output polarity values. Output Polarity List: "p" - Positive @@ -334,7 +153,7 @@ def available_output_polarities(self): print(polarities) # Example output: ["p", "n"] """ command = _get_mon_channel_command(self._channel, ":CONF:OUTPUT:POL:LIST") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, str) if len(response) != 2: raise ValueError("Wrong number of values were sent, two values expected") @@ -360,13 +179,13 @@ def voltage_set(self): print(voltage) # Example output: 1234.0 """ command = _get_mon_channel_command(self._channel, ":READ:VOLT") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, str) if len(response) != 1: raise ValueError("Wrong number of values were sent, one value expected") return float(response[0][:-1]) @property - def voltage_limit(self): + def voltage_limit(self) -> float: # Instruction for SHR only """ Query the voltage limit Vlim in Volt. @@ -378,13 +197,13 @@ def voltage_limit(self): print(limit) # Example output: 3000.0 """ command = _get_mon_channel_command(self._channel, ":READ:VOLT:LIM") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, float) if len(response) != 1: raise ValueError("Wrong number of values were sent, one value expected") return float(response[0][:-1]) @property - def voltage_nominal(self): + def voltage_nominal(self) -> float: """ Query the channel voltage nominal Vnom in Volt. @@ -396,13 +215,13 @@ def voltage_nominal(self): print(nominal) # Example output: 6000.0 """ command = _get_mon_channel_command(self._channel, ":READ:VOLT:NOM") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, float) if len(response) != 1: raise ValueError("Wrong number of values were sent, one value expected") return float(response[0][:-1]) @property - def voltage_mode(self): + def voltage_mode(self) -> str: # Instruction for NHR or SHR only """ Query the configured channel voltage mode with polarity sign in Volt. @@ -414,13 +233,13 @@ def voltage_mode(self): print(mode) # Example output: "6.0E3V" """ command = _get_mon_channel_command(self._channel, ":READ:VOLT:MODE") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, str) if len(response) != 1: raise ValueError("Wrong number of values were sent, one value expected") - return float(response[0][:-1]) + return response[0][:-1] @property - def voltage_mode_list(self): + def voltage_mode_list(self) -> List[str]: # Instruction for NHR or SHR only """ Query the available channel voltage modes as a list. @@ -432,13 +251,13 @@ def voltage_mode_list(self): print(mode_list) # Example output: ["2.0E3V", "4.0E3V", "6.0E3V"] """ command = _get_mon_channel_command(self._channel, ":READ:VOLT:MODE:LIST") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, List[str]) if len(response) != 3: raise ValueError("Wrong number of values were sent, three values expected") - return [float(modes[:-1]) for modes in response] + return [mode[:-1] for mode in response] @property - def voltage_bounds(self): + def voltage_bounds(self) -> str: """ Query the channel voltage bounds in Volt. @@ -450,13 +269,13 @@ def voltage_bounds(self): print(bounds) # Example output: "0.00000E3V" """ command = _get_mon_channel_command(self._channel, ":READ:VOLT:BOUNDS") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, str) if len(response) != 1: raise ValueError("Wrong number of values were sent, one value expected") - return float(response[0][:-1]) + return response[0][:-1] @property - def is_set_on(self) -> bool: + def set_on(self) -> bool: """ Query the channel control bit "Set On". @@ -464,17 +283,17 @@ def is_set_on(self) -> bool: bool: True if the channel control bit is set on, False otherwise. Example: - is_on = channel.is_set_on + is_on = channel.set_on print(is_on) # Example output: True """ command = _get_mon_channel_command(self._channel, ":READ:VOLT:ON") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, bool) if len(response) != 1: raise ValueError("Wrong number of values were received, one value expected") return int(response[0][:-1]) == 1 @property - def is_set_emergency_off(self) -> bool: + def emergency_off(self) -> bool: """ Query the channel control bit "Set Emergency Off". @@ -482,11 +301,11 @@ def is_set_emergency_off(self) -> bool: bool: True if the channel control bit is set to Emergency Off, False otherwise. Example: - is_emergency_off = channel.is_set_emergency_off + is_emergency_off = channel.emergency_off print(is_emergency_off) # Example output: False """ command = _get_mon_channel_command(self._channel, ":READ:VOLT:EMCY") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, bool) if len(response) != 1: raise ValueError("Wrong number of values were received, one value expected") return int(response[0][:-1]) == 1 @@ -505,15 +324,13 @@ def current_set(self) -> float: """ command = _get_mon_channel_command(self._channel, ":READ:CURR") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, float) if len(response) != 1: raise ValueError("Wrong number of values were received, one value expected") - return float( - response[0][:-1] - ) # Remove the last character from the response (unit) + return float(response[0][:-1]) @property - def current_limit(self) -> float: + def current_limit(self) -> float: # Instruction for SHR only """ Query the current limit (Ilim) in Ampere. @@ -526,7 +343,7 @@ def current_limit(self) -> float: """ command = _get_mon_channel_command(self._channel, ":READ:CURR:LIM") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, float) if len(response) != 1: raise ValueError("Wrong number of values were received, one value expected") return float( @@ -547,7 +364,7 @@ def current_nominal(self) -> float: """ command = _get_mon_channel_command(self._channel, ":READ:CURR:NOM") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, float) if len(response) != 1: raise ValueError("Wrong number of values were received, one value expected") return float( @@ -555,7 +372,7 @@ def current_nominal(self) -> float: ) # Remove the last character from the response (unit) @property - def current_mode(self) -> float: + def current_mode(self) -> float: # Instruction for NHR or SHR only """ Query the configured channel current mode in Ampere. @@ -568,7 +385,7 @@ def current_mode(self) -> float: """ command = _get_mon_channel_command(self._channel, ":READ:CURR:MODE") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, float) if len(response) != 1: raise ValueError("Wrong number of values were received, one value expected") return float( @@ -576,7 +393,7 @@ def current_mode(self) -> float: ) # Remove the last character from the response (unit) @property - def current_mode_list(self) -> List[float]: + def current_mode_list(self) -> List[float]: # Instruction for NHR or SHR only """ Query the available channel current modes as a list. @@ -589,7 +406,7 @@ def current_mode_list(self) -> List[float]: """ command = _get_mon_channel_command(self._channel, ":READ:CURR:MODE:LIST") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, List[float]) if len(response) != 1: raise ValueError("Wrong number of values were received, one value expected") modes = [ @@ -611,13 +428,13 @@ def current_bounds(self) -> float: """ command = _get_mon_channel_command(self._channel, ":READ:CURR:BOUNDS") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, float) if len(response) != 1: raise ValueError("Wrong number of values were received, one value expected") return float(response[0][:-1]) @property - def current_ramp_speed(self) -> float: + def current_ramp_speed(self) -> float: # Instruction for EHS, NHR or SHR only """ Query the channel current ramp speed in Ampere/second. @@ -630,13 +447,13 @@ def current_ramp_speed(self) -> float: """ command = _get_mon_channel_command(self._channel, ":READ:RAMP:CURR") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, float) if len(response) != 1: raise ValueError("Wrong number of values were received, one value expected") return float(response[0][:-3]) @property - def voltage_ramp_speed(self) -> float: + def voltage_ramp_speed(self) -> float: # Instruction for EHS, NHR or SHR only """ Query the channel voltage ramp speed in Volt/second. @@ -649,13 +466,15 @@ def voltage_ramp_speed(self) -> float: """ command = _get_mon_channel_command(self._channel, ":READ:RAMP:VOLT") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, float) if len(response) != 1: raise ValueError("Wrong number of values were received, one value expected") return float(response[0][:-3]) @property - def voltage_ramp_speed_minimum(self) -> float: + def voltage_ramp_speed_minimum( + self, + ) -> float: # Instruction for EHS, NHR or SHR only """ Query the channel voltage ramp speed minimum in Volt/second. @@ -668,13 +487,15 @@ def voltage_ramp_speed_minimum(self) -> float: """ command = _get_mon_channel_command(self._channel, ":READ:RAMP:VOLT:MIN") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, float) if len(response) != 1: raise ValueError("Wrong number of values were received, one value expected") return float(response[0][:-3]) @property - def voltage_ramp_speed_maximum(self) -> float: + def voltage_ramp_speed_maximum( + self, + ) -> float: # Instruction for EHS, NHR or SHR only """ Query the channel voltage ramp speed maximum in Volt/second. @@ -687,13 +508,15 @@ def voltage_ramp_speed_maximum(self) -> float: """ command = _get_mon_channel_command(self._channel, ":READ:RAMP:VOLT:MAX") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, float) if len(response) != 1: raise ValueError("Wrong number of values were received, one value expected") return float(response[0][:-3]) @property - def current_ramp_speed_minimum(self) -> float: + def current_ramp_speed_minimum( + self, + ) -> float: # Instruction for EHS, NHR or SHR only """ Query the channel current ramp speed minimum in Ampere/second. @@ -706,13 +529,15 @@ def current_ramp_speed_minimum(self) -> float: """ command = _get_mon_channel_command(self._channel, ":READ:RAMP:CURR:MIN") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, float) if len(response) != 1: raise ValueError("Wrong number of values were received, one value expected") return float(response[0][:-3]) @property - def current_ramp_speed_maximum(self) -> float: + def current_ramp_speed_maximum( + self, + ) -> float: # Instruction for EHS, NHR or SHR only """ Query the channel current ramp speed maximum in Ampere/second. @@ -725,7 +550,528 @@ def current_ramp_speed_maximum(self) -> float: """ command = _get_mon_channel_command(self._channel, ":READ:RAMP:CURR:MAX") - response = _write_command(self._serial, command) + response = _write_command(self._serial, command, float) if len(response) != 1: raise ValueError("Wrong number of values were received, one value expected") return float(response[0][:-3]) + + @property + def channel_control(self) -> int: + """ + Query the Channel Control register. + + Returns: + int: The Channel Control register value. + + Example: + control = channel.channel_control + print(control) # Example output: 8 + + """ + command = _get_mon_channel_command(self._channel, ":READ:CHAN:CONTROL") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def channel_status(self) -> int: + """ + Query the Channel Status register. + + Returns: + int: The Channel Status register value. + + Example: + status = channel.channel_status + print(status) # Example output: 132 + + """ + command = _get_mon_channel_command(self._channel, ":READ:CHAN:STATUS") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def channel_event_mask(self) -> int: + """ + Query the Channel Event Mask register. + + Returns: + int: The Channel Event Mask register value. + + Example: + mask = channel.channel_event_mask + print(mask) # Example output: 0 + """ + command = _get_mon_channel_command(self._channel, "READ:CHAN:EVENT:MASK") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def measured_voltage(self) -> float: + """ + Query the measured channel voltage in Volt. + + Returns: + float: The measured channel voltage. + + Example: + voltage = channel.measured_voltage + print(voltage) # Example output: 1234.56 + """ + command = _get_mon_channel_command(self._channel, ":MEAS:VOLT") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return float(response[0][:-1]) + + @property + def measured_current(self) -> float: + """ + Query the measured channel current in Ampere. + + Returns: + float: The measured channel current. + + Example: + current = channel.measured_current + print(current) # Example output: 0.00123456 + """ + command = _get_mon_channel_command(self._channel, ":MEAS:CURR") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return float(response[0][:-1]) + + @property + def channel_voltage_ramp_up_speed( + self, + ) -> float: # Instruction for EHS, NHR or SHR only + """ + Query the channel voltage ramp up speed in Volt/second. + + Returns: + float: The channel voltage ramp up speed in Volt/second. + + Example: + speed = channel.channel_voltage_ramp_up_speed + print(speed) # Example output: 0.250E3 + """ + command = _get_mon_channel_command(self._channel, ":CONF:RAMP:VOLT:UP") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return float(response[0][:-3]) # Remove the last 3 characters (unit) + + @property + def channel_voltage_ramp_down_speed( + self, + ) -> float: # Instruction for EHS, NHR or SHR only + """ + Query the channel voltage ramp down speed in Volt/second. + + Returns: + float: The channel voltage ramp down speed in Volt/second. + + Example: + speed = channel.channel_voltage_ramp_down_speed + print(speed) # Example output: 0.12500E3 + """ + command = _get_mon_channel_command(self._channel, ":CONF:RAMP:VOLT:DOWN") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return float( + response[0][:-3] + ) # Remove the last 3 characters (unit) and cast to float + + @property + def channel_current_ramp_up_speed(self) -> float: + """ + Query the channel current ramp up speed in Ampere/second. + + Returns: + float: The channel current ramp up speed in Ampere/second. + + Example: + speed = channel.channel_voltage_ramp_down_speed + print(speed) # Example output: 0.12500E3 + """ + command = _get_mon_channel_command(self._channel, ":CONF:RAMP:CURR:UP") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return float( + response[0][:-3] + ) # Remove the last 3 characters (unit) and cast to float + + @property + def channel_current_ramp_down_speed(self) -> float: + """ + Query the channel current ramp down speed in Ampere/second. + + Returns: + float: The channel current ramp down speed in Ampere/second. + + """ + command = _get_mon_channel_command(self._channel, ":CONF:RAMP:CURR:DOWN") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return float( + response[0][:-3] + ) # Remove the last 3 characters (unit) and cast to float + + # Setters + + @trip_action.setter + def trip_action( + self, action: int + ) -> None: # Instruction for NHR or SHR only. Instruction for NHS + """ + Set the action to be taken when a current trip occurs for the channel. + + Args: + action: The action value to set. Possible values are: + - 0: No action (Trip status flag will be set after timeout) + - 1: Turn off the channel with ramp + - 2: Shut down the channel without ramp + - 3: Shut down the whole module without ramp + - 4: Disable the Delayed Trip function + """ + if not 0 <= action <= 4: + raise ValueError( + "Invalid action value. Expected values are 0, 1, 2, 3, or 4." + ) + command = _get_set_channel_command(self._channel, ":CONF:TRIP:ACTION", action) + response = _write_command(self._serial, command, None) + if int(response[0]) != 1: + raise ValueError("Last command haven't been processed.") + + @trip_timeout.setter + def trip_timeout( + self, timeout: int + ) -> None: # Instruction for NHR or SHR only. Instruction for NHS + """ + Set the trip timeout with one millisecond resolution. + + Args: + timeout: The timeout value to set in milliseconds. Must be in the range 1 to 4095 ms. + """ + if not 1 <= timeout <= 4095: + raise ValueError("Timeout value must be in the range 1 to 4095 ms.") + command = _get_set_channel_command(self._channel, ":CONF:TRIP:TIME", timeout) + response = _write_command(self._serial, command, None) + if int(response[0]) != 1: + raise ValueError("Last command haven't been processed.") + + @external_inhibit_action.setter + def external_inhibit_action( + self, action: int + ) -> None: # Instruction for NHR or SHR only. Instruction for NHS + """ + Set the action to be taken when an External Inhibit event occurs for the channel. + + Args: + action: The action value to set. Possible values are: + - 0: No action (External Inhibit status flag will be set) + - 1: Turn off the channel with ramp + - 2: Shut down the channel without ramp + - 3: Shut down the whole module without ramp + - 4: Disable the External Inhibit function + """ + if not 0 <= action <= 4: + raise ValueError( + "Invalid action value. Expected values are 0, 1, 2, 3, or 4." + ) + command = _get_set_channel_command(self._channel, ":CONF:INHP:ACTION", action) + response = _write_command(self._serial, command, None) + if int(response[0]) != 1: + raise ValueError("Last command haven't been processed.") + + @output_mode.setter + def output_mode(self, mode: int) -> None: # Instruction for NHR or SHR only. + """ + Set the channel output mode. + + Args: + mode: The output mode value to set. + + Raises: + ValueError: If the specified mode value is not allowed. + + Output Mode allowed values: 1, 2, 3. + """ + allowed_modes = [1, 2, 3] + if mode not in allowed_modes: + raise ValueError("Invalid output mode. Allowed modes are: 1, 2, 3.") + command = _get_set_channel_command(self._channel, ":CONF:OUTPUT:MODE", mode) + response = _write_command(self._serial, command, None) + if int(response[0]) != 1: + raise ValueError("Last command haven't been processed.") + + @output_polarity.setter + def output_polarity( + self, polarity: str + ) -> None: # Instruction for NHR or SHR only. + """ + Set the output polarity of the channel. + + Args: + polarity: The output polarity to set. Valid values are "p" for positive and "n" for negative. + + Raises: + ValueError: If an invalid polarity value is provided. + + Example: + channel.output_polarity("n") + """ + if polarity not in ["p", "n"]: + raise ValueError( + "Invalid polarity value. Valid values are 'p' for positive and 'n' for negative." + ) + command = _get_set_channel_command(self._channel, ":CONF:OUTPUT:POL", polarity) + response = _write_command(self._serial, command, None) + if int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @voltage_set.setter + def voltage_set(self, vset: float) -> None: + """ + Set the channel voltage set. + + Args: + vset (float): The voltage set value to set in Volt. + """ + command = _get_set_channel_command(self._channel, ":VOLT", vset) + response = _write_command(self._serial, command, None) + if int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @voltage_bounds.setter + def voltage_bounds(self, vbounds: float) -> None: + """ + Set the channel voltage bounds. + + Args: + vbounds (float): The voltage bounds value to set in Volt. + """ + command = _get_set_channel_command(self._channel, ":VOLT:BOUNDS", vbounds) + response = _write_command(self._serial, command, None) + if int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + def clear_emergency_off(self) -> None: + """ + Clear the channel from state emergency off. The channel goes to state off. + """ + command = _get_set_channel_command(self._channel, ":VOLT EMCY", "CLR") + response = _write_command(self._serial, command, None) + if int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @current_set.setter + def current_set(self, iset: float) -> None: + """ + Set the channel current set. + + Args: + iset (float): The current set value to set in Ampere. + """ + command = _get_set_channel_command(self._channel, ":CURR", iset) + response = _write_command(self._serial, command, None) + if int(response[0]) != 1: + raise ValueError("Last command haven't been processed.") + + @current_bounds.setter + def current_bounds(self, ibounds: float) -> None: + """ + Set the channel current bounds. + + Args: + ibounds (float): The current bounds value to set in Ampere. + """ + command = _get_set_channel_command(self._channel, ":CURR:BOUNDS", ibounds) + response = _write_command(self._serial, command, None) + if int(response[0]) != 1: + raise ValueError("Last command haven't been processed.") + + def set_channel_voltage_ramp_up_down_speed( + self, speed: int + ) -> None: # Instruction for EHS, NHR or SHR only + """ + Set the channel voltage ramp speed for up and down direction in Volt/second. + + Args: + speed (int): The voltage ramp speed in Volt/second. + + Example: + channel.set_channel_voltage_ramp_up_down_speed(250) + """ + command = _get_set_channel_command(self._channel, ":CONF:RAMP:VOLT", speed) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @channel_voltage_ramp_up_speed.setter + def channel_voltage_ramp_up_speed( + self, speed: int + ) -> None: # Instruction for EHS, NHR or SHR only + """ + Set the channel voltage ramp up speed in Volt/second. + + Args: + speed (int): The voltage ramp up speed in Volt/second. + + Example: + channel.set_channel_voltage_ramp_up_speed(250) + """ + command = _get_set_channel_command(self._channel, ":CONF:RAMP:VOLT:UP", speed) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @channel_voltage_ramp_down_speed.setter + def channel_voltage_ramp_down_speed( + self, speed: float + ) -> None: # Instruction for EHS, NHR or SHR only + """ + Set the channel voltage ramp down speed in Volt/second. + + Args: + speed (float): The channel voltage ramp down speed to set in Volt/second. + + Example: + channel.set_channel_voltage_ramp_down_speed(125.0) + """ + command = _get_set_channel_command(self._channel, ":CONF:RAMP:VOLT:DOWN", speed) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + def set_channel_current_ramp_up_down_speed( + self, speed: float + ) -> None: # Instruction for EHS, NHR or SHR only + """ + Set the channel current ramp speed for up and down direction in Ampere/second. + + Args: + speed (float): The channel current ramp down speed to set in Ampere/second. + + Example: + channel.set_channel_current_ramp_up_down_speed(125.0) + """ + command = _get_set_channel_command(self._channel, ":CONF:RAMP:CURR", speed) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @channel_current_ramp_up_speed.setter + def channel_current_ramp_up_speed( + self, speed: float + ) -> None: # Instruction for EHS, NHR or SHR only + """ + Set the channel current ramp up speed in Ampere/second. + + Args: + speed (float): The channel current ramp up speed to set in Ampere/second. + + Example: + channel.set_channel_current_ramp_up_speed(125.0) + """ + command = _get_set_channel_command(self._channel, ":CONF:RAMP:CURR:UP", speed) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @channel_current_ramp_down_speed.setter + def channel_current_ramp_down_speed( + self, speed: float + ) -> None: # Instruction for EHS, NHR or SHR only + """ + Set the channel current ramp down speed in Ampere/second. + + Args: + speed (float): The channel current ramp down speed to set in Ampere/second. + + Example: + channel.set_channel_current_ramp_down_speed(125.0) + """ + command = _get_set_channel_command(self._channel, ":CONF:RAMP:CURR:DOWN", speed) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + def switch_on_high_voltage(self) -> None: + """ + Switch on the high voltage with the configured ramp speed. + """ + command = _get_set_channel_command(self._channel, ":VOLT ON", None) + response = _write_command(self._serial, command, None) + if int(response[0]) != 1: + raise ValueError("Last command haven't been processed.") + + def switch_off_high_voltage(self) -> None: + """ + Switch off the high voltage with the configured ramp speed. + """ + command = _get_set_channel_command(self._channel, ":VOLT OFF", None) + response = _write_command(self._serial, command, None) + if int(response[0]) != 1: + raise ValueError("Last command haven't been processed.") + + def shutdown_channel_high_voltage(self) -> None: + """ + Shut down the channel high voltage (without ramp). The channel stays in Emergency Off until the command EMCY CLR is given. + """ + command = _get_set_channel_command(self._channel, ":VOLT EMCY OFF", None) + response = _write_command(self._serial, command, None) + if int(response[0]) != 1: + raise ValueError("Last command haven't been processed.") + + def clear_channel_emergency_off(self) -> None: + """ + Clear the channel from state emergency off. The channel goes to state off. + """ + command = _get_set_channel_command(self._channel, ":VOLT EMCY CLR", None) + response = _write_command(self._serial, command, None) + if int(response[0]) != 1: + raise ValueError("Last command haven't been processed.") + + def clear_event_status(self) -> None: + """ + Clear the Channel Event Status register. + """ + command = _get_set_channel_command(self._channel, ":EVENT CLEAR", None) + response = _write_command(self._serial, command, None) + if int(response[0]) != 1: + raise ValueError("Last command haven't been processed.") + + def clear_event_bits(self, bits: int) -> None: + """ + Clears single bits or bit combinations in the Channel Event Status register + by writing a one to the corresponding bit position. + + Args: + bits: The bits or bit combinations to clear. Should be provided as an integer. + """ + command = _get_set_channel_command(self._channel, ":EVENT", bits) + response = _write_command(self._serial, command, None) + if int(response[0]) != 1: + raise ValueError("Last command haven't been processed.") + + def set_event_mask(self, mask: int) -> None: + """ + Set the Channel Event Mask register + + Args: + mask: new mask value + """ + command = _get_set_channel_command(self._channel, ":EVENT:MASK", mask) + response = _write_command(self._serial, command, None) + if int(response[0]) != 1: + raise ValueError("Last command haven't been processed.") diff --git a/src/hvps/devices/iseg/module.py b/src/hvps/devices/iseg/module.py index 51a57ef..b209491 100644 --- a/src/hvps/devices/iseg/module.py +++ b/src/hvps/devices/iseg/module.py @@ -1,3 +1,13 @@ +from __future__ import annotations +from functools import cached_property +from typing import List + +import serial + +from ...commands.iseg.module import _get_mon_module_command, _get_set_module_command +from ...commands.iseg import _write_command +from ...utils.utils import string_number_to_bit_array +from .channel import Channel from ..module import Module as BaseModule from .channel import Channel @@ -5,3 +15,637 @@ class Module(BaseModule): def channel(self, channel: int) -> Channel: return super().channel(channel) + + def __init__(self, _serial: serial.Serial): + self._serial = _serial + self._channels: List[Channel] = [] + + @cached_property + def number_of_channels(self) -> int: + """The number of channels in the module. + + Returns: + int: The number of channels. + """ + command = _get_mon_module_command(":READ:MODULE:CHANNELNUMBER") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def firmware_release(self) -> str: + """ + Read out Firmware Release (XX.X) + + Returns: + str: The firmware release. + """ + command = _get_mon_module_command(":READ:FIRMWARE:RELEASE") + response = _write_command(self._serial, command, str) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return response[0] + + @property + def module_status(self) -> dict: + """ + Read out module status register + + Returns: + str: The board alarm status value. + """ + command = _get_mon_module_command(":READ:MODULE:STATUS") + response = _write_command(self._serial, command, dict) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + + bit_array = string_number_to_bit_array(response[0]) + bit_array = list(reversed(bit_array)) + + # TODO: review this + return { + "Is Voltage Ramp Speed Limited": bit_array[21], + "Is Fast Ramp Down": bit_array[16], + "Is Kill Enable": bit_array[15], + "Is Temperature Good": bit_array[14], + "Is Supply Good": bit_array[13], + "Is Module Good": bit_array[12], + "Is Event Active": bit_array[11], + "Is Safety Loop Good": bit_array[10], + "Is No Ramp": bit_array[9], + "Is No Sum": bit_array[8], + "Is Input Error": bit_array[6], + "Is Service Is High": bit_array[4], + "Voltage On": bit_array[3], + "Is Fine Adjustment": bit_array[0], + } + + @property + def filter_averaging_steps(self) -> int: + """Query the digital filter averaging steps. + + Returns: + int: The number of steps for filtering. + """ + command = _get_mon_module_command(":CONF:AVER") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def kill_enable(self) -> int: + """Get the current value for the kill enable function. + + Returns: + int: The current kill enable value. 1 for enable, 0 for disable. + """ + command = _get_mon_module_command(":CONF:KILL") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def adjustment(self) -> int: + """Get the fine adjustment state. + + Returns: + int: The current fine adjustment state. 1 for on, 0 for off. + """ + command = _get_mon_module_command(":CONF:ADJUST") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def module_can_address(self) -> int: + """Query the module's CAN bus address. + + Returns: + int: The current CAN bus address of the module. + """ + command = _get_mon_module_command(":CONF:CAN:ADDR") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def module_can_bitrate(self) -> int: + """Query the module's CAN bus bit rate. + + Returns: + int: The current CAN bus bit rate of the module. + """ + command = _get_mon_module_command(":CONF:CAN:BITRATE") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def serial_baud_rate( + self, + ) -> ( + int + ): # Instruction is currently implemented for EHS devices with serial interface only + """Query the device's serial baud rate. + + Returns: + int: The current serial baud rate of the device. + """ + command = _get_mon_module_command(":CONF:SERIAL:BAUD") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def serial_echo_enable(self) -> int: + """Check if serial echo is enabled or disabled. + + Returns: + int: 1 if serial echo is enabled, 0 if disabled. + """ + command = _get_mon_module_command(":CONF:SERIAL:ECHO") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def serial_echo_enabled(self) -> bool: + return self.serial_echo_enable == 1 + + @property + def serial_echo_disabled(self) -> bool: + return self.serial_echo_enable == 0 + + @property + def module_voltage_limit(self) -> float: + """Query the module's voltage limit in percent. + + Returns: + float: The current voltage limit of the module. + """ + command = _get_mon_module_command(":READ:VOLT:LIM") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return float(response[0].rstrip("%")) + + @property + def module_current_limit(self) -> float: + """Query the module's current limit in percent. + + Returns: + float: The current current limit of the module. + """ + command = _get_mon_module_command(":READ:CURR:LIM") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return float(response[0].rstrip("%")) + + @property + def module_voltage_ramp_speed(self) -> float: + """Query the module's voltage ramp speed in percent/second. + + Returns: + float: The current voltage ramp speed of the module. + """ + command = _get_mon_module_command(":READ:RAMP:VOLT") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return float(response[0][:3]) + + @property + def module_current_ramp_speed(self) -> float: + """Query the module's current ramp speed in percent/second. + + Returns: + float: The current current ramp speed of the module. + """ + command = _get_mon_module_command(":READ:RAMP:CURR") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return float(response[0][:3]) + + @property + def module_control_register(self) -> int: + """Query the Module Control register. + + Returns: + int: The value of the Module Control register. + """ + command = _get_mon_module_command(":READ:MODULE:CONTROL") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def module_status_register(self) -> int: + """Query the Module Status register. + + Returns: + int: The value of the Module Status register. + """ + command = _get_mon_module_command(":READ:MODULE:STATUS") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def module_event_status_register(self) -> int: + """Query the Module Event Status register. + + Returns: + int: The value of the Module Event Status register. + """ + command = _get_mon_module_command(":READ:MODULE:EVENT:STATUS") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def module_event_mask_register(self) -> int: + """Query the Module Event Mask register. + + Returns: + int: The value of the Module Event Mask register. + """ + command = _get_mon_module_command(":READ:MODULE:EVENT:MASK") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def module_event_channel_status_register(self) -> int: + """Query the Module Event Channel Status register. + + Returns: + int: The value of the Module Event Channel Status register. + """ + command = _get_mon_module_command(":READ:MODULE:EVENT:CHANSTAT") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def module_event_channel_mask_register(self) -> int: + """Query the Module Event Channel Mask register. + + Returns: + int: The value of the Module Event Channel Mask register. + """ + command = _get_mon_module_command(":READ:MODULE:EVENT:CHANMASK") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def module_supply_voltage(self) -> List[float]: + """Query the module supply voltages. + + Returns: + Tuple[str, str, str, str, str, str]: The module supply voltages. + """ + command = _get_mon_module_command(":READ:MODULE:SUPPLY? (@0-6)") + response = _write_command(self._serial, command, List[float]) + if len(response) != 7: + raise ValueError("Wrong number of values were received, one value expected") + + return [float(string[:-1]) for string in response] + + @property + def module_supply_voltage_p24v(self) -> float: + """Query the module supply voltage +24 Volt. + + Returns: + float: The module supply voltage +24 Volt. + """ + command = _get_mon_module_command(":READ:MODULE:SUPPLY:P24V") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + voltage = response[0][:-1] + return float(voltage) + + @property + def module_supply_voltage_n24v(self) -> float: + """Query the module supply voltage -24 Volt. + + Returns: + float: The module supply voltage -24 Volt. + """ + command = _get_mon_module_command(":READ:MODULE:SUPPLY:N24V") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + voltage = response[0][:-1] + return float(voltage) + + @property + def module_supply_voltage_p5v(self) -> float: + """Query the module supply voltage +5 Volt. + + Returns: + float: The module supply voltage +5 Volt. + """ + command = _get_mon_module_command(":READ:MODULE:SUPPLY:P5V") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + voltage = response[0][:-1] + return float(voltage) + + @property + def module_supply_voltage_p3v(self) -> float: + """Query the module internal supply voltage +3.3 Volt. + + Returns: + float: The module internal supply voltage +3.3 Volt. + """ + command = _get_mon_module_command(":READ:MODULE:SUPPLY:P3V") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + voltage = response[0][:-1] + return float(voltage) + + @property + def module_supply_voltage_p12v(self) -> float: + """Query the module internal supply voltage +12 Volt. + + Returns: + float: The module internal supply voltage +12 Volt. + """ + command = _get_mon_module_command(":READ:MODULE:SUPPLY:P12V") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + voltage = response[0][:-1] + return float(voltage) + + @property + def module_supply_voltage_n12v(self) -> float: + """Query the module internal supply voltage -12 Volt. + + Returns: + float: The module internal supply voltage -12 Volt. + """ + command = _get_mon_module_command(":READ:MODULE:SUPPLY:N12V") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + voltage = response[0][:-1] + return float(voltage) + + @property + def module_temperature(self) -> float: + """Query the module temperature in degree Celsius. + + Returns: + float: The module temperature in degree Celsius. + """ + command = _get_mon_module_command(":READ:MODULE:TEMPERATURE") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + temperature = response[0][:-1] + return float(temperature) + + @property + def setvalue_changes_counter(self) -> int: + """Query the setvalue changes counter. + + Returns: + int: The setvalue changes counter. + """ + command = _get_mon_module_command(":READ:MODULE:SETVALUE") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def firmware_name(self) -> str: + """Query the module's firmware name. + + Returns: + str: The firmware name. + """ + command = _get_mon_module_command(":READ:FIRMWARE:NAME") + response = _write_command(self._serial, command, str) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return response[0] + + @property + def configuration_mode(self) -> bool: + """Check if the device is in configuration mode. + + Returns: + bool: true if in configuration mode, otherwise false. + """ + command = _get_mon_module_command(":SYSTEM:USER:CONFIG") + response = _write_command(self._serial, command, bool) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) == 1 + + # Setters + + @serial_baud_rate.setter + def serial_baud_rate(self, baud_rate: int) -> None: + """Set the device's serial baud rate. + + Args: + baud_rate (int): The serial baud rate to set. + """ + command = _get_set_module_command(":CONF:SERIAL:BAUD", baud_rate) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != baud_rate: + raise ValueError("Last command hasn't been processed.") + + @serial_echo_enable.setter + def serial_echo_enable(self, enabled: int) -> None: + """Enable or disable serial echo. + + Args: + enabled (int): 1 to enable serial echo, 0 to disable. + """ + if enabled not in [0, 1]: + raise ValueError( + "Invalid serial echo value. Please choose 1 for enabled or 0 for disabled." + ) + + command = _get_set_module_command(":CONF:SERIAL:ECHO", enabled) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @filter_averaging_steps.setter + def filter_averaging_steps(self, steps: int) -> None: + """Set the number of digital filter averaging steps. + + Args: + steps (int): The number of steps for filtering. Accepts values 1, 16, 64, 256, 512, or 1024. + + Raises: + ValueError: If an invalid number of steps is provided. + """ + valid_steps = [1, 16, 64, 256, 512, 1024] + if steps not in valid_steps: + raise ValueError( + f"Invalid number of steps. Please choose from {valid_steps}." + ) + + command = _get_set_module_command(":CONF:AVER", str(steps)) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @kill_enable.setter + def kill_enable(self, enable: int) -> None: + """Set function kill enable (1) or kill disable (0). + + Args: + enable (int): The kill enable value to set. Accepts 1 for enable or 0 for disable. + """ + if enable not in [0, 1]: + raise ValueError( + "Invalid kill enable value. Please choose 1 for enable or 0 for disable." + ) + + command = _get_set_module_command(":CONF:KILL", str(enable)) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @adjustment.setter + def adjustment(self, value: int) -> None: + """Set the fine adjustment function on (1) or off (0). + + Args: + value (int): The adjustment value to set. Accepts 1 for on or 0 for off. + """ + if value not in [0, 1]: + raise ValueError( + "Invalid adjustment value. Please choose 1 for on or 0 for off." + ) + + command = _get_set_module_command(":CONF:ADJUST", str(value)) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @module_event_mask_register.setter + def module_event_mask_register(self, mask: int) -> None: + """Set the Module Event Mask register. + + Args: + mask (int): The value to set in the Module Event Mask register. + """ + command = _get_set_module_command(":CONF:EVENT:MASK", str(mask)) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @module_event_channel_mask_register.setter + def module_event_channel_mask_register(self, mask: int) -> None: + """Set the Module Event Channel Mask register. + + Args: + mask (int): The value to set in the Module Event Channel Mask register. + """ + command = _get_set_module_command(":CONF:EVENT:CHANMASK", str(mask)) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @module_can_address.setter + def module_can_address(self, address: int) -> None: + """Set the module's CAN bus address. + + Args: + address (int): The CAN bus address to set (0-63). + """ + if not (0 <= address <= 63): + raise ValueError( + "Invalid CAN bus address. Please choose an address between 0 and 63." + ) + + command = _get_set_module_command(":CONF:CAN:ADDR", str(address)) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @module_can_bitrate.setter + def module_can_bitrate(self, bitrate: int) -> None: + """Set the module's CAN bus bit rate. + + Args: + bitrate (int): The CAN bus bit rate to set. Accepts 125000 or 250000. + """ + if bitrate not in [125000, 250000]: + raise ValueError( + "Invalid CAN bus bitrate. Please choose either 125000 or 250000." + ) + + command = _get_set_module_command(":CONF:CAN:BITRATE", str(bitrate)) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + def enter_configuration_mode(self, serial_number: int): + """Set the device to configuration mode to change the CAN bitrate or address. + + Parameters: + serial_number (int): The device serial number. + + """ + command = _get_set_module_command(":SYSTEM:USER:CONFIG", str(serial_number)) + _write_command(self._serial, command, None) + + def exit_configuration_mode(self): + """Set the device back to normal mode.""" + command = _get_set_module_command(":SYSTEM:USER:CONFIG", "0") + _write_command(self._serial, command, None) + + def set_serial_echo_enabled(self) -> None: + """Enable serial echo.""" + self.serial_echo_enable = 1 + + def set_serial_echo_disabled(self) -> None: + """Disable serial echo.""" + self.serial_echo_enable = 0 + + def reset_module_event_status(self) -> None: + """Reset the Module Event Status register.""" + command = _get_set_module_command(":CONF:EVENT CLEAR", "") + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + def clear_module_event_status_bits(self, bits: int) -> None: + """Clear single bits or bit combinations in the Module Event Status register. + + Args: + bits (int): The bits to clear in the Module Event Status register. + """ + command = _get_set_module_command(":CONF:EVENT", str(bits)) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") diff --git a/src/hvps/devices/iseg/output.py b/src/hvps/devices/iseg/output.py new file mode 100644 index 0000000..b209491 --- /dev/null +++ b/src/hvps/devices/iseg/output.py @@ -0,0 +1,651 @@ +from __future__ import annotations +from functools import cached_property +from typing import List + +import serial + +from ...commands.iseg.module import _get_mon_module_command, _get_set_module_command +from ...commands.iseg import _write_command +from ...utils.utils import string_number_to_bit_array +from .channel import Channel +from ..module import Module as BaseModule +from .channel import Channel + + +class Module(BaseModule): + def channel(self, channel: int) -> Channel: + return super().channel(channel) + + def __init__(self, _serial: serial.Serial): + self._serial = _serial + self._channels: List[Channel] = [] + + @cached_property + def number_of_channels(self) -> int: + """The number of channels in the module. + + Returns: + int: The number of channels. + """ + command = _get_mon_module_command(":READ:MODULE:CHANNELNUMBER") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def firmware_release(self) -> str: + """ + Read out Firmware Release (XX.X) + + Returns: + str: The firmware release. + """ + command = _get_mon_module_command(":READ:FIRMWARE:RELEASE") + response = _write_command(self._serial, command, str) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return response[0] + + @property + def module_status(self) -> dict: + """ + Read out module status register + + Returns: + str: The board alarm status value. + """ + command = _get_mon_module_command(":READ:MODULE:STATUS") + response = _write_command(self._serial, command, dict) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + + bit_array = string_number_to_bit_array(response[0]) + bit_array = list(reversed(bit_array)) + + # TODO: review this + return { + "Is Voltage Ramp Speed Limited": bit_array[21], + "Is Fast Ramp Down": bit_array[16], + "Is Kill Enable": bit_array[15], + "Is Temperature Good": bit_array[14], + "Is Supply Good": bit_array[13], + "Is Module Good": bit_array[12], + "Is Event Active": bit_array[11], + "Is Safety Loop Good": bit_array[10], + "Is No Ramp": bit_array[9], + "Is No Sum": bit_array[8], + "Is Input Error": bit_array[6], + "Is Service Is High": bit_array[4], + "Voltage On": bit_array[3], + "Is Fine Adjustment": bit_array[0], + } + + @property + def filter_averaging_steps(self) -> int: + """Query the digital filter averaging steps. + + Returns: + int: The number of steps for filtering. + """ + command = _get_mon_module_command(":CONF:AVER") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def kill_enable(self) -> int: + """Get the current value for the kill enable function. + + Returns: + int: The current kill enable value. 1 for enable, 0 for disable. + """ + command = _get_mon_module_command(":CONF:KILL") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def adjustment(self) -> int: + """Get the fine adjustment state. + + Returns: + int: The current fine adjustment state. 1 for on, 0 for off. + """ + command = _get_mon_module_command(":CONF:ADJUST") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def module_can_address(self) -> int: + """Query the module's CAN bus address. + + Returns: + int: The current CAN bus address of the module. + """ + command = _get_mon_module_command(":CONF:CAN:ADDR") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def module_can_bitrate(self) -> int: + """Query the module's CAN bus bit rate. + + Returns: + int: The current CAN bus bit rate of the module. + """ + command = _get_mon_module_command(":CONF:CAN:BITRATE") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def serial_baud_rate( + self, + ) -> ( + int + ): # Instruction is currently implemented for EHS devices with serial interface only + """Query the device's serial baud rate. + + Returns: + int: The current serial baud rate of the device. + """ + command = _get_mon_module_command(":CONF:SERIAL:BAUD") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def serial_echo_enable(self) -> int: + """Check if serial echo is enabled or disabled. + + Returns: + int: 1 if serial echo is enabled, 0 if disabled. + """ + command = _get_mon_module_command(":CONF:SERIAL:ECHO") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def serial_echo_enabled(self) -> bool: + return self.serial_echo_enable == 1 + + @property + def serial_echo_disabled(self) -> bool: + return self.serial_echo_enable == 0 + + @property + def module_voltage_limit(self) -> float: + """Query the module's voltage limit in percent. + + Returns: + float: The current voltage limit of the module. + """ + command = _get_mon_module_command(":READ:VOLT:LIM") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return float(response[0].rstrip("%")) + + @property + def module_current_limit(self) -> float: + """Query the module's current limit in percent. + + Returns: + float: The current current limit of the module. + """ + command = _get_mon_module_command(":READ:CURR:LIM") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return float(response[0].rstrip("%")) + + @property + def module_voltage_ramp_speed(self) -> float: + """Query the module's voltage ramp speed in percent/second. + + Returns: + float: The current voltage ramp speed of the module. + """ + command = _get_mon_module_command(":READ:RAMP:VOLT") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return float(response[0][:3]) + + @property + def module_current_ramp_speed(self) -> float: + """Query the module's current ramp speed in percent/second. + + Returns: + float: The current current ramp speed of the module. + """ + command = _get_mon_module_command(":READ:RAMP:CURR") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return float(response[0][:3]) + + @property + def module_control_register(self) -> int: + """Query the Module Control register. + + Returns: + int: The value of the Module Control register. + """ + command = _get_mon_module_command(":READ:MODULE:CONTROL") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def module_status_register(self) -> int: + """Query the Module Status register. + + Returns: + int: The value of the Module Status register. + """ + command = _get_mon_module_command(":READ:MODULE:STATUS") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def module_event_status_register(self) -> int: + """Query the Module Event Status register. + + Returns: + int: The value of the Module Event Status register. + """ + command = _get_mon_module_command(":READ:MODULE:EVENT:STATUS") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def module_event_mask_register(self) -> int: + """Query the Module Event Mask register. + + Returns: + int: The value of the Module Event Mask register. + """ + command = _get_mon_module_command(":READ:MODULE:EVENT:MASK") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def module_event_channel_status_register(self) -> int: + """Query the Module Event Channel Status register. + + Returns: + int: The value of the Module Event Channel Status register. + """ + command = _get_mon_module_command(":READ:MODULE:EVENT:CHANSTAT") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def module_event_channel_mask_register(self) -> int: + """Query the Module Event Channel Mask register. + + Returns: + int: The value of the Module Event Channel Mask register. + """ + command = _get_mon_module_command(":READ:MODULE:EVENT:CHANMASK") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def module_supply_voltage(self) -> List[float]: + """Query the module supply voltages. + + Returns: + Tuple[str, str, str, str, str, str]: The module supply voltages. + """ + command = _get_mon_module_command(":READ:MODULE:SUPPLY? (@0-6)") + response = _write_command(self._serial, command, List[float]) + if len(response) != 7: + raise ValueError("Wrong number of values were received, one value expected") + + return [float(string[:-1]) for string in response] + + @property + def module_supply_voltage_p24v(self) -> float: + """Query the module supply voltage +24 Volt. + + Returns: + float: The module supply voltage +24 Volt. + """ + command = _get_mon_module_command(":READ:MODULE:SUPPLY:P24V") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + voltage = response[0][:-1] + return float(voltage) + + @property + def module_supply_voltage_n24v(self) -> float: + """Query the module supply voltage -24 Volt. + + Returns: + float: The module supply voltage -24 Volt. + """ + command = _get_mon_module_command(":READ:MODULE:SUPPLY:N24V") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + voltage = response[0][:-1] + return float(voltage) + + @property + def module_supply_voltage_p5v(self) -> float: + """Query the module supply voltage +5 Volt. + + Returns: + float: The module supply voltage +5 Volt. + """ + command = _get_mon_module_command(":READ:MODULE:SUPPLY:P5V") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + voltage = response[0][:-1] + return float(voltage) + + @property + def module_supply_voltage_p3v(self) -> float: + """Query the module internal supply voltage +3.3 Volt. + + Returns: + float: The module internal supply voltage +3.3 Volt. + """ + command = _get_mon_module_command(":READ:MODULE:SUPPLY:P3V") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + voltage = response[0][:-1] + return float(voltage) + + @property + def module_supply_voltage_p12v(self) -> float: + """Query the module internal supply voltage +12 Volt. + + Returns: + float: The module internal supply voltage +12 Volt. + """ + command = _get_mon_module_command(":READ:MODULE:SUPPLY:P12V") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + voltage = response[0][:-1] + return float(voltage) + + @property + def module_supply_voltage_n12v(self) -> float: + """Query the module internal supply voltage -12 Volt. + + Returns: + float: The module internal supply voltage -12 Volt. + """ + command = _get_mon_module_command(":READ:MODULE:SUPPLY:N12V") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + voltage = response[0][:-1] + return float(voltage) + + @property + def module_temperature(self) -> float: + """Query the module temperature in degree Celsius. + + Returns: + float: The module temperature in degree Celsius. + """ + command = _get_mon_module_command(":READ:MODULE:TEMPERATURE") + response = _write_command(self._serial, command, float) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + temperature = response[0][:-1] + return float(temperature) + + @property + def setvalue_changes_counter(self) -> int: + """Query the setvalue changes counter. + + Returns: + int: The setvalue changes counter. + """ + command = _get_mon_module_command(":READ:MODULE:SETVALUE") + response = _write_command(self._serial, command, int) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) + + @property + def firmware_name(self) -> str: + """Query the module's firmware name. + + Returns: + str: The firmware name. + """ + command = _get_mon_module_command(":READ:FIRMWARE:NAME") + response = _write_command(self._serial, command, str) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return response[0] + + @property + def configuration_mode(self) -> bool: + """Check if the device is in configuration mode. + + Returns: + bool: true if in configuration mode, otherwise false. + """ + command = _get_mon_module_command(":SYSTEM:USER:CONFIG") + response = _write_command(self._serial, command, bool) + if len(response) != 1: + raise ValueError("Wrong number of values were received, one value expected") + return int(response[0]) == 1 + + # Setters + + @serial_baud_rate.setter + def serial_baud_rate(self, baud_rate: int) -> None: + """Set the device's serial baud rate. + + Args: + baud_rate (int): The serial baud rate to set. + """ + command = _get_set_module_command(":CONF:SERIAL:BAUD", baud_rate) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != baud_rate: + raise ValueError("Last command hasn't been processed.") + + @serial_echo_enable.setter + def serial_echo_enable(self, enabled: int) -> None: + """Enable or disable serial echo. + + Args: + enabled (int): 1 to enable serial echo, 0 to disable. + """ + if enabled not in [0, 1]: + raise ValueError( + "Invalid serial echo value. Please choose 1 for enabled or 0 for disabled." + ) + + command = _get_set_module_command(":CONF:SERIAL:ECHO", enabled) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @filter_averaging_steps.setter + def filter_averaging_steps(self, steps: int) -> None: + """Set the number of digital filter averaging steps. + + Args: + steps (int): The number of steps for filtering. Accepts values 1, 16, 64, 256, 512, or 1024. + + Raises: + ValueError: If an invalid number of steps is provided. + """ + valid_steps = [1, 16, 64, 256, 512, 1024] + if steps not in valid_steps: + raise ValueError( + f"Invalid number of steps. Please choose from {valid_steps}." + ) + + command = _get_set_module_command(":CONF:AVER", str(steps)) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @kill_enable.setter + def kill_enable(self, enable: int) -> None: + """Set function kill enable (1) or kill disable (0). + + Args: + enable (int): The kill enable value to set. Accepts 1 for enable or 0 for disable. + """ + if enable not in [0, 1]: + raise ValueError( + "Invalid kill enable value. Please choose 1 for enable or 0 for disable." + ) + + command = _get_set_module_command(":CONF:KILL", str(enable)) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @adjustment.setter + def adjustment(self, value: int) -> None: + """Set the fine adjustment function on (1) or off (0). + + Args: + value (int): The adjustment value to set. Accepts 1 for on or 0 for off. + """ + if value not in [0, 1]: + raise ValueError( + "Invalid adjustment value. Please choose 1 for on or 0 for off." + ) + + command = _get_set_module_command(":CONF:ADJUST", str(value)) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @module_event_mask_register.setter + def module_event_mask_register(self, mask: int) -> None: + """Set the Module Event Mask register. + + Args: + mask (int): The value to set in the Module Event Mask register. + """ + command = _get_set_module_command(":CONF:EVENT:MASK", str(mask)) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @module_event_channel_mask_register.setter + def module_event_channel_mask_register(self, mask: int) -> None: + """Set the Module Event Channel Mask register. + + Args: + mask (int): The value to set in the Module Event Channel Mask register. + """ + command = _get_set_module_command(":CONF:EVENT:CHANMASK", str(mask)) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @module_can_address.setter + def module_can_address(self, address: int) -> None: + """Set the module's CAN bus address. + + Args: + address (int): The CAN bus address to set (0-63). + """ + if not (0 <= address <= 63): + raise ValueError( + "Invalid CAN bus address. Please choose an address between 0 and 63." + ) + + command = _get_set_module_command(":CONF:CAN:ADDR", str(address)) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + @module_can_bitrate.setter + def module_can_bitrate(self, bitrate: int) -> None: + """Set the module's CAN bus bit rate. + + Args: + bitrate (int): The CAN bus bit rate to set. Accepts 125000 or 250000. + """ + if bitrate not in [125000, 250000]: + raise ValueError( + "Invalid CAN bus bitrate. Please choose either 125000 or 250000." + ) + + command = _get_set_module_command(":CONF:CAN:BITRATE", str(bitrate)) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + def enter_configuration_mode(self, serial_number: int): + """Set the device to configuration mode to change the CAN bitrate or address. + + Parameters: + serial_number (int): The device serial number. + + """ + command = _get_set_module_command(":SYSTEM:USER:CONFIG", str(serial_number)) + _write_command(self._serial, command, None) + + def exit_configuration_mode(self): + """Set the device back to normal mode.""" + command = _get_set_module_command(":SYSTEM:USER:CONFIG", "0") + _write_command(self._serial, command, None) + + def set_serial_echo_enabled(self) -> None: + """Enable serial echo.""" + self.serial_echo_enable = 1 + + def set_serial_echo_disabled(self) -> None: + """Disable serial echo.""" + self.serial_echo_enable = 0 + + def reset_module_event_status(self) -> None: + """Reset the Module Event Status register.""" + command = _get_set_module_command(":CONF:EVENT CLEAR", "") + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") + + def clear_module_event_status_bits(self, bits: int) -> None: + """Clear single bits or bit combinations in the Module Event Status register. + + Args: + bits (int): The bits to clear in the Module Event Status register. + """ + command = _get_set_module_command(":CONF:EVENT", str(bits)) + response = _write_command(self._serial, command, None) + if len(response) != 1 or int(response[0]) != 1: + raise ValueError("Last command hasn't been processed.") diff --git a/src/hvps/devices/iseg/script.py b/src/hvps/devices/iseg/script.py new file mode 100644 index 0000000..23c9d09 --- /dev/null +++ b/src/hvps/devices/iseg/script.py @@ -0,0 +1,19 @@ +import re + +input_file = "module.py" +output_file = "output.py" + +pattern1 = r"-> ([^:]*):" +pattern2 = r"_write_command\(self._serial, command" +group1 = "" + +with open(input_file, "r") as input_f, open(output_file, "w") as output_f: + for line in input_f: + match = re.search(pattern1, line) + if match: + group1 = match.group(1) # Extract the wildcard value + + modified_line = re.sub( + pattern2, f"_write_command(self._serial, command, {group1}", line + ) + output_f.write(modified_line) diff --git a/src/hvps/utils/utils.py b/src/hvps/utils/utils.py index b0300b2..b1bc0e7 100644 --- a/src/hvps/utils/utils.py +++ b/src/hvps/utils/utils.py @@ -4,13 +4,13 @@ def string_number_to_bit_array(string) -> List[bool]: """ - Converts a string representing a 16-bit integer into a list of bits. + Converts a string representing a 32-bit integer into a list of bits. Args: string (str): The string to convert. It must be a string representing an integer (e.g. "01024"). Returns: - list: The list of 16 bits, from least significant to most significant. + list: The list of 32 bits, from least significant to most significant. """ try: @@ -18,7 +18,7 @@ def string_number_to_bit_array(string) -> List[bool]: except ValueError: raise ValueError(f"Invalid string '{string}'. Must be an integer.") - return list(reversed([bool(int(bit)) for bit in f"{string_as_int:016b}"])) + return list(reversed([bool(int(bit)) for bit in f"{string_as_int:032b}"])) def get_serial_ports() -> List[str]: diff --git a/tests/test_caen_commands.py b/tests/test_caen_commands.py index 6d60f4b..1b0dc67 100644 --- a/tests/test_caen_commands.py +++ b/tests/test_caen_commands.py @@ -1,13 +1,16 @@ import pytest -from hvps.commands.caen import ( - _parse_response, - _get_set_module_command, - _get_mon_module_command, +from hvps.commands.caen import _parse_response +from hvps.commands.caen.channel import ( _get_set_channel_command, _get_mon_channel_command, ) +from hvps.commands.caen.module import ( + _get_set_module_command, + _get_mon_module_command, +) + def test_caen_module_get_commands(): with pytest.raises(ValueError): @@ -23,17 +26,16 @@ def test_caen_module_get_commands(): def test_caen_module_set_commands(): - def test_caen_module_set_commands(): - with pytest.raises(ValueError): - # invalid parameter name - _get_set_module_command(0, "TEST", 10) + with pytest.raises(ValueError): + # invalid parameter name + _get_set_module_command(0, "TEST", 10) - with pytest.raises(ValueError): - # invalid board number - _get_set_module_command(-1, "BDNAME", 10) + with pytest.raises(ValueError): + # invalid board number + _get_set_module_command(-1, "BDILKM", 10) - command = _get_set_module_command(0, "BDNAME", 10) - assert command == b"$BD:00,CMD:SET,PAR:BDNAME,VAL:10\r\n" + command = _get_set_module_command(0, "BDILKM", 10) + assert command == b"$BD:00,CMD:SET,PAR:BDILKM,VAL:10\r\n" def test_caen_channel_get_commands(): @@ -99,7 +101,7 @@ def test_caen_parse_response(): response = b"#BD:09,CMD:OK\r\n" bd, value = _parse_response(response) assert bd == 9 - assert value == None + assert value is None with pytest.raises(ValueError): # number needs to be two digits diff --git a/tests/test_caen_serial.py b/tests/test_caen_serial.py index b29972e..4886f47 100644 --- a/tests/test_caen_serial.py +++ b/tests/test_caen_serial.py @@ -35,7 +35,7 @@ def test_caen_module_monitor(): baudrate=serial_baud, connect=True, timeout=timeout, - verbosity=logging.DEBUG, + logging_level=logging.DEBUG, ) print( f"Serial port status: connected: {caen.connected}, port: {caen.port}, baudrate: {caen.baudrate}, timeout: {caen.timeout}" @@ -98,7 +98,7 @@ def test_caen_channel_serial(): baudrate=serial_baud, connect=True, timeout=timeout, - verbosity=logging.DEBUG, + logging_level=logging.DEBUG, ) print( f"Serial port status: connected: {caen.connected}, port: {caen.port}, baudrate: {caen.baudrate}, timeout: {caen.timeout}" diff --git a/tests/test_cli.py b/tests/test_cli.py index 0ecda58..daa6ea7 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,7 +1,6 @@ import os import subprocess import sys -import pytest def run_main_with_arguments(arguments: list) -> tuple: diff --git a/tests/test_iseg_commands.py b/tests/test_iseg_commands.py index 1705862..2f4dcc6 100644 --- a/tests/test_iseg_commands.py +++ b/tests/test_iseg_commands.py @@ -1,5 +1,3 @@ -import pytest - """ trip_action: 4 output_mode: 1 @@ -10,26 +8,93 @@ voltage_nominal: 2000.0 available_output_modes: [1] """ +import pytest +from typing import List, Set + +from hvps.commands.iseg import _parse_response +from hvps.commands.iseg.channel import ( + _get_set_channel_command, + _get_mon_channel_command, +) + +from hvps.commands.iseg.module import ( + _get_set_module_command, + _get_mon_module_command, +) # TODO: validate commands generation and response parsing def test_iseg_module_get_commands(): - pass + with pytest.raises(ValueError): + # invalid parameter name + _get_mon_module_command("TEST") + + command = _get_mon_module_command(":READ:MODULE:EVENT:MASK") + assert command == b":READ:MODULE:EVENT:MASK?\r\n" def test_iseg_module_set_commands(): - pass + with pytest.raises(ValueError): + # invalid parameter name + _get_set_module_command("TEST", 16) + + command = _get_set_module_command(":CONF:AVER", 16) + assert command == b":CONF:AVER 16;*OPC?\r\n" def test_iseg_channel_get_commands(): - pass + with pytest.raises(ValueError): + # invalid parameter name + _get_mon_channel_command(0, "TEST") + + with pytest.raises(ValueError): + # invalid channel number + _get_mon_channel_command(-1, ":CONF:OUTPUT:POL:LIST") + + command = _get_mon_channel_command(0, ":CONF:OUTPUT:POL:LIST") + assert command == b":CONF:OUTPUT:POL:LIST? (@0)\r\n" def test_iseg_channel_set_commands(): - pass + with pytest.raises(ValueError): + # invalid parameter name + _get_set_channel_command(0, "TEST", 0) + + with pytest.raises(ValueError): + # invalid channel number + _get_set_channel_command(-1, ":VOLT:BOUNDS", 10) + + command = _get_set_channel_command(0, ":VOLT:BOUNDS", 10) + assert command == b":VOLT:BOUNDS 10,(@0);*OPC?\r\n" def test_iseg_parse_response(): - pass + response = b"1\r\n" + parsed_response = _parse_response(response, None) + assert parsed_response == ["1"] + + response = b"p,n\r\n" + parsed_response = _parse_response(response, List[str]) + assert parsed_response == ["p", "n"] + + response = b"p\r\n" + parsed_response = _parse_response(response, str) + assert parsed_response == ["p"] + + response = b"1.23400E3\r\n" + parsed_response = _parse_response(response, float) + assert parsed_response == ["1.23400E3"] + + response = b"132\r\n" + parsed_response = _parse_response(response, int) + assert parsed_response == ["132"] + + # non-supported type + with pytest.raises(ValueError): + _parse_response(b"{1, 2}", Set[int]) + + # type missmatch + with pytest.raises(ValueError): + _parse_response(b"1.23400E^3", float) diff --git a/tests/test_iseg_serial.py b/tests/test_iseg_serial.py index cbd30e6..388a46c 100644 --- a/tests/test_iseg_serial.py +++ b/tests/test_iseg_serial.py @@ -1,19 +1,176 @@ +from hvps.devices.iseg.channel import Channel +from hvps.utils import get_serial_ports from hvps import Iseg + import pytest -from hvps.commands.iseg import _get_set_channel_command, _get_mon_channel_command -from hvps.devices.iseg.channel import Channel +import sys import serial - -from .test_caen_serial import serial_skip_decorator +import logging serial_port = "COM4" # change this to the serial port you are using serial_baud = 9600 timeout = 5.0 +def serial_port_available(): + ports = get_serial_ports() + return ports != [] + + +def is_macos(): + return sys.platform == "Darwin" + + +serial_skip_decorator = pytest.mark.skipif( + serial_port_available() or not is_macos(), reason="No serial ports available" +) + + @serial_skip_decorator def test_iseg_module_monitor(): - ... + iseg = Iseg( + port=serial_port, + baudrate=serial_baud, + connect=True, + timeout=timeout, + logging_level=logging.DEBUG, + ) + + print( + f"Serial port status: connected: {iseg.connected}, port: {iseg.port}, baudrate: {iseg.baudrate}, timeout: {iseg.timeout}" + ) + module = iseg.module(0) + + number_of_channels = module.number_of_channels + print(f"number_of_channels: {number_of_channels}") + + firmware_release = module.firmware_release + print(f"firmware_release: {firmware_release}") + + module_status = module.module_status + print(f"module_status: {module_status}") + + filter_averaging_steps = module.filter_averaging_steps + print(f"filter_averaging_steps: {filter_averaging_steps}") + + kill_enable = module.kill_enable + print(f"kill_enable: {kill_enable}") + + adjustment = module.adjustment + print(f"adjustment: {adjustment}") + + module_can_address = module.module_can_address + print(f"module_can_address: {module_can_address}") + + module_can_bitrate = module.module_can_bitrate + print(f"module_can_bitrate: {module_can_bitrate}") + + serial_echo_enabled = module.serial_echo_enabled + print(f"serial_echo_enabled: {serial_echo_enabled}") + + serial_echo_disabled = module.serial_echo_disabled + print(f"serial_echo_disabled: {serial_echo_disabled}") + + module_voltage_limit = module.module_voltage_limit + print(f"module_voltage_limit: {module_voltage_limit}") + + module_current_limit = module.module_current_limit + print(f"module_current_limit: {module_current_limit}") + + module_voltage_ramp_speed = module.module_voltage_ramp_speed + print(f"module_voltage_ramp_speed: {module_voltage_ramp_speed}") + + module_current_ramp_speed = module.module_current_ramp_speed + print(f"module_current_ramp_speed: {module_current_ramp_speed}") + + module_control_register = module.module_control_register + print(f"module_control_register: {module_control_register}") + + module_status_register = module.module_status_register + print(f"module_status_register: {module_status_register}") + + module_event_status_register = module.module_event_status_register + print(f"module_event_status_register: {module_event_status_register}") + + module_event_mask_register = module.module_event_mask_register + print(f"module_event_mask_register: {module_event_mask_register}") + + module_event_channel_status_register = module.module_event_channel_status_register + print( + f"module_event_channel_status_register: {module_event_channel_status_register}" + ) + + module_event_channel_mask_register = module.module_event_channel_mask_register + print(f"module_event_channel_mask_register: {module_event_channel_mask_register}") + + module_supply_voltage = module.module_supply_voltage + print(f"module_supply_voltage: {module_supply_voltage}") + + module_supply_voltage_p24v = module.module_supply_voltage_p24v + print(f"module_supply_voltage_p24v: {module_supply_voltage_p24v}") + + module_supply_voltage_n24v = module.module_supply_voltage_n24v + print(f"module_supply_voltage_n24v: {module_supply_voltage_n24v}") + + module_supply_voltage_p5v = module.module_supply_voltage_p5v + print(f"module_supply_voltage_p5v: {module_supply_voltage_p5v}") + + module_supply_voltage_p3v = module.module_supply_voltage_p3v + print(f"module_supply_voltage_p3v: {module_supply_voltage_p3v}") + + module_supply_voltage_p12v = module.module_supply_voltage_p12v + print(f"module_supply_voltage_p12v: {module_supply_voltage_p12v}") + + module_supply_voltage_n12v = module.module_supply_voltage_n12v + print(f"module_supply_voltage_n12v: {module_supply_voltage_n12v}") + + module_temperature = module.module_temperature + print(f"module_temperature: {module_temperature}") + + setvalue_changes_counter = module.setvalue_changes_counter + print(f"setvalue_changes_counter: {setvalue_changes_counter}") + + firmware_name = module.firmware_name + print(f"firmware_name: {firmware_name}") + + configuration_mode = module.configuration_mode + print(f"configuration_mode: {configuration_mode}") + + # Setter methods + module.serial_baud_rate = 9600 + module.serial_echo_enable = 1 + with pytest.raises(ValueError): + module.serial_echo_enable = 2 + + module.filter_averaging_steps = 256 + with pytest.raises(ValueError): + module.filter_averaging_steps = 257 + + module.kill_enable = 1 + with pytest.raises(ValueError): + module.kill_enable = 2 + + module.adjustment = 0 + with pytest.raises(ValueError): + module.adjustment = 2 + + module.module_event_mask_register = 5 + + module.module_can_address = 12 + with pytest.raises(ValueError): + module.module_can_address = 250 + + module.module_can_bitrate = 250000 + with pytest.raises(ValueError): + module.module_can_bitrate = 250 + + # Other methods + module.enter_configuration_mode(12345) + module.exit_configuration_mode() + module.set_serial_echo_enabled() + module.set_serial_echo_disabled() + module.reset_module_event_status() + module.clear_module_event_status_bits(8) @serial_skip_decorator @@ -45,3 +202,151 @@ def test_iseg_channel_monitor(): available_output_modes = channel.available_output_modes print(f"available_output_modes: {available_output_modes}") + + voltage_mode = channel.voltage_mode + print(f"voltage_mode: {voltage_mode}") + + voltage_mode_list = channel.voltage_mode_list + print(f"voltage_mode_list: {voltage_mode_list}") + + voltage_bounds = channel.voltage_bounds + print(f"voltage_bounds: {voltage_bounds}") + + set_on = channel.set_on + print(f"set_on: {set_on}") + + emergency_off = channel.emergency_off + print(f"emergency_off: {emergency_off}") + + current_set = channel.current_set + print(f"current_set: {current_set}") + + current_limit = channel.current_limit + print(f"current_limit: {current_limit}") + + current_nominal = channel.current_nominal + print(f"current_nominal: {current_nominal}") + + current_mode = channel.current_mode + print(f"current_mode: {current_mode}") + + modes = channel.current_mode_list + print(f"modes: {modes}") + + bounds = channel.current_bounds + print(f"bounds: {bounds}") + + speed = channel.current_ramp_speed + print(f"speed: {speed}") + + speed = channel.voltage_ramp_speed + print(f"speed: {speed}") + + speed_min = channel.voltage_ramp_speed_minimum + print(f"speed_min: {speed_min}") + + speed_max = channel.voltage_ramp_speed_maximum + print(f"speed_max: {speed_max}") + + speed_min = channel.current_ramp_speed_minimum + print(f"speed_min: {speed_min}") + + speed_max = channel.current_ramp_speed_maximum + print(f"speed_max: {speed_max}") + + control = channel.channel_control + print(f"control: {control}") + + status = channel.channel_status + print(f"status: {status}") + + mask = channel.channel_event_mask + print(f"mask: {mask}") + + voltage = channel.measured_voltage + print(f"voltage: {voltage}") + + current = channel.measured_current + print(f"current: {current}") + + speed = channel.channel_voltage_ramp_up_speed + print(f"speed: {speed}") + + speed = channel.channel_voltage_ramp_down_speed + print(f"speed: {speed}") + + speed = channel.channel_current_ramp_up_speed + print(f"speed: {speed}") + + speed = channel.channel_current_ramp_down_speed + print(f"speed: {speed}") + + # Set the action to be taken when a current trip occurs for the channel + channel.trip_action = 1 + with pytest.raises(ValueError): + channel.trip_action = 5 + + # Set the trip timeout in milliseconds + channel.trip_timeout = 2000 + with pytest.raises(ValueError): + channel.trip_timeout = 0 + + # Set the action to be taken when an External Inhibit event occurs for the channel + channel.external_inhibit_action = 2 + with pytest.raises(ValueError): + channel.external_inhibit_action = 5 + + # Set the channel output mode + channel.output_mode = 3 + with pytest.raises(ValueError): + channel.output_mode = 5 + + # Set the output polarity of the channel + channel.output_polarity = "n" + with pytest.raises(ValueError): + channel.output_polarity = "x" + + # Set the channel voltage set + channel.voltage_set = 12.5 + + # Set the channel voltage bounds + channel.voltage_bounds = 10.0 + + # Clear the channel from state emergency off + channel.clear_emergency_off() + + # Set the channel current set + channel.current_set = 2.5 + + # Set the channel current bounds + channel.current_bounds = 2.0 + + # Set the channel voltage ramp speed for up and down direction + channel.set_channel_voltage_ramp_up_down_speed(250) + + # Set the channel voltage ramp up speed + channel.channel_voltage_ramp_up_speed = 200 + + # Set the channel voltage ramp down speed + channel.channel_voltage_ramp_down_speed = 150.0 + + # Switch on the high voltage with the configured ramp speed + channel.switch_on_high_voltage() + + # Switch off the high voltage with the configured ramp speed + channel.switch_off_high_voltage() + + # Shut down the channel high voltage (without ramp) + channel.shutdown_channel_high_voltage() + + # Clear the channel from state emergency off + channel.clear_channel_emergency_off() + + # Clear the Channel Event Status register + channel.clear_event_status() + + # Clears single bits or bit combinations in the Channel Event Status register + channel.clear_event_bits(3) + + # Set the Channel Event Mask register + channel.set_event_mask(7) diff --git a/tests/test_utils.py b/tests/test_utils.py index 36ef9a7..4be7ba5 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,4 +1,3 @@ -import pytest from hvps.utils import string_number_to_bit_array @@ -23,6 +22,22 @@ def test_string_number_to_bit_array(): False, False, False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, ], ), ( @@ -44,6 +59,22 @@ def test_string_number_to_bit_array(): False, False, False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, ], ), ( @@ -65,6 +96,22 @@ def test_string_number_to_bit_array(): False, False, False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, ], ), ( @@ -86,6 +133,22 @@ def test_string_number_to_bit_array(): False, False, False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, ], ), ]: