hitl_tester.modules.cyber_6t.charger
Provides controls for the Rigol DL711 Power Supply.
(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 Rigol DL711 Power Supply. 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 30from __future__ import annotations 31 32import atexit 33import time 34import traceback 35from time import sleep 36from typing import cast 37 38import pyvisa as visa 39from pyvisa.resources import MessageBasedResource 40 41from hitl_tester.modules.logger import logger 42 43 44class Charger: 45 """Keysight Power Supply command wrapper.""" 46 47 ADDRESS = "Keysight Technologies,E36731A" 48 49 def __init__(self) -> None: 50 """ 51 Initialize the Keysight wrapper from a PyVISA resource. 52 """ 53 54 # Find charger 55 resource_manager = visa.ResourceManager() 56 resources = resource_manager.list_resources() 57 if not resources: 58 raise RuntimeError("No PyVISA resources found") 59 self.resource: MessageBasedResource | None = None 60 for resource_address in resources: 61 if resource_address.startswith("USB"): 62 try: 63 resource = cast(MessageBasedResource, resource_manager.open_resource(resource_address)) 64 if self.ADDRESS in resource.query("*IDN?"): 65 self.resource = resource 66 break 67 except Exception as e: # pylint: disable=broad-exception-caught 68 logger.write_debug_to_report(traceback.format_exception(e)) 69 logger.write_debug_to_report(f"Could not open: {resource_address}") 70 if not self.resource: 71 raise RuntimeError("Could not find power supply") 72 73 # Set default values 74 self.target_amps: float = 0 75 self.target_volts: float = 0 76 self.reset() 77 78 @atexit.register 79 def __atexit__(): 80 """Configure a safe shut down for when the class instance is destroyed.""" 81 self.disable() 82 83 def __call__(self, amps: float, volts: float = 30): 84 self.set_profile(volts, amps) 85 return self 86 87 def __enter__(self): 88 self.enable() 89 return self 90 91 def __exit__(self, exc_type, exc_value, exc_traceback): 92 self.disable() 93 94 def write(self, command: str) -> None: 95 """Write SCPI command if possible.""" 96 try: 97 if self.resource: 98 self.resource.write(command) 99 except Exception as e: # pylint: disable=broad-exception-caught 100 raise RuntimeError(traceback.format_exception(e)) from e 101 102 def set_profile(self, volts: float, amps: float): 103 """Sets (dis)charging profile""" 104 # Limit 30.9V, 4A 105 if volts >= 30.9: 106 logger.write_warning_to_html_report("Voltage too high, lowering.") 107 volts = 30.8 108 if not -4 <= amps <= 4: 109 raise RuntimeError("Current not in range +/- 4A.") 110 self.target_volts = volts 111 self.target_amps = amps 112 if amps < 0: # Load mode 113 self.write("EMUL LOAD") 114 time.sleep(10) 115 self.amps = amps 116 else: 117 self.write("EMUL PSUP") 118 time.sleep(10) 119 self.write(f"APPL CH1, {volts}, {amps}") 120 sleep(10) # The charger needs time to process the previous commands 121 122 def _amps_limit(self, new_amps_limit: float): 123 """Sets output current limit""" 124 self.write(f"CURR:LIM {new_amps_limit} (@1)") 125 126 def _volts_limit(self, new_volts_limit: float): 127 """Sets output voltage limit""" 128 self.write(f"VOLT:PROT {new_volts_limit}, (@1)") 129 130 amps_limit = property(None, _amps_limit) # make this function accessible as a write-only attribute 131 volts_limit = property(None, _volts_limit) # make this function accessible as a write-only attribute 132 133 @property 134 def amps(self) -> float: 135 """Reads the current being sourced""" 136 assert self.resource, "Resource does not exist." 137 return float(self.resource.query("MEAS:CURR? (@1)")) 138 139 @amps.setter 140 def amps(self, new_amps: float): 141 """Sets the power supply output current""" 142 self.target_amps = new_amps 143 self.write(f"CURR {new_amps}, (@1)") 144 145 @property 146 def volts(self) -> float: 147 """Reads the volts being sourced""" 148 assert self.resource, "Resource does not exist." 149 return float(self.resource.query("MEAS:VOLT? (@1)")) 150 151 @volts.setter 152 def volts(self, new_volts: float): 153 """Sets the power supply output voltage""" 154 self.target_volts = new_volts 155 self.write(f"VOLT {new_volts}, (@1)") 156 157 def enable(self): 158 """Enables power supply output""" 159 if self.target_amps > 0: 160 logger.write_info_to_report("Enabling charger") 161 self.write("OUTP ON, (@1)") 162 else: 163 logger.write_info_to_report("Enabling load") 164 self.write("INP ON, (@1)") 165 sleep(1) # This instrument needs time to initialize 166 167 def disable(self): 168 """Disables power supply output""" 169 if self.target_amps > 0: 170 logger.write_info_to_report("Disabling charger") 171 self.write("OUTP OFF, (@1)") 172 else: 173 logger.write_info_to_report("Disabling load") 174 self.write("INP OFF, (@1)") 175 sleep(1) # The charger needs time to process the previous commands 176 177 def reset(self): 178 """Resets the instrument""" 179 self.write("*RST") 180 sleep(3) 181 self.write("*CLS") 182 sleep(3) 183 self.write("SYST:RWL") 184 sleep(3)
class
Charger:
45class Charger: 46 """Keysight Power Supply command wrapper.""" 47 48 ADDRESS = "Keysight Technologies,E36731A" 49 50 def __init__(self) -> None: 51 """ 52 Initialize the Keysight wrapper from a PyVISA resource. 53 """ 54 55 # Find charger 56 resource_manager = visa.ResourceManager() 57 resources = resource_manager.list_resources() 58 if not resources: 59 raise RuntimeError("No PyVISA resources found") 60 self.resource: MessageBasedResource | None = None 61 for resource_address in resources: 62 if resource_address.startswith("USB"): 63 try: 64 resource = cast(MessageBasedResource, resource_manager.open_resource(resource_address)) 65 if self.ADDRESS in resource.query("*IDN?"): 66 self.resource = resource 67 break 68 except Exception as e: # pylint: disable=broad-exception-caught 69 logger.write_debug_to_report(traceback.format_exception(e)) 70 logger.write_debug_to_report(f"Could not open: {resource_address}") 71 if not self.resource: 72 raise RuntimeError("Could not find power supply") 73 74 # Set default values 75 self.target_amps: float = 0 76 self.target_volts: float = 0 77 self.reset() 78 79 @atexit.register 80 def __atexit__(): 81 """Configure a safe shut down for when the class instance is destroyed.""" 82 self.disable() 83 84 def __call__(self, amps: float, volts: float = 30): 85 self.set_profile(volts, amps) 86 return self 87 88 def __enter__(self): 89 self.enable() 90 return self 91 92 def __exit__(self, exc_type, exc_value, exc_traceback): 93 self.disable() 94 95 def write(self, command: str) -> None: 96 """Write SCPI command if possible.""" 97 try: 98 if self.resource: 99 self.resource.write(command) 100 except Exception as e: # pylint: disable=broad-exception-caught 101 raise RuntimeError(traceback.format_exception(e)) from e 102 103 def set_profile(self, volts: float, amps: float): 104 """Sets (dis)charging profile""" 105 # Limit 30.9V, 4A 106 if volts >= 30.9: 107 logger.write_warning_to_html_report("Voltage too high, lowering.") 108 volts = 30.8 109 if not -4 <= amps <= 4: 110 raise RuntimeError("Current not in range +/- 4A.") 111 self.target_volts = volts 112 self.target_amps = amps 113 if amps < 0: # Load mode 114 self.write("EMUL LOAD") 115 time.sleep(10) 116 self.amps = amps 117 else: 118 self.write("EMUL PSUP") 119 time.sleep(10) 120 self.write(f"APPL CH1, {volts}, {amps}") 121 sleep(10) # The charger needs time to process the previous commands 122 123 def _amps_limit(self, new_amps_limit: float): 124 """Sets output current limit""" 125 self.write(f"CURR:LIM {new_amps_limit} (@1)") 126 127 def _volts_limit(self, new_volts_limit: float): 128 """Sets output voltage limit""" 129 self.write(f"VOLT:PROT {new_volts_limit}, (@1)") 130 131 amps_limit = property(None, _amps_limit) # make this function accessible as a write-only attribute 132 volts_limit = property(None, _volts_limit) # make this function accessible as a write-only attribute 133 134 @property 135 def amps(self) -> float: 136 """Reads the current being sourced""" 137 assert self.resource, "Resource does not exist." 138 return float(self.resource.query("MEAS:CURR? (@1)")) 139 140 @amps.setter 141 def amps(self, new_amps: float): 142 """Sets the power supply output current""" 143 self.target_amps = new_amps 144 self.write(f"CURR {new_amps}, (@1)") 145 146 @property 147 def volts(self) -> float: 148 """Reads the volts being sourced""" 149 assert self.resource, "Resource does not exist." 150 return float(self.resource.query("MEAS:VOLT? (@1)")) 151 152 @volts.setter 153 def volts(self, new_volts: float): 154 """Sets the power supply output voltage""" 155 self.target_volts = new_volts 156 self.write(f"VOLT {new_volts}, (@1)") 157 158 def enable(self): 159 """Enables power supply output""" 160 if self.target_amps > 0: 161 logger.write_info_to_report("Enabling charger") 162 self.write("OUTP ON, (@1)") 163 else: 164 logger.write_info_to_report("Enabling load") 165 self.write("INP ON, (@1)") 166 sleep(1) # This instrument needs time to initialize 167 168 def disable(self): 169 """Disables power supply output""" 170 if self.target_amps > 0: 171 logger.write_info_to_report("Disabling charger") 172 self.write("OUTP OFF, (@1)") 173 else: 174 logger.write_info_to_report("Disabling load") 175 self.write("INP OFF, (@1)") 176 sleep(1) # The charger needs time to process the previous commands 177 178 def reset(self): 179 """Resets the instrument""" 180 self.write("*RST") 181 sleep(3) 182 self.write("*CLS") 183 sleep(3) 184 self.write("SYST:RWL") 185 sleep(3)
Keysight Power Supply command wrapper.
Charger()
50 def __init__(self) -> None: 51 """ 52 Initialize the Keysight wrapper from a PyVISA resource. 53 """ 54 55 # Find charger 56 resource_manager = visa.ResourceManager() 57 resources = resource_manager.list_resources() 58 if not resources: 59 raise RuntimeError("No PyVISA resources found") 60 self.resource: MessageBasedResource | None = None 61 for resource_address in resources: 62 if resource_address.startswith("USB"): 63 try: 64 resource = cast(MessageBasedResource, resource_manager.open_resource(resource_address)) 65 if self.ADDRESS in resource.query("*IDN?"): 66 self.resource = resource 67 break 68 except Exception as e: # pylint: disable=broad-exception-caught 69 logger.write_debug_to_report(traceback.format_exception(e)) 70 logger.write_debug_to_report(f"Could not open: {resource_address}") 71 if not self.resource: 72 raise RuntimeError("Could not find power supply") 73 74 # Set default values 75 self.target_amps: float = 0 76 self.target_volts: float = 0 77 self.reset() 78 79 @atexit.register 80 def __atexit__(): 81 """Configure a safe shut down for when the class instance is destroyed.""" 82 self.disable()
Initialize the Keysight wrapper from a PyVISA resource.
def
write(self, command: str) -> None:
95 def write(self, command: str) -> None: 96 """Write SCPI command if possible.""" 97 try: 98 if self.resource: 99 self.resource.write(command) 100 except Exception as e: # pylint: disable=broad-exception-caught 101 raise RuntimeError(traceback.format_exception(e)) from e
Write SCPI command if possible.
def
set_profile(self, volts: float, amps: float):
103 def set_profile(self, volts: float, amps: float): 104 """Sets (dis)charging profile""" 105 # Limit 30.9V, 4A 106 if volts >= 30.9: 107 logger.write_warning_to_html_report("Voltage too high, lowering.") 108 volts = 30.8 109 if not -4 <= amps <= 4: 110 raise RuntimeError("Current not in range +/- 4A.") 111 self.target_volts = volts 112 self.target_amps = amps 113 if amps < 0: # Load mode 114 self.write("EMUL LOAD") 115 time.sleep(10) 116 self.amps = amps 117 else: 118 self.write("EMUL PSUP") 119 time.sleep(10) 120 self.write(f"APPL CH1, {volts}, {amps}") 121 sleep(10) # The charger needs time to process the previous commands
Sets (dis)charging profile
amps: float
134 @property 135 def amps(self) -> float: 136 """Reads the current being sourced""" 137 assert self.resource, "Resource does not exist." 138 return float(self.resource.query("MEAS:CURR? (@1)"))
Reads the current being sourced
volts: float
146 @property 147 def volts(self) -> float: 148 """Reads the volts being sourced""" 149 assert self.resource, "Resource does not exist." 150 return float(self.resource.query("MEAS:VOLT? (@1)"))
Reads the volts being sourced
def
enable(self):
158 def enable(self): 159 """Enables power supply output""" 160 if self.target_amps > 0: 161 logger.write_info_to_report("Enabling charger") 162 self.write("OUTP ON, (@1)") 163 else: 164 logger.write_info_to_report("Enabling load") 165 self.write("INP ON, (@1)") 166 sleep(1) # This instrument needs time to initialize
Enables power supply output
def
disable(self):
168 def disable(self): 169 """Disables power supply output""" 170 if self.target_amps > 0: 171 logger.write_info_to_report("Disabling charger") 172 self.write("OUTP OFF, (@1)") 173 else: 174 logger.write_info_to_report("Disabling load") 175 self.write("INP OFF, (@1)") 176 sleep(1) # The charger needs time to process the previous commands
Disables power supply output