hitl_tester.modules.bms.korad

Provides controls for the Rigol DM3068 DMM.

(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 DM3068 DMM.
  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
 33from enum import IntFlag
 34
 35from hitl_tester.modules.bms_types import SafeResource
 36from hitl_tester.modules.logger import logger
 37
 38
 39class Status(IntFlag):
 40    """Reset flags from the CSR."""
 41
 42    # 0x80 = N/A or OVP
 43    OUTPUT_ON = 0x40
 44    OVERVOLTAGE_OVERCURRENT_PROTECTION = 0x20  # or OCP or Lock 0=Lock, 1=Unlock
 45    # 0x10 = Beep 0=Off, 1=On
 46    # 0x08+0x04 = Tracking 00=Independent, 01=Tracking series,11=Tracking parallel
 47    # 0x02 = CH2 0=CC mode, 1=CV mode
 48    CONSTANT_VOLTAGE = 0x01  # Constant current if off
 49
 50    def __str__(self) -> str:
 51        flags = [str(flag.name).title() for flag in Status if self.value & flag]
 52        if not self.value & self.OUTPUT_ON:
 53            flags.append("Output Off")
 54        if not self.value & self.CONSTANT_VOLTAGE:
 55            flags.append("Constant Current")
 56        return " | ".join(flags)
 57
 58
 59class Korad:
 60    """Korad KA6003P power supply command wrapper."""
 61
 62    VOLTAGE_MAX = 60
 63    VOLTAGE_MIN = 1.2
 64    CURRENT_MAX = 3
 65
 66    def __init__(self, korad_id: int, resource: SafeResource):
 67        """
 68        Initialize the KA6003P wrapper with a specific PyVISA resource.
 69        This class does NOT open the resource, you have to open it for yourself!
 70        """
 71        self.id: int = korad_id
 72        self.resource = resource
 73        self._overvoltage_protection = False
 74        self._overcurrent_protection = False
 75        self._beep = False
 76        self.disable()
 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    @property
 84    def status(self) -> Status:
 85        """Get the power supply status."""
 86        self.resource.write("STATUS?")
 87        response = self.resource.read_raw()[1:]
 88        return Status(int.from_bytes(response, byteorder="big") if len(response) == 1 else 0)
 89
 90    def set_profile(self, volts: float, amps: float):
 91        """Sets charging profile"""
 92        self.volts = volts
 93        self.amps = amps
 94
 95    @property
 96    def overcurrent_protection(self) -> float:
 97        """Get the last set overcurrent protection state."""
 98        return self._overcurrent_protection
 99
100    @overcurrent_protection.setter
101    def overcurrent_protection(self, enabled: bool):
102        """Enable or disable overcurrent protection."""
103        self._overcurrent_protection = enabled
104        self.resource.write(f"OCP{int(enabled)}")
105
106    @property
107    def overvoltage_protection(self) -> float:
108        """Get the last set overvoltage protection state."""
109        return self._overvoltage_protection
110
111    @overvoltage_protection.setter
112    def overvoltage_protection(self, enabled: bool):
113        """Enable or disable overvoltage protection."""
114        self._overvoltage_protection = enabled
115        self.resource.write(f"OVP{int(enabled)}")
116
117    @property
118    def measured_amps(self) -> float:
119        """Measures current."""
120        return float(self.resource.query("IOUT1?"))
121
122    @property
123    def amps(self) -> float:
124        """Get the target current."""
125        return float(self.resource.query("ISET1?"))
126
127    @amps.setter
128    def amps(self, new_amps: float):
129        """Set the target current."""
130        if new_amps > Korad.CURRENT_MAX:
131            raise RuntimeError(f"Current of {new_amps}A exceeds maximum of {Korad.CURRENT_MAX}A.")
132        self.resource.write(f"ISET1:{new_amps}")
133
134    @property
135    def measured_volts(self) -> float:
136        """Measures voltage."""
137        return float(self.resource.query("VOUT1?"))
138
139    @property
140    def volts(self) -> float:
141        """Get the target voltage."""
142        return float(self.resource.query("VSET1?"))
143
144    @volts.setter
145    def volts(self, new_volts: float):
146        """Set the target voltage"""
147        if not Korad.VOLTAGE_MIN <= new_volts <= Korad.VOLTAGE_MAX:
148            raise RuntimeError(f"{new_volts}V is out of range {Korad.VOLTAGE_MIN}V to {Korad.VOLTAGE_MAX}V.")
149        self.resource.write(f"VSET1:{new_volts}")
150
151    @property
152    def beep(self) -> bool:
153        """Get the beep setting."""
154        return self._beep
155
156    @beep.setter
157    def beep(self, enable_beep: bool):
158        """Turns on or off the beep."""
159        self.resource.write(f"BEEP{int(enable_beep)}")
160
161    def recall(self, memory_number: int):
162        """Recalls a panel setting from memory 1 to 5."""
163        self.resource.write(f"RCL{memory_number}")
164
165    def store(self, memory_number: int):
166        """Stores a panel setting to memory 1 to 5."""
167        self.resource.write(f"SAV{memory_number}")
168
169    def enable(self):
170        """Enable the output."""
171        logger.write_info_to_report("Enabling Korad charger")
172        self.resource.write("OUT1")
173
174    def disable(self):
175        """Disable the output."""
176        logger.write_info_to_report("Disabling Korad charger")
177        self.resource.write("OUT0")
class Status(enum.IntFlag):
40class Status(IntFlag):
41    """Reset flags from the CSR."""
42
43    # 0x80 = N/A or OVP
44    OUTPUT_ON = 0x40
45    OVERVOLTAGE_OVERCURRENT_PROTECTION = 0x20  # or OCP or Lock 0=Lock, 1=Unlock
46    # 0x10 = Beep 0=Off, 1=On
47    # 0x08+0x04 = Tracking 00=Independent, 01=Tracking series,11=Tracking parallel
48    # 0x02 = CH2 0=CC mode, 1=CV mode
49    CONSTANT_VOLTAGE = 0x01  # Constant current if off
50
51    def __str__(self) -> str:
52        flags = [str(flag.name).title() for flag in Status if self.value & flag]
53        if not self.value & self.OUTPUT_ON:
54            flags.append("Output Off")
55        if not self.value & self.CONSTANT_VOLTAGE:
56            flags.append("Constant Current")
57        return " | ".join(flags)

Reset flags from the CSR.

OUTPUT_ON = <Status.OUTPUT_ON: 64>
OVERVOLTAGE_OVERCURRENT_PROTECTION = <Status.OVERVOLTAGE_OVERCURRENT_PROTECTION: 32>
CONSTANT_VOLTAGE = <Status.CONSTANT_VOLTAGE: 1>
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 Korad:
 60class Korad:
 61    """Korad KA6003P power supply command wrapper."""
 62
 63    VOLTAGE_MAX = 60
 64    VOLTAGE_MIN = 1.2
 65    CURRENT_MAX = 3
 66
 67    def __init__(self, korad_id: int, resource: SafeResource):
 68        """
 69        Initialize the KA6003P wrapper with a specific PyVISA resource.
 70        This class does NOT open the resource, you have to open it for yourself!
 71        """
 72        self.id: int = korad_id
 73        self.resource = resource
 74        self._overvoltage_protection = False
 75        self._overcurrent_protection = False
 76        self._beep = False
 77        self.disable()
 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    @property
 85    def status(self) -> Status:
 86        """Get the power supply status."""
 87        self.resource.write("STATUS?")
 88        response = self.resource.read_raw()[1:]
 89        return Status(int.from_bytes(response, byteorder="big") if len(response) == 1 else 0)
 90
 91    def set_profile(self, volts: float, amps: float):
 92        """Sets charging profile"""
 93        self.volts = volts
 94        self.amps = amps
 95
 96    @property
 97    def overcurrent_protection(self) -> float:
 98        """Get the last set overcurrent protection state."""
 99        return self._overcurrent_protection
100
101    @overcurrent_protection.setter
102    def overcurrent_protection(self, enabled: bool):
103        """Enable or disable overcurrent protection."""
104        self._overcurrent_protection = enabled
105        self.resource.write(f"OCP{int(enabled)}")
106
107    @property
108    def overvoltage_protection(self) -> float:
109        """Get the last set overvoltage protection state."""
110        return self._overvoltage_protection
111
112    @overvoltage_protection.setter
113    def overvoltage_protection(self, enabled: bool):
114        """Enable or disable overvoltage protection."""
115        self._overvoltage_protection = enabled
116        self.resource.write(f"OVP{int(enabled)}")
117
118    @property
119    def measured_amps(self) -> float:
120        """Measures current."""
121        return float(self.resource.query("IOUT1?"))
122
123    @property
124    def amps(self) -> float:
125        """Get the target current."""
126        return float(self.resource.query("ISET1?"))
127
128    @amps.setter
129    def amps(self, new_amps: float):
130        """Set the target current."""
131        if new_amps > Korad.CURRENT_MAX:
132            raise RuntimeError(f"Current of {new_amps}A exceeds maximum of {Korad.CURRENT_MAX}A.")
133        self.resource.write(f"ISET1:{new_amps}")
134
135    @property
136    def measured_volts(self) -> float:
137        """Measures voltage."""
138        return float(self.resource.query("VOUT1?"))
139
140    @property
141    def volts(self) -> float:
142        """Get the target voltage."""
143        return float(self.resource.query("VSET1?"))
144
145    @volts.setter
146    def volts(self, new_volts: float):
147        """Set the target voltage"""
148        if not Korad.VOLTAGE_MIN <= new_volts <= Korad.VOLTAGE_MAX:
149            raise RuntimeError(f"{new_volts}V is out of range {Korad.VOLTAGE_MIN}V to {Korad.VOLTAGE_MAX}V.")
150        self.resource.write(f"VSET1:{new_volts}")
151
152    @property
153    def beep(self) -> bool:
154        """Get the beep setting."""
155        return self._beep
156
157    @beep.setter
158    def beep(self, enable_beep: bool):
159        """Turns on or off the beep."""
160        self.resource.write(f"BEEP{int(enable_beep)}")
161
162    def recall(self, memory_number: int):
163        """Recalls a panel setting from memory 1 to 5."""
164        self.resource.write(f"RCL{memory_number}")
165
166    def store(self, memory_number: int):
167        """Stores a panel setting to memory 1 to 5."""
168        self.resource.write(f"SAV{memory_number}")
169
170    def enable(self):
171        """Enable the output."""
172        logger.write_info_to_report("Enabling Korad charger")
173        self.resource.write("OUT1")
174
175    def disable(self):
176        """Disable the output."""
177        logger.write_info_to_report("Disabling Korad charger")
178        self.resource.write("OUT0")

Korad KA6003P power supply command wrapper.

Korad(korad_id: int, resource: hitl_tester.modules.bms_types.SafeResource)
67    def __init__(self, korad_id: int, resource: SafeResource):
68        """
69        Initialize the KA6003P wrapper with a specific PyVISA resource.
70        This class does NOT open the resource, you have to open it for yourself!
71        """
72        self.id: int = korad_id
73        self.resource = resource
74        self._overvoltage_protection = False
75        self._overcurrent_protection = False
76        self._beep = False
77        self.disable()
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 KA6003P wrapper with a specific PyVISA resource. This class does NOT open the resource, you have to open it for yourself!

VOLTAGE_MAX = 60
VOLTAGE_MIN = 1.2
CURRENT_MAX = 3
id: int
resource
status: Status
84    @property
85    def status(self) -> Status:
86        """Get the power supply status."""
87        self.resource.write("STATUS?")
88        response = self.resource.read_raw()[1:]
89        return Status(int.from_bytes(response, byteorder="big") if len(response) == 1 else 0)

Get the power supply status.

def set_profile(self, volts: float, amps: float):
91    def set_profile(self, volts: float, amps: float):
92        """Sets charging profile"""
93        self.volts = volts
94        self.amps = amps

Sets charging profile

overcurrent_protection: float
96    @property
97    def overcurrent_protection(self) -> float:
98        """Get the last set overcurrent protection state."""
99        return self._overcurrent_protection

Get the last set overcurrent protection state.

overvoltage_protection: float
107    @property
108    def overvoltage_protection(self) -> float:
109        """Get the last set overvoltage protection state."""
110        return self._overvoltage_protection

Get the last set overvoltage protection state.

measured_amps: float
118    @property
119    def measured_amps(self) -> float:
120        """Measures current."""
121        return float(self.resource.query("IOUT1?"))

Measures current.

amps: float
123    @property
124    def amps(self) -> float:
125        """Get the target current."""
126        return float(self.resource.query("ISET1?"))

Get the target current.

measured_volts: float
135    @property
136    def measured_volts(self) -> float:
137        """Measures voltage."""
138        return float(self.resource.query("VOUT1?"))

Measures voltage.

volts: float
140    @property
141    def volts(self) -> float:
142        """Get the target voltage."""
143        return float(self.resource.query("VSET1?"))

Get the target voltage.

beep: bool
152    @property
153    def beep(self) -> bool:
154        """Get the beep setting."""
155        return self._beep

Get the beep setting.

def recall(self, memory_number: int):
162    def recall(self, memory_number: int):
163        """Recalls a panel setting from memory 1 to 5."""
164        self.resource.write(f"RCL{memory_number}")

Recalls a panel setting from memory 1 to 5.

def store(self, memory_number: int):
166    def store(self, memory_number: int):
167        """Stores a panel setting to memory 1 to 5."""
168        self.resource.write(f"SAV{memory_number}")

Stores a panel setting to memory 1 to 5.

def enable(self):
170    def enable(self):
171        """Enable the output."""
172        logger.write_info_to_report("Enabling Korad charger")
173        self.resource.write("OUT1")

Enable the output.

def disable(self):
175    def disable(self):
176        """Disable the output."""
177        logger.write_info_to_report("Disabling Korad charger")
178        self.resource.write("OUT0")

Disable the output.