feat: add IT6000C eload driver (PyVISA) with manual control CLI
This commit is contained in:
@@ -0,0 +1,97 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
import pyvisa
|
||||||
|
|
||||||
|
|
||||||
|
class EloadError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EloadConnectionError(EloadError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EloadCommunicationError(EloadError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Eload:
|
||||||
|
"""ITECH IT6000C bi-directional supply, used here as an electronic load (CV mode with +/- current limit)."""
|
||||||
|
|
||||||
|
def __init__(self, resource_name, baud_rate=9600, timeout_ms=3000, retries=3, retry_delay_s=0.2):
|
||||||
|
self._retries = retries
|
||||||
|
self._retry_delay_s = retry_delay_s
|
||||||
|
try:
|
||||||
|
self._rm = pyvisa.ResourceManager()
|
||||||
|
self._inst = self._rm.open_resource(resource_name)
|
||||||
|
except pyvisa.VisaIOError as exc:
|
||||||
|
raise EloadConnectionError(f"failed to open {resource_name}") from exc
|
||||||
|
|
||||||
|
self._inst.baud_rate = baud_rate
|
||||||
|
self._inst.write_termination = "\n"
|
||||||
|
self._inst.read_termination = "\n"
|
||||||
|
self._inst.timeout = timeout_ms
|
||||||
|
|
||||||
|
def _write(self, cmd):
|
||||||
|
for attempt in range(self._retries):
|
||||||
|
try:
|
||||||
|
self._inst.write(cmd)
|
||||||
|
return
|
||||||
|
except pyvisa.VisaIOError as exc:
|
||||||
|
if attempt == self._retries - 1:
|
||||||
|
raise EloadCommunicationError(f"write failed: {cmd!r}") from exc
|
||||||
|
time.sleep(self._retry_delay_s)
|
||||||
|
|
||||||
|
def _query(self, cmd):
|
||||||
|
for attempt in range(self._retries):
|
||||||
|
try:
|
||||||
|
return self._inst.query(cmd).strip()
|
||||||
|
except pyvisa.VisaIOError as exc:
|
||||||
|
if attempt == self._retries - 1:
|
||||||
|
raise EloadCommunicationError(f"query failed: {cmd!r}") from exc
|
||||||
|
time.sleep(self._retry_delay_s)
|
||||||
|
|
||||||
|
def set_remote(self):
|
||||||
|
self._write("SYST:REM")
|
||||||
|
|
||||||
|
def set_local(self):
|
||||||
|
self._write("SYST:LOC")
|
||||||
|
|
||||||
|
def set_mode_cv(self):
|
||||||
|
self._write("SOUR:FUNC VOLT")
|
||||||
|
|
||||||
|
def set_mode_cc(self):
|
||||||
|
self._write("SOUR:FUNC CURR")
|
||||||
|
|
||||||
|
def set_voltage(self, volts):
|
||||||
|
self._write(f"VOLT {volts}")
|
||||||
|
|
||||||
|
def set_current(self, amps):
|
||||||
|
self._write(f"CURR {amps}")
|
||||||
|
|
||||||
|
def set_current_limit_positive(self, amps):
|
||||||
|
self._write(f"CURR:LIM:POS {amps}")
|
||||||
|
|
||||||
|
def set_current_limit_negative(self, amps):
|
||||||
|
self._write(f"CURR:LIM:NEG {amps}")
|
||||||
|
|
||||||
|
def output_on(self):
|
||||||
|
self._write("OUTP ON")
|
||||||
|
|
||||||
|
def output_off(self):
|
||||||
|
self._write("OUTP OFF")
|
||||||
|
|
||||||
|
def is_output_on(self):
|
||||||
|
return self._query("OUTP?") == "1"
|
||||||
|
|
||||||
|
def measure_voltage(self):
|
||||||
|
return float(self._query("MEAS:VOLT?"))
|
||||||
|
|
||||||
|
def measure_current(self):
|
||||||
|
return float(self._query("MEAS:CURR?"))
|
||||||
|
|
||||||
|
def measure_power(self):
|
||||||
|
return float(self._query("MEAS:POW?"))
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
self._inst.close()
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||||
|
|
||||||
|
import msvcrt
|
||||||
|
|
||||||
|
from drivers.eload import Eload, EloadError
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
com_input = input("Enter COM port number (e.g., 5 for COM5): ").strip()
|
||||||
|
try:
|
||||||
|
com_num = int(com_input)
|
||||||
|
except ValueError:
|
||||||
|
print("Invalid input. Please enter a numeric COM port number.")
|
||||||
|
return
|
||||||
|
|
||||||
|
resource_name = f"ASRL{com_num}::INSTR"
|
||||||
|
try:
|
||||||
|
eload = Eload(resource_name)
|
||||||
|
except EloadError as exc:
|
||||||
|
print(f"Failed to open port {resource_name}: {exc}")
|
||||||
|
return
|
||||||
|
|
||||||
|
eload.set_remote()
|
||||||
|
quick_voltage_mode = False
|
||||||
|
|
||||||
|
while True:
|
||||||
|
cmd = input("\nCommand [on/off/set | manual/remote | status/monitor | setopt | exit]: ").strip().lower()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if cmd == "set":
|
||||||
|
eload.set_mode_cv()
|
||||||
|
v = float(input("Enter voltage setpoint (V): ").strip())
|
||||||
|
eload.set_voltage(v)
|
||||||
|
print(f"Set voltage to {v} V.")
|
||||||
|
|
||||||
|
if not quick_voltage_mode:
|
||||||
|
i_pos = float(input("Enter positive current limit I+ (A): ").strip())
|
||||||
|
i_neg = float(input("Enter negative current limit I- (A): ").strip())
|
||||||
|
eload.set_current_limit_positive(i_pos)
|
||||||
|
eload.set_current_limit_negative(i_neg)
|
||||||
|
print(f"Set I+ limit = {i_pos} A, I- limit = {i_neg} A.")
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
print(f"Measured voltage after set: {eload.measure_voltage()} V")
|
||||||
|
print(f"Measured current after set: {eload.measure_current()} A")
|
||||||
|
|
||||||
|
elif cmd == "setopt":
|
||||||
|
quick_voltage_mode = not quick_voltage_mode
|
||||||
|
mode_desc = "Voltage-only SET enabled" if quick_voltage_mode else "Full voltage+current SET enabled"
|
||||||
|
print(f"Set behavior changed: {mode_desc}.")
|
||||||
|
|
||||||
|
elif cmd == "on":
|
||||||
|
eload.output_on()
|
||||||
|
time.sleep(1)
|
||||||
|
if eload.is_output_on():
|
||||||
|
print("Output is ON.")
|
||||||
|
print(f"Measured voltage: {eload.measure_voltage()} V")
|
||||||
|
print(f"Measured current: {eload.measure_current()} A")
|
||||||
|
else:
|
||||||
|
print("Failed to turn ON.")
|
||||||
|
|
||||||
|
elif cmd == "off":
|
||||||
|
eload.output_off()
|
||||||
|
time.sleep(1)
|
||||||
|
v = eload.measure_voltage()
|
||||||
|
print(f"Measured voltage after OFF: {v} V")
|
||||||
|
print("Voltage properly dropped to ~0 V." if v <= 0.1 else "Warning: Voltage still present!")
|
||||||
|
|
||||||
|
elif cmd == "status":
|
||||||
|
print(f"Output status: {'ON' if eload.is_output_on() else 'OFF'}")
|
||||||
|
print(f"Measured voltage: {eload.measure_voltage()} V")
|
||||||
|
print(f"Measured current: {eload.measure_current()} A")
|
||||||
|
|
||||||
|
elif cmd == "monitor":
|
||||||
|
print("Monitor mode started. Press any key to stop.")
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
print(f"Voltage: {eload.measure_voltage()} V | Current: {eload.measure_current()} A")
|
||||||
|
for _ in range(10):
|
||||||
|
if msvcrt.kbhit():
|
||||||
|
msvcrt.getch()
|
||||||
|
raise KeyboardInterrupt
|
||||||
|
time.sleep(0.1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nMonitor mode stopped.")
|
||||||
|
|
||||||
|
elif cmd == "manual":
|
||||||
|
eload.set_local()
|
||||||
|
print("Switched to manual (local) mode.")
|
||||||
|
|
||||||
|
elif cmd == "remote":
|
||||||
|
eload.set_remote()
|
||||||
|
print("Switched back to remote control mode.")
|
||||||
|
|
||||||
|
elif cmd == "exit":
|
||||||
|
eload.set_local()
|
||||||
|
print("Exiting and returning to manual mode...")
|
||||||
|
break
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("Unknown command. Please enter one of: on/off/set | manual/remote | status/monitor | setopt | exit.")
|
||||||
|
|
||||||
|
except EloadError as exc:
|
||||||
|
print(f"Eload error: {exc}")
|
||||||
|
|
||||||
|
eload.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user