diff --git a/drivers/eload.py b/drivers/eload.py new file mode 100644 index 0000000..18dbb7c --- /dev/null +++ b/drivers/eload.py @@ -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() diff --git a/tools/eload_manual_control.py b/tools/eload_manual_control.py new file mode 100644 index 0000000..c7fe36f --- /dev/null +++ b/tools/eload_manual_control.py @@ -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()