hitl_tester.modules.bms.chroma_sim

Provides controls for the Chroma 16CH Cell Simulator.

(c) 2020-2024 TurnAround Factor, Inc.

#

CUI DISTRIBUTION CONTROL

Controlled by: DLA J68 R&D SBIP

CUI Category: Small Business Research and Technology

Distribution/Dissemination Controls: PROTECTED BY SBIR DATA RIGHTS

POC: GOV SBIP Program Manager Denise Price, 571-767-0111

Distribution authorized to U.S. Government Agencies only, to protect information not owned by the

U.S. Government and protected by a contractor’s ‘limited rights’ statement, or received with the understanding that

it is not routinely transmitted outside the U.S. Government (determination made September 14, 2024). Other requests

for this document shall be referred to ACOR DLA Logistics Operations (J-68), 8725 John J. Kingman Rd., Suite 4317,

Fort Belvoir, VA 22060-6221

#

SBIR DATA RIGHTS

Contract No.:SP4701-23-C-0083

Contractor Name: TurnAround Factor, Inc.

Contractor Address: 10365 Wood Park Ct. Suite 313 / Ashland, VA 23005

Expiration of SBIR Data Rights Period: September 24, 2029

The Government's rights to use, modify, reproduce, release, perform, display, or disclose technical data or computer

software marked with this legend are restricted during the period shown as provided in paragraph (b)(4) of the Rights

in Noncommercial Technical Data and Computer Software--Small Business Innovative Research (SBIR) Program clause

contained in the above identified contract. No restrictions apply after the expiration date shown above. Any

reproduction of technical data, computer software, or portions thereof marked with this legend must also reproduce

the markings.

  1"""
  2Provides controls for the Chroma 16CH Cell Simulator.
  3
  4# (c) 2020-2024 TurnAround Factor, Inc.
  5#
  6# CUI DISTRIBUTION CONTROL
  7# Controlled by: DLA J68 R&D SBIP
  8# CUI Category: Small Business Research and Technology
  9# Distribution/Dissemination Controls: PROTECTED BY SBIR DATA RIGHTS
 10# POC: GOV SBIP Program Manager Denise Price, 571-767-0111
 11# Distribution authorized to U.S. Government Agencies only, to protect information not owned by the
 12# U.S. Government and protected by a contractor’s ‘limited rights’ statement, or received with the understanding that
 13# it is not routinely transmitted outside the U.S. Government (determination made September 14, 2024). Other requests
 14# for this document shall be referred to ACOR DLA Logistics Operations (J-68), 8725 John J. Kingman Rd., Suite 4317,
 15# Fort Belvoir, VA 22060-6221
 16#
 17# SBIR DATA RIGHTS
 18# Contract No.:SP4701-23-C-0083
 19# Contractor Name: TurnAround Factor, Inc.
 20# Contractor Address: 10365 Wood Park Ct. Suite 313 / Ashland, VA 23005
 21# Expiration of SBIR Data Rights Period: September 24, 2029
 22# The Government's rights to use, modify, reproduce, release, perform, display, or disclose technical data or computer
 23# software marked with this legend are restricted during the period shown as provided in paragraph (b)(4) of the Rights
 24# in Noncommercial Technical Data and Computer Software--Small Business Innovative Research (SBIR) Program clause
 25# contained in the above identified contract. No restrictions apply after the expiration date shown above. Any
 26# reproduction of technical data, computer software, or portions thereof marked with this legend must also reproduce
 27# the markings.
 28"""
 29
 30import time
 31from contextlib import suppress
 32from enum import IntEnum, IntFlag
 33
 34from hitl_tester.modules.bms.cell import Cell
 35from hitl_tester.modules.bms_types import UnderVoltageError, OverVoltageError, CellCompMode, SafeResource
 36from hitl_tester.modules.logger import logger
 37
 38CURRENT_LIMIT = 5.0
 39
 40
 41class CellTestingStatus(IntEnum):
 42    """Cell testing status."""
 43
 44    RUNNING = 0
 45    STOP_BY_IPC = 1
 46    STOP_BY_PROTECTION = 2
 47    STOP_BY_ERROR = 3
 48    STOP_BY_EMERGENCY_OFF = 4
 49
 50
 51class CellOutputStatus(IntEnum):
 52    """Cell output status."""
 53
 54    IDLE = 0
 55    TESTING = 1
 56    STOP = 2
 57
 58
 59class CellProtection(IntFlag):
 60    """Cell protection status."""
 61
 62    NO_ERROR = 0
 63    OCP = 2
 64    WIRELOSS = 8
 65    FAN_FAIL = 16
 66    POWER_FAIL = 32
 67    FAN_SPEED_WARNING = 256
 68    OLDP = 512
 69
 70
 71class ChromaCell(Cell):
 72    """Chroma 16CH Cell Simulator command wrapper."""
 73
 74    def __init__(self, cell_id: int, resource: SafeResource, cell_chemistry: str, cell_slot: int):
 75        self.slot = cell_slot
 76        super().__init__(cell_id, resource, cell_chemistry)
 77
 78    @property
 79    def measured_volts(self) -> float:
 80        """Measures actual cell voltage."""
 81        with self._lock:
 82            result = float(self._resource.query(f"SIM:MEAS:CELL:VOLT? 1,{self.slot},{self.slot}"))
 83            return result if result != 9.91e37 else 0.0  # Convert NaN to 0
 84
 85    @property
 86    def volts(self) -> float:
 87        """Gets the last target voltage."""
 88        with self._lock:
 89            return self._volts
 90
 91    @volts.setter
 92    def volts(self, new_voltage: float):
 93        """Sets the output voltage."""
 94        with self._lock:
 95            if not self.disengage_safety_protocols and new_voltage <= self.data.uv_protection:
 96                raise UnderVoltageError(
 97                    f"Undervoltage protection triggered at {time.strftime('%x %X')} on cell {self.id}, slot {self.slot}"
 98                    f" Voltage {new_voltage} is lower than {self.data.uv_protection}."
 99                )
100            if not self.disengage_safety_protocols and new_voltage >= self.data.ov_protection:
101                raise OverVoltageError(
102                    f"Overvoltage protection triggered at {time.strftime('%x %X')} on cell {self.id}, slot {self.slot}."
103                    f" Voltage {new_voltage} is higher than {self.data.ov_protection}."
104                )
105
106            self._resource.write(f"SIM:PROG:CELL 1,1,{self.slot},{self.slot},{new_voltage},{CURRENT_LIMIT}")
107            self._volts = new_voltage
108            self._resource.write("SIM:OUTP:IMM")
109            self.enable()  # FIXME(JA): explicitly enable cells
110
111    @property
112    def ohms(self) -> float:
113        """Measures internal resistance of the cell. Not supported."""
114        with self._lock:
115            return self._resistance
116
117    @ohms.setter
118    def ohms(self, new_ohms: float):
119        """Sets the internal resistance of the cell. Not supported."""
120        with self._lock:
121            self._resistance = new_ohms
122
123    @property
124    def amps(self) -> float:
125        """Measures cell current."""
126        with self._lock:
127            result = float(self._resource.query(f"SIM:MEAS:CELL:CURR? 1,{self.slot},{self.slot}"))
128            return result if result != 9.91e37 else 0.0  # Convert NaN to 0
129
130    @amps.setter
131    def amps(self, new_amps: float):
132        """Sets the current limit of the cell (current≥0:charge, current<0:discharge)."""
133        global CURRENT_LIMIT
134        with self._lock:
135            CURRENT_LIMIT = new_amps
136            self.volts = float(self._resource.query(f"SIM:PROG:CELL? 1,1,{self.slot},{self.slot}").split(",")[2])
137
138    @property
139    def compensation_mode(self) -> CellCompMode:
140        """Sets compensation mode"""
141        return CellCompMode.LLOCAL
142
143    @compensation_mode.setter
144    def compensation_mode(self, mode: CellCompMode):
145        """Sets compensation mode"""
146
147    def enable(self):
148        """Enables the cell."""
149        self._resource.write(f"SIM:OUTP:SPE ON,1,{self.slot},{self.slot}")
150        # with suppress(ValueError):  # Useful for debugging
151        #     protection = CellProtection(int(self._resource.query(f"SIM:MEAS:CELL:PROT? 1,{self.slot},{self.slot}")))
152        #     logger.write_debug_to_report(f"Protection is: {protection} ({protection!s})")
153        self._thread_pause_flag.set()
154
155    def disable(self):
156        """Disables the cell."""
157        self._thread_pause_flag.clear()
158        self._resource.write(f"SIM:OUTP:SPE OFF,1,{self.slot},{self.slot}")
159
160    def is_enabled(self) -> bool:
161        """Check output status."""
162        return bool(self._resource.query(f"SIM:MEAS:CELL:OPER? 1,{self.slot},{self.slot}"))
163
164    def reset(self):
165        """Resets the instrument"""
166        self._resource.write("*CLS")  # Clear the Error Queue and other status registers
167
168        logger.write_debug_to_report(f"Identification String: {self._resource.query('*IDN?')}")
169        logger.write_debug_to_report(f"Status: {self._resource.query('SYST:FRAME:STAT? 0')}")
170        logger.write_debug_to_report(f"Master Frame info: {self._resource.query('SYST:FRAME? 1')}")
171        logger.write_debug_to_report(f"Channels Status: {self._resource.query('SYST:FRAME:CHAN:STAT? 0')}")
172        logger.write_debug_to_report(f"Unit Channel: {self._resource.query('SYST:FRAME:CHAN:NUMB? 0')}")
173        logger.write_debug_to_report(f"Errors?: {self._resource.query('SYST:ERROR?')}")
174
175        self._resource.write("SYST:FRAME:PROT:CLE")  # Clears the protection status of all devices
176        self._resource.write("SIM:CONF:CLE")  # Clears UUT settings and resets them the default settings
177        self._resource.write("SIM:CONF:BMS:NUMB 1")  # 1 BMS
178        self._resource.write("SIM:CONF:SAMP:TIME 10")  # 10ms sample time
179        self._resource.write("SIM:CONF:CELL:NUMB 1,16")  # 16 cells
180        self._resource.write("SIM:CONF:CELL:PARA 1,1,8,2,4")  # current range (2 = 5A, 4 = 10A)
181
182        time.sleep(6)  # Needs delay after reset
183
184    def output_status(self) -> CellOutputStatus | None:
185        """Read cell output status."""
186        with suppress(ValueError):
187            return CellOutputStatus(int(self._resource.query(f"SIM:MEAS:CELL:OPER? 1,{self.slot},{self.slot}")))
188        return None
189
190    def testing_status(self) -> CellTestingStatus | None:
191        """Read cell testing status."""
192        with suppress(ValueError):
193            return CellTestingStatus(int(self._resource.query(f"SIM:MEAS:CELL:STAT? 1,{self.slot},{self.slot}")))
194        return None
195
196    def write(self, command: str):
197        """Issue an SCPI write command."""
198        self._resource.write(command)
199
200    def query(self, command: str) -> str:
201        """Issue an SCPI query command."""
202        return str(self._resource.query(command))
CURRENT_LIMIT = 5.0
class CellTestingStatus(enum.IntEnum):
42class CellTestingStatus(IntEnum):
43    """Cell testing status."""
44
45    RUNNING = 0
46    STOP_BY_IPC = 1
47    STOP_BY_PROTECTION = 2
48    STOP_BY_ERROR = 3
49    STOP_BY_EMERGENCY_OFF = 4

Cell testing status.

RUNNING = <CellTestingStatus.RUNNING: 0>
STOP_BY_IPC = <CellTestingStatus.STOP_BY_IPC: 1>
STOP_BY_PROTECTION = <CellTestingStatus.STOP_BY_PROTECTION: 2>
STOP_BY_ERROR = <CellTestingStatus.STOP_BY_ERROR: 3>
STOP_BY_EMERGENCY_OFF = <CellTestingStatus.STOP_BY_EMERGENCY_OFF: 4>
Inherited Members
enum.Enum
name
value
builtins.int
conjugate
bit_length
bit_count
to_bytes
from_bytes
as_integer_ratio
is_integer
real
imag
numerator
denominator
class CellOutputStatus(enum.IntEnum):
52class CellOutputStatus(IntEnum):
53    """Cell output status."""
54
55    IDLE = 0
56    TESTING = 1
57    STOP = 2

Cell output status.

IDLE = <CellOutputStatus.IDLE: 0>
TESTING = <CellOutputStatus.TESTING: 1>
STOP = <CellOutputStatus.STOP: 2>
Inherited Members
enum.Enum
name
value
builtins.int
conjugate
bit_length
bit_count
to_bytes
from_bytes
as_integer_ratio
is_integer
real
imag
numerator
denominator
class CellProtection(enum.IntFlag):
60class CellProtection(IntFlag):
61    """Cell protection status."""
62
63    NO_ERROR = 0
64    OCP = 2
65    WIRELOSS = 8
66    FAN_FAIL = 16
67    POWER_FAIL = 32
68    FAN_SPEED_WARNING = 256
69    OLDP = 512

Cell protection status.

NO_ERROR = <CellProtection.NO_ERROR: 0>
OCP = <CellProtection.OCP: 2>
WIRELOSS = <CellProtection.WIRELOSS: 8>
FAN_FAIL = <CellProtection.FAN_FAIL: 16>
POWER_FAIL = <CellProtection.POWER_FAIL: 32>
FAN_SPEED_WARNING = <CellProtection.FAN_SPEED_WARNING: 256>
OLDP = <CellProtection.OLDP: 512>
Inherited Members
builtins.int
conjugate
bit_length
bit_count
to_bytes
from_bytes
as_integer_ratio
is_integer
real
imag
numerator
denominator
enum.Enum
name
value
class ChromaCell(hitl_tester.modules.bms.cell.Cell):
 72class ChromaCell(Cell):
 73    """Chroma 16CH Cell Simulator command wrapper."""
 74
 75    def __init__(self, cell_id: int, resource: SafeResource, cell_chemistry: str, cell_slot: int):
 76        self.slot = cell_slot
 77        super().__init__(cell_id, resource, cell_chemistry)
 78
 79    @property
 80    def measured_volts(self) -> float:
 81        """Measures actual cell voltage."""
 82        with self._lock:
 83            result = float(self._resource.query(f"SIM:MEAS:CELL:VOLT? 1,{self.slot},{self.slot}"))
 84            return result if result != 9.91e37 else 0.0  # Convert NaN to 0
 85
 86    @property
 87    def volts(self) -> float:
 88        """Gets the last target voltage."""
 89        with self._lock:
 90            return self._volts
 91
 92    @volts.setter
 93    def volts(self, new_voltage: float):
 94        """Sets the output voltage."""
 95        with self._lock:
 96            if not self.disengage_safety_protocols and new_voltage <= self.data.uv_protection:
 97                raise UnderVoltageError(
 98                    f"Undervoltage protection triggered at {time.strftime('%x %X')} on cell {self.id}, slot {self.slot}"
 99                    f" Voltage {new_voltage} is lower than {self.data.uv_protection}."
100                )
101            if not self.disengage_safety_protocols and new_voltage >= self.data.ov_protection:
102                raise OverVoltageError(
103                    f"Overvoltage protection triggered at {time.strftime('%x %X')} on cell {self.id}, slot {self.slot}."
104                    f" Voltage {new_voltage} is higher than {self.data.ov_protection}."
105                )
106
107            self._resource.write(f"SIM:PROG:CELL 1,1,{self.slot},{self.slot},{new_voltage},{CURRENT_LIMIT}")
108            self._volts = new_voltage
109            self._resource.write("SIM:OUTP:IMM")
110            self.enable()  # FIXME(JA): explicitly enable cells
111
112    @property
113    def ohms(self) -> float:
114        """Measures internal resistance of the cell. Not supported."""
115        with self._lock:
116            return self._resistance
117
118    @ohms.setter
119    def ohms(self, new_ohms: float):
120        """Sets the internal resistance of the cell. Not supported."""
121        with self._lock:
122            self._resistance = new_ohms
123
124    @property
125    def amps(self) -> float:
126        """Measures cell current."""
127        with self._lock:
128            result = float(self._resource.query(f"SIM:MEAS:CELL:CURR? 1,{self.slot},{self.slot}"))
129            return result if result != 9.91e37 else 0.0  # Convert NaN to 0
130
131    @amps.setter
132    def amps(self, new_amps: float):
133        """Sets the current limit of the cell (current≥0:charge, current<0:discharge)."""
134        global CURRENT_LIMIT
135        with self._lock:
136            CURRENT_LIMIT = new_amps
137            self.volts = float(self._resource.query(f"SIM:PROG:CELL? 1,1,{self.slot},{self.slot}").split(",")[2])
138
139    @property
140    def compensation_mode(self) -> CellCompMode:
141        """Sets compensation mode"""
142        return CellCompMode.LLOCAL
143
144    @compensation_mode.setter
145    def compensation_mode(self, mode: CellCompMode):
146        """Sets compensation mode"""
147
148    def enable(self):
149        """Enables the cell."""
150        self._resource.write(f"SIM:OUTP:SPE ON,1,{self.slot},{self.slot}")
151        # with suppress(ValueError):  # Useful for debugging
152        #     protection = CellProtection(int(self._resource.query(f"SIM:MEAS:CELL:PROT? 1,{self.slot},{self.slot}")))
153        #     logger.write_debug_to_report(f"Protection is: {protection} ({protection!s})")
154        self._thread_pause_flag.set()
155
156    def disable(self):
157        """Disables the cell."""
158        self._thread_pause_flag.clear()
159        self._resource.write(f"SIM:OUTP:SPE OFF,1,{self.slot},{self.slot}")
160
161    def is_enabled(self) -> bool:
162        """Check output status."""
163        return bool(self._resource.query(f"SIM:MEAS:CELL:OPER? 1,{self.slot},{self.slot}"))
164
165    def reset(self):
166        """Resets the instrument"""
167        self._resource.write("*CLS")  # Clear the Error Queue and other status registers
168
169        logger.write_debug_to_report(f"Identification String: {self._resource.query('*IDN?')}")
170        logger.write_debug_to_report(f"Status: {self._resource.query('SYST:FRAME:STAT? 0')}")
171        logger.write_debug_to_report(f"Master Frame info: {self._resource.query('SYST:FRAME? 1')}")
172        logger.write_debug_to_report(f"Channels Status: {self._resource.query('SYST:FRAME:CHAN:STAT? 0')}")
173        logger.write_debug_to_report(f"Unit Channel: {self._resource.query('SYST:FRAME:CHAN:NUMB? 0')}")
174        logger.write_debug_to_report(f"Errors?: {self._resource.query('SYST:ERROR?')}")
175
176        self._resource.write("SYST:FRAME:PROT:CLE")  # Clears the protection status of all devices
177        self._resource.write("SIM:CONF:CLE")  # Clears UUT settings and resets them the default settings
178        self._resource.write("SIM:CONF:BMS:NUMB 1")  # 1 BMS
179        self._resource.write("SIM:CONF:SAMP:TIME 10")  # 10ms sample time
180        self._resource.write("SIM:CONF:CELL:NUMB 1,16")  # 16 cells
181        self._resource.write("SIM:CONF:CELL:PARA 1,1,8,2,4")  # current range (2 = 5A, 4 = 10A)
182
183        time.sleep(6)  # Needs delay after reset
184
185    def output_status(self) -> CellOutputStatus | None:
186        """Read cell output status."""
187        with suppress(ValueError):
188            return CellOutputStatus(int(self._resource.query(f"SIM:MEAS:CELL:OPER? 1,{self.slot},{self.slot}")))
189        return None
190
191    def testing_status(self) -> CellTestingStatus | None:
192        """Read cell testing status."""
193        with suppress(ValueError):
194            return CellTestingStatus(int(self._resource.query(f"SIM:MEAS:CELL:STAT? 1,{self.slot},{self.slot}")))
195        return None
196
197    def write(self, command: str):
198        """Issue an SCPI write command."""
199        self._resource.write(command)
200
201    def query(self, command: str) -> str:
202        """Issue an SCPI query command."""
203        return str(self._resource.query(command))

Chroma 16CH Cell Simulator command wrapper.

ChromaCell( cell_id: int, resource: hitl_tester.modules.bms_types.SafeResource, cell_chemistry: str, cell_slot: int)
75    def __init__(self, cell_id: int, resource: SafeResource, cell_chemistry: str, cell_slot: int):
76        self.slot = cell_slot
77        super().__init__(cell_id, resource, cell_chemistry)

Initialize the 66321 wrapper with a specific PyVISA resource.

slot
measured_volts: float
79    @property
80    def measured_volts(self) -> float:
81        """Measures actual cell voltage."""
82        with self._lock:
83            result = float(self._resource.query(f"SIM:MEAS:CELL:VOLT? 1,{self.slot},{self.slot}"))
84            return result if result != 9.91e37 else 0.0  # Convert NaN to 0

Measures actual cell voltage.

volts: float
86    @property
87    def volts(self) -> float:
88        """Gets the last target voltage."""
89        with self._lock:
90            return self._volts

Gets the last target voltage.

ohms: float
112    @property
113    def ohms(self) -> float:
114        """Measures internal resistance of the cell. Not supported."""
115        with self._lock:
116            return self._resistance

Measures internal resistance of the cell. Not supported.

amps: float
124    @property
125    def amps(self) -> float:
126        """Measures cell current."""
127        with self._lock:
128            result = float(self._resource.query(f"SIM:MEAS:CELL:CURR? 1,{self.slot},{self.slot}"))
129            return result if result != 9.91e37 else 0.0  # Convert NaN to 0

Measures cell current.

compensation_mode: hitl_tester.modules.bms_types.CellCompMode
139    @property
140    def compensation_mode(self) -> CellCompMode:
141        """Sets compensation mode"""
142        return CellCompMode.LLOCAL

Sets compensation mode

def enable(self):
148    def enable(self):
149        """Enables the cell."""
150        self._resource.write(f"SIM:OUTP:SPE ON,1,{self.slot},{self.slot}")
151        # with suppress(ValueError):  # Useful for debugging
152        #     protection = CellProtection(int(self._resource.query(f"SIM:MEAS:CELL:PROT? 1,{self.slot},{self.slot}")))
153        #     logger.write_debug_to_report(f"Protection is: {protection} ({protection!s})")
154        self._thread_pause_flag.set()

Enables the cell.

def disable(self):
156    def disable(self):
157        """Disables the cell."""
158        self._thread_pause_flag.clear()
159        self._resource.write(f"SIM:OUTP:SPE OFF,1,{self.slot},{self.slot}")

Disables the cell.

def is_enabled(self) -> bool:
161    def is_enabled(self) -> bool:
162        """Check output status."""
163        return bool(self._resource.query(f"SIM:MEAS:CELL:OPER? 1,{self.slot},{self.slot}"))

Check output status.

def reset(self):
165    def reset(self):
166        """Resets the instrument"""
167        self._resource.write("*CLS")  # Clear the Error Queue and other status registers
168
169        logger.write_debug_to_report(f"Identification String: {self._resource.query('*IDN?')}")
170        logger.write_debug_to_report(f"Status: {self._resource.query('SYST:FRAME:STAT? 0')}")
171        logger.write_debug_to_report(f"Master Frame info: {self._resource.query('SYST:FRAME? 1')}")
172        logger.write_debug_to_report(f"Channels Status: {self._resource.query('SYST:FRAME:CHAN:STAT? 0')}")
173        logger.write_debug_to_report(f"Unit Channel: {self._resource.query('SYST:FRAME:CHAN:NUMB? 0')}")
174        logger.write_debug_to_report(f"Errors?: {self._resource.query('SYST:ERROR?')}")
175
176        self._resource.write("SYST:FRAME:PROT:CLE")  # Clears the protection status of all devices
177        self._resource.write("SIM:CONF:CLE")  # Clears UUT settings and resets them the default settings
178        self._resource.write("SIM:CONF:BMS:NUMB 1")  # 1 BMS
179        self._resource.write("SIM:CONF:SAMP:TIME 10")  # 10ms sample time
180        self._resource.write("SIM:CONF:CELL:NUMB 1,16")  # 16 cells
181        self._resource.write("SIM:CONF:CELL:PARA 1,1,8,2,4")  # current range (2 = 5A, 4 = 10A)
182
183        time.sleep(6)  # Needs delay after reset

Resets the instrument

def output_status(self) -> CellOutputStatus | None:
185    def output_status(self) -> CellOutputStatus | None:
186        """Read cell output status."""
187        with suppress(ValueError):
188            return CellOutputStatus(int(self._resource.query(f"SIM:MEAS:CELL:OPER? 1,{self.slot},{self.slot}")))
189        return None

Read cell output status.

def testing_status(self) -> CellTestingStatus | None:
191    def testing_status(self) -> CellTestingStatus | None:
192        """Read cell testing status."""
193        with suppress(ValueError):
194            return CellTestingStatus(int(self._resource.query(f"SIM:MEAS:CELL:STAT? 1,{self.slot},{self.slot}")))
195        return None

Read cell testing status.

def write(self, command: str):
197    def write(self, command: str):
198        """Issue an SCPI write command."""
199        self._resource.write(command)

Issue an SCPI write command.

def query(self, command: str) -> str:
201    def query(self, command: str) -> str:
202        """Issue an SCPI query command."""
203        return str(self._resource.query(command))

Issue an SCPI query command.