From 6cdcaf0f4d371709937eee3bfb1de7380ea4178b Mon Sep 17 00:00:00 2001 From: AlexWells Date: Fri, 15 Nov 2024 13:30:15 +0000 Subject: [PATCH] Add support for Agilent 34401A multimeter Each "instrument" slot now has a generic Multimeter associated with it, which can be instantiated to one of the supported multimeters. These two have completely different APIs, and very different behaviours in their remote interface. --- README.md | 5 +- images/gui.png | Bin 33607 -> 40418 bytes src/psc_datalogger/connection.py | 69 +++++++++-------- src/psc_datalogger/gui.py | 53 ++++++++++--- src/psc_datalogger/multimeter.py | 128 +++++++++++++++++++++++++++++++ tests/test_connection.py | 94 +++++++++++++++++------ 6 files changed, 280 insertions(+), 69 deletions(-) create mode 100644 src/psc_datalogger/multimeter.py diff --git a/README.md b/README.md index 3749b2f..7d19fb9 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,10 @@ # psc_datalogger -Provide a GUI interface to allow logging voltages from one to three Agilent 3458A Multimeters. +Provide a GUI interface to allow logging measurements from one to three Multimeters. Supported multimeters are the +Agilent 3458A and 34401A devices. Logging is done at a configurable interval. It can be also be configured to convert voltage readings into -a temperature, if a Type K Thermocouple is in use. +a temperature, if a Analog Devices AD8494 Thermocouple Amplifier is connected to the multimeter. The data is output in a CSV format. ![GUI](images/gui.png) diff --git a/images/gui.png b/images/gui.png index 43fd753050cda16d1ea7ca5a9fb5a3a0d6e0e9fa..d4ddb46780a9046e79c0318500179513c2d279dd 100644 GIT binary patch literal 40418 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 diff --git a/src/psc_datalogger/connection.py b/src/psc_datalogger/connection.py index 3a14ee9..6936445 100644 --- a/src/psc_datalogger/connection.py +++ b/src/psc_datalogger/connection.py @@ -5,21 +5,20 @@ from dataclasses import dataclass from datetime import datetime from threading import Event, RLock -from typing import List, Optional, TextIO, Tuple, cast +from typing import List, Optional, TextIO, Tuple, Type, cast import pyvisa from PyQt5.QtCore import QObject, QThread, pyqtSignal from pyvisa.resources import Resource, SerialInstrument +from .multimeter import Agilent3458A, InvalidNplcException, Multimeter from .statusbar import StatusBar from .temperature.converter import volts_to_celcius class ConnectionManager: - """Manage the connection to the instruments - - NOTE: There is expected to only be 1 Prologix device connected, which - will talk to up to 3 Agilent3458A Multimeters""" + """Manage the connection to the instruments, which is handled on a separate + thread.""" def __init__(self): self.thread = QThread() @@ -56,11 +55,12 @@ def set_instrument( enabled: bool, gpib_address: str, measure_temp: bool, + multimeter_type: Type[Multimeter], ) -> None: """Configure the given instrument number with the provided parameters""" try: self._worker.set_instrument( - instrument_number, enabled, gpib_address, measure_temp + instrument_number, enabled, gpib_address, measure_temp, multimeter_type ) except ConnectionNotInitialized: logging.exception( @@ -101,6 +101,8 @@ class InstrumentConfig: address: int = -1 # Indicate whether the voltage read should be converted into a temperature convert_to_temp: bool = False + # The multimeter in use. Default to 3458A, matching the GUI's default + multimeter: Multimeter = Agilent3458A() class DataWriter(DictWriter): @@ -220,6 +222,7 @@ def set_instrument( enabled: bool, gpib_address: str, measure_temp: bool, + multimeter_type: Type[Multimeter], ) -> None: """Configure the given instrument number with the provided parameters""" assert ( @@ -239,7 +242,7 @@ def set_instrument( f"Address {gpib_address}, measure temp {measure_temp}" ) self.instrument_configs[instrument_number] = InstrumentConfig( - enabled, address, measure_temp + enabled, address, measure_temp, multimeter_type() ) self._init_instrument(self.instrument_configs[instrument_number]) @@ -261,23 +264,18 @@ def _init_instrument(self, instrument: InstrumentConfig) -> None: f"_init_instrument called with invalid address '{gpib_address}'" ) + multimeter = instrument.multimeter + with self.lock: self._set_prologix_address(gpib_address) - self._connection_write("PRESET NORM") # Set a variety of defaults - self._connection_write("BEEP 0") # Disable annoying beeps + multimeter.initialize(self._connection) self._set_nplc(instrument) - self._connection_write("TRIG HOLD") # Disable triggering - # This means the instrument will stop collecting measurements, thus - # not filling its internal memory buffer. Later we will send single - # trigger events and immediately read it, thus keeping the buffer - # empty so we avoid reading stale results - - # Finally, read all data remaining in the buffer; it is possible for - # samples to be taken in the time between us sending the various above - # commands + # Read all data remaining in the buffer; it is possible for + # samples to be taken while initializing the multimeters. + # (Mostly an issue with the 3458A but doesn't hurt for other types) while self._connection_bytes_in_buffer(): try: self._connection_read() @@ -288,21 +286,25 @@ def _init_instrument(self, instrument: InstrumentConfig) -> None: def set_nplc(self, nplc: str) -> None: """Set the NPLC for all configured instruments""" - nplc_int = int(nplc) - if not 1 <= nplc_int <= 2000: - self.error.emit(f"NPLC value {nplc_int} outside allowed range 1 - 2000") + try: + for instrument in self.instrument_configs.values(): + if instrument.enabled: + self._set_nplc(instrument) + except InvalidNplcException as e: + self.error.emit( + f"NPLC value {self.nplc} outside allowed range " + f"{e.min_allowed} - {e.max_allowed}" + ) return - self.nplc = nplc_int - - for instrument in self.instrument_configs.values(): - if instrument.enabled: - self._set_nplc(instrument) def _set_nplc(self, instrument: InstrumentConfig) -> None: """Set the NPLC for the given instrument""" with self.lock: self._set_prologix_address(instrument.address) - self._connection_write(f"NPLC {self.nplc}") + + multimeter = instrument.multimeter + + multimeter.set_nplc(self._connection, self.nplc) # The number of samples is tied to the electrical frequency. # i.e. an NPLC of 50 will take 1 second, as our mains runs at 50Hz. @@ -316,11 +318,7 @@ def _set_prologix_address(self, gpib_address: int) -> None: """Configure the Prologix to point to the given GPIB address""" with self.lock: self._connection_write(f"++addr {gpib_address}") - # Instruct Prologix to enable read-after-write, - # which allows the controller to write data back to us! - self._connection_write("++auto 1") - - # Prologix seems to need a moment to process previous commands + # Prologix seems to need a moment to process previous command time.sleep(0.1) def validate_parameters(self) -> bool: @@ -468,8 +466,11 @@ def query_instruments(self) -> Tuple[datetime, str, str, str]: self._connection_write(f"++addr {i.address}") logging.debug(f"Triggering instrument {i.address}") + + multimeter = i.multimeter + # Request a single measurement - val = self._connection_query("TRIG SGL") + val = multimeter.take_reading(self._connection) logging.debug(f"Address {i.address} Value {val}") @@ -506,7 +507,7 @@ def _exit(self) -> None: if self.writer: self.writer.close() # Send relevant flags to allow run() to terminate - # Note it will do 1 more iteration of the loop, inlcuding the sleep + # Note it will do 1 more iteration of the loop, including the sleep self.running = False self.logging_signal.set() diff --git a/src/psc_datalogger/gui.py b/src/psc_datalogger/gui.py index 04666d3..a8fb519 100644 --- a/src/psc_datalogger/gui.py +++ b/src/psc_datalogger/gui.py @@ -1,11 +1,14 @@ import logging -from typing import Callable, Optional +from typing import Callable, Optional, Type +from PyQt5.QtCore import Qt from PyQt5.QtGui import QIntValidator from PyQt5.QtWidgets import ( QApplication, + QButtonGroup, QFileDialog, QFrame, + QGridLayout, QGroupBox, QHBoxLayout, QLabel, @@ -18,6 +21,8 @@ QWidget, ) +from psc_datalogger.multimeter import Agilent3458A, Agilent34401A, Multimeter + from . import __version__ from .connection import ConnectionManager from .statusbar import StatusBar @@ -136,6 +141,7 @@ def handle_instrument_changed(self): i.isChecked(), i.get_address(), i.get_temperature_checked(), + i.get_multimeter(), ) except ValueError: # Expected when first activating an instrument; the Address field @@ -188,7 +194,7 @@ def file_selected(self, new_path: str) -> None: class AgilentWidgets(QGroupBox): - """Contains widgets describing a single Agilent 3458A instrument""" + """Contains widgets to configure a single instrument""" def __init__( self, instrument_number: int, checked: bool, instrument_changed: Callable @@ -197,7 +203,7 @@ def __init__( Args: instrument_number: The number to use to identify this instance checked: True if the widgets should be enabled by default - instrument_changed: Callback to be called whenever an address changes + instrument_changed: Callback to be called whenever any configuration changes """ super().__init__(f"Instrument {instrument_number}") self.instrument_number = instrument_number @@ -222,12 +228,38 @@ def create_widgets(self, instrument_changed: Callable) -> None: # Don't need to .connect() the second button as changing the first one # triggers a callback that will check both buttons - layout = QHBoxLayout(self) - layout.addWidget(address_label) - layout.addWidget(self.address_input_box) - layout.addWidget(self.voltage_radiobutton) - layout.addWidget(self.temperature_radiobutton) - self.setLayout(layout) + grid_layout = QGridLayout(self) + + first_row_layout = QHBoxLayout() + + self.A3458_radiobutton = QRadioButton("3458A") + self.A3458_radiobutton.setChecked(True) + self.A3458_radiobutton.toggled.connect(instrument_changed) + self.A34401_radiobutton = QRadioButton("34401A") + + device_groupbox = QButtonGroup(self) + device_groupbox.addButton(self.A3458_radiobutton) + device_groupbox.addButton(self.A34401_radiobutton) + + first_row_layout.addWidget(self.A3458_radiobutton) + first_row_layout.addWidget(self.A34401_radiobutton) + + grid_layout.addLayout(first_row_layout, 0, 0, Qt.AlignmentFlag.AlignCenter) + + second_row_layout = QHBoxLayout() + + measurement_type_groupbox = QButtonGroup(self) + measurement_type_groupbox.addButton(self.voltage_radiobutton) + measurement_type_groupbox.addButton(self.temperature_radiobutton) + + second_row_layout.addWidget(address_label) + second_row_layout.addWidget(self.address_input_box) + second_row_layout.addWidget(self.voltage_radiobutton) + second_row_layout.addWidget(self.temperature_radiobutton) + + grid_layout.addLayout(second_row_layout, 1, 0, Qt.AlignmentFlag.AlignCenter) + + self.setLayout(grid_layout) def get_address(self) -> str: return self.address_input_box.text() @@ -236,3 +268,6 @@ def get_temperature_checked(self) -> bool: """Returns True if this instrument is configured to read temperature instead of voltage""" return self.temperature_radiobutton.isChecked() + + def get_multimeter(self) -> Type[Multimeter]: + return Agilent3458A if self.A3458_radiobutton.isChecked() else Agilent34401A diff --git a/src/psc_datalogger/multimeter.py b/src/psc_datalogger/multimeter.py new file mode 100644 index 0000000..6a4846d --- /dev/null +++ b/src/psc_datalogger/multimeter.py @@ -0,0 +1,128 @@ +import logging +import time +from abc import ABC, abstractmethod + +from pyvisa.resources import SerialInstrument + + +class InvalidNplcException(Exception): + """Exception thrown when user attempts to input an NPLC value that is + not valid for one-or-more multimeters""" + + min_allowed: float + max_allowed: float + + def __init__(self, min_allowed: float, max_allowed: float, *args): + self.min_allowed = min_allowed + self.max_allowed = max_allowed + super().__init__(args) + + +class Multimeter(ABC): + """Generic type of Multimeters. Provides interface for possible operations + on different controllers""" + + @abstractmethod + def initialize(self, connection: SerialInstrument): + """Perform the commands to initialize (or reset) the multimeter to a + known state""" + + @abstractmethod + def set_nplc(self, connection: SerialInstrument, nplc: int): + """Perform the commands to set the given nplc. Raises InvalidNplcException if + nplc is invalid.""" + + @abstractmethod + def take_reading(self, connection: SerialInstrument) -> str: + """Perform the commands to return a single voltage reading""" + + +class Agilent3458A(Multimeter): + """Implements the required commands for an Agilent 3458A multimeter""" + + def initialize(self, connection: SerialInstrument): + self._ensure_prologix_settings(connection) + connection.write("PRESET NORM") # Set a variety of defaults + connection.write("BEEP 0") # Disable annoying beeps + + # This means the instrument will stop collecting measurements, thus + # not filling its internal memory buffer. Later we will send single + # trigger events and immediately read it, thus keeping the buffer + # empty so we avoid reading stale results + connection.write("TRIG HOLD") + + logging.info("Initialized Agilent3458A") + + def set_nplc(self, connection: SerialInstrument, nplc: int): + self._ensure_prologix_settings(connection) + min = 1 + max = 2000 + if not min <= nplc <= max: + raise InvalidNplcException(min, max) + + connection.write(f"NPLC {nplc}") + + def take_reading(self, connection: SerialInstrument) -> str: + # Send single trigger command to the multimeter, and return the resultant value + return connection.query("TRIG SGL") + + def _ensure_prologix_settings(self, connection: SerialInstrument): + connection.write("++auto 1") # Enables use of "query" + connection.read_termination = "\r\n" + + +class Agilent34401A(Multimeter): + """Implements the required commands for an Agilent 34401A multimeter""" + + def initialize(self, connection: SerialInstrument): + self._ensure_prologix_settings(connection) + connection.write("*RST") # Reset the multimeter to its power-on configuration. + # Configure for voltage reading with range of 10V, resolution of 0.003 + connection.write("CONFigure:VOLTage:DC 10, 0.003") + connection.write("SYSTEM:BEEPER:STATE OFF") # Disable system beeps + + logging.info("Initialized Agilent34401A") + + def set_nplc(self, connection: SerialInstrument, nplc: int): + self._ensure_prologix_settings(connection) + + min = 0.02 + max = 100 + if not min <= nplc <= max: + raise InvalidNplcException(min, max) + + connection.write(f"VOLT:DC:NPLCYCLES {nplc}") + + def take_reading(self, connection: SerialInstrument) -> str: + self._ensure_prologix_settings(connection) + + # Get the currently configured NPLC setting, to allow us to estimate how + # long the subsequent voltage reading will take + connection.write("VOLT:DC:NPLCYCLES?") + nplc = connection.query("++read eoi") + + # Triggers a single reading of the multimeter, which will save the data + # into internal memory. This can take several seconds, based on NPLC setting. + connection.write("INIT") + + # Estimate how long to wait. An NPLC of 50 should take 1 second (matches + # electrical frequency). Add 1 for some padding. + wait_time = (float(self._clean_string(nplc)) / 50) + 1 + time.sleep(wait_time) + + # Data should now be in internal memory; tell the multimeter to move it + # to output buffer, then tell the Prologix controller to read it. + connection.write("FETCH?") + volts = connection.query("++read eoi") + + return self._clean_string(volts) + + def _clean_string(self, volts: str) -> str: + """Clean up the returned string by removing extraneous characters""" + return volts.replace("\x00", "") + + def _ensure_prologix_settings(self, connection: SerialInstrument): + # "auto 1" triggers error -420 "Query Unterminated" errors, so must do manual + # explicit write and reads + connection.write("++auto 0") + connection.read_termination = "\n" diff --git a/tests/test_connection.py b/tests/test_connection.py index 0955e41..d22ea90 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -4,8 +4,8 @@ from datetime import datetime, timedelta from pathlib import Path from threading import Event -from typing import Any, Generator -from unittest.mock import MagicMock, call, patch +from typing import Any, Generator, List +from unittest.mock import MagicMock, _Call, call, patch import pytest import pyvisa @@ -22,6 +22,7 @@ PrologixNotFoundException, Worker, ) +from psc_datalogger.multimeter import Agilent3458A, Agilent34401A, Multimeter class TestConnectionManager: @@ -117,7 +118,9 @@ def test_set_instrument_handles_expected_exception( connmgr._worker = mocked_worker with caplog.at_level(logging.ERROR): - connmgr.set_instrument(0, False, "10", False) # parameters don't matter + connmgr.set_instrument( + 0, False, "10", False, Agilent3458A + ) # parameters don't matter # Check that the expected message appears assert "Could not set instrument" in caplog.text @@ -208,10 +211,19 @@ def test_set_instrument(self, worker: Worker): enabled = True address = 22 measure_temp = False - worker.set_instrument(num, enabled, str(address), measure_temp) + multimeter = MagicMock() + multimeter_returner = MagicMock(return_value=multimeter, spec=Agilent3458A) + worker.set_instrument( + num, + enabled, + str(address), + measure_temp, + multimeter_returner, # type: ignore + ) - expected_config = InstrumentConfig(enabled, address, measure_temp) + expected_config = InstrumentConfig(enabled, address, measure_temp, multimeter) + multimeter_returner.assert_called_once_with() assert worker.instrument_configs[num] == expected_config mocked_init_instrument.assert_called_once_with(expected_config) @@ -226,15 +238,24 @@ def test_override_instrument(self, worker: Worker): enabled = True address = 22 measure_temp = False - worker.set_instrument(num, enabled, str(address), measure_temp) + worker.set_instrument(num, enabled, str(address), measure_temp, Agilent3458A) # Override values enabled = False address = 44 measure_temp = True - expected_config = InstrumentConfig(enabled, address, measure_temp) - worker.set_instrument(num, enabled, str(address), measure_temp) + multimeter = MagicMock() + multimeter_returner = MagicMock(return_value=multimeter, spec=Agilent3458A) + expected_config = InstrumentConfig(enabled, address, measure_temp, multimeter) + worker.set_instrument( + num, + enabled, + str(address), + measure_temp, + multimeter_returner, # type: ignore + ) + multimeter_returner.assert_called_once_with() assert worker.instrument_configs[num] == expected_config mocked_init_instrument.assert_called_with(expected_config) @@ -242,29 +263,54 @@ def test_override_instrument(self, worker: Worker): def test_set_instrument_invalid_values(self, invalid_value: int, worker: Worker): """Test that passing invalid values raises expected exception""" with pytest.raises(AssertionError): - worker.set_instrument(invalid_value, True, "123", False) + worker.set_instrument(invalid_value, True, "123", False, Agilent3458A) - def test_init_instrument(self, worker: Worker): + @pytest.mark.parametrize( + "multimeter, expected_calls", + [ + ( + Agilent3458A(), + [ + call("++addr 22"), + call("++auto 1"), + call("PRESET NORM"), + call("BEEP 0"), + call("TRIG HOLD"), + call("++addr 22"), + call("++auto 1"), + call("NPLC 50"), + ], + ), + ( + Agilent34401A(), + [ + call("++addr 22"), + call("++auto 0"), + call("*RST"), + call("CONFigure:VOLTage:DC 10, 0.003"), + call("SYSTEM:BEEPER:STATE OFF"), + call("++addr 22"), + call("++auto 0"), + call("VOLT:DC:NPLCYCLES 50"), + ], + ), + ], + ids=["Agilent3458A", "Agilent34401A"], + ) + def test_init_instrument( + self, worker: Worker, multimeter: Multimeter, expected_calls: List[_Call] + ): """Test that init_instrument sends the expected calls to the hardware""" - mocked_connection = MagicMock() + mocked_connection = MagicMock(spec=SerialInstrument) mocked_connection.bytes_in_buffer = 0 worker._connection = mocked_connection enabled = True - address = 22 - worker._init_instrument(InstrumentConfig(enabled, address)) - - expected_calls = [ - call(f"++addr {address}"), - call("++auto 1"), - call("PRESET NORM"), - call("BEEP 0"), - call(f"++addr {address}"), - call("++auto 1"), - call("NPLC 50"), - call("TRIG HOLD"), - ] + address = 22 # Note: Duplicated in the parameterized calls + worker._init_instrument( + InstrumentConfig(enabled, address, multimeter=multimeter) + ) mocked_connection.write.assert_has_calls(expected_calls, any_order=False)