Skip to content

PSU Data Logger

Timed logging of power supply readings

Data logging for power supply measurements.

Provides CSV-based logging of PSU output measurements over time. Useful for automated testing, characterization, and monitoring.

PSUDataLogger

PSUDataLogger(psu: PowerSupply, filepath: str, outputs: Optional[List[int]] = None)

Logs PSU output measurements to CSV file.

Records voltage, current, power, and mode for all outputs at regular intervals.

Example

psu = PowerSupply('192.168.1.200') psu.connect() logger = PSUDataLogger(psu, "psu_log.csv") logger.start()

... PSU operates ...

logger.log_measurement() # Manual logging logger.stop()

Initialize data logger.

Parameters:

Name Type Description Default
psu PowerSupply

PowerSupply instance to log

required
filepath str

Path to CSV log file

required
outputs Optional[List[int]]

List of output numbers to log (None = all outputs)

None
Source code in scpi_control/psu_data_logger.py
def __init__(
    self,
    psu: "PowerSupply",
    filepath: str,
    outputs: Optional[List[int]] = None,
):
    """Initialize data logger.

    Args:
        psu: PowerSupply instance to log
        filepath: Path to CSV log file
        outputs: List of output numbers to log (None = all outputs)
    """
    self.psu = psu
    self.filepath = Path(filepath)
    self.outputs = outputs
    self._file = None
    self._writer = None
    self._is_logging = False

    # Validate outputs
    if outputs is not None:
        for output_num in outputs:
            if not hasattr(self.psu, f"output{output_num}"):
                raise ValueError(f"PSU does not have output{output_num}")

is_logging property

is_logging: bool

Check if logger is currently active.

Returns:

Type Description
bool

True if logging, False otherwise

start

start() -> None

Start logging (open file and write header).

Creates CSV file with timestamp and measurement columns.

Source code in scpi_control/psu_data_logger.py
def start(self) -> None:
    """Start logging (open file and write header).

    Creates CSV file with timestamp and measurement columns.
    """
    if self._is_logging:
        logger.warning("Logger already started")
        return

    # Create directory if needed
    self.filepath.parent.mkdir(parents=True, exist_ok=True)

    # Open CSV file
    self._file = open(self.filepath, "w", newline="")
    self._writer = csv.writer(self._file)

    # Write header
    header = ["timestamp"]
    outputs_to_log = self._get_outputs_to_log()

    for output_num in outputs_to_log:
        header.extend(
            [
                f"output{output_num}_voltage_V",
                f"output{output_num}_current_A",
                f"output{output_num}_power_W",
                f"output{output_num}_mode",
                f"output{output_num}_enabled",
            ]
        )

    self._writer.writerow(header)
    self._file.flush()

    self._is_logging = True
    logger.info(f"Started logging to {self.filepath}")

log_measurement

log_measurement() -> None

Log a single measurement from all configured outputs.

Writes timestamp and current measurements to CSV.

Raises:

Type Description
RuntimeError

If logger not started

Source code in scpi_control/psu_data_logger.py
def log_measurement(self) -> None:
    """Log a single measurement from all configured outputs.

    Writes timestamp and current measurements to CSV.

    Raises:
        RuntimeError: If logger not started
    """
    if not self._is_logging:
        raise RuntimeError("Logger not started. Call start() first.")

    timestamp = datetime.now().isoformat()
    row = [timestamp]

    outputs_to_log = self._get_outputs_to_log()

    for output_num in outputs_to_log:
        output = getattr(self.psu, f"output{output_num}")

        try:
            voltage = output.measure_voltage()
            current = output.measure_current()
            power = output.measure_power()
            mode = output.get_mode()
            enabled = output.enabled

            row.extend(
                [
                    f"{voltage:.6f}",
                    f"{current:.6f}",
                    f"{power:.6f}",
                    mode,
                    str(enabled),
                ]
            )

        except Exception as e:
            logger.error(f"Failed to measure output {output_num}: {e}")
            # Write placeholder values on error
            row.extend(["ERROR", "ERROR", "ERROR", "ERROR", "ERROR"])

    self._writer.writerow(row)
    self._file.flush()

stop

stop() -> None

Stop logging and close file.

Source code in scpi_control/psu_data_logger.py
def stop(self) -> None:
    """Stop logging and close file."""
    if not self._is_logging:
        logger.warning("Logger not running")
        return

    if self._file:
        self._file.close()
        self._file = None
        self._writer = None

    self._is_logging = False
    logger.info(f"Stopped logging. Data saved to {self.filepath}")

TimedPSULogger

TimedPSULogger(psu: PowerSupply, filepath: str, interval: float = 1.0, outputs: Optional[List[int]] = None)

Timed data logger with automatic periodic measurements.

Uses a background thread to log measurements at regular intervals.

Example

psu = PowerSupply('192.168.1.200') psu.connect() with TimedPSULogger(psu, "psu_log.csv", interval=1.0) as logger: ... time.sleep(10) # Log for 10 seconds

Initialize timed logger.

Parameters:

Name Type Description Default
psu PowerSupply

PowerSupply instance to log

required
filepath str

Path to CSV log file

required
interval float

Logging interval in seconds (default: 1.0)

1.0
outputs Optional[List[int]]

List of output numbers to log (None = all outputs)

None
Source code in scpi_control/psu_data_logger.py
def __init__(
    self,
    psu: "PowerSupply",
    filepath: str,
    interval: float = 1.0,
    outputs: Optional[List[int]] = None,
):
    """Initialize timed logger.

    Args:
        psu: PowerSupply instance to log
        filepath: Path to CSV log file
        interval: Logging interval in seconds (default: 1.0)
        outputs: List of output numbers to log (None = all outputs)
    """
    self.psu = psu
    self.interval = interval
    self.logger = PSUDataLogger(psu, filepath, outputs)
    self._timer = None
    self._running = False

is_logging property

is_logging: bool

Check if logger is currently active.

Returns:

Type Description
bool

True if logging, False otherwise

start

start() -> None

Start timed logging.

Source code in scpi_control/psu_data_logger.py
def start(self) -> None:
    """Start timed logging."""
    if self._running:
        logger.warning("Timed logger already running")
        return

    self.logger.start()
    self._running = True
    self._schedule_next_log()
    logger.info(f"Started timed logging (interval={self.interval}s)")

stop

stop() -> None

Stop timed logging.

Source code in scpi_control/psu_data_logger.py
def stop(self) -> None:
    """Stop timed logging."""
    if not self._running:
        logger.warning("Timed logger not running")
        return

    self._running = False

    if self._timer:
        self._timer.cancel()
        self._timer = None

    self.logger.stop()
    logger.info("Stopped timed logging")