From f89c849af4b825710fd6d627c7bfc11f316a2ea1 Mon Sep 17 00:00:00 2001 From: Jasper-Harvey0 Date: Tue, 8 Jul 2025 15:23:32 +1000 Subject: [PATCH 1/8] Add concept for changes to current mode selection --- src/fixate/drivers/dmm/fluke_8846a.py | 42 ++++++++++++++++++++++++- src/fixate/drivers/dmm/keithley_6500.py | 34 ++++++++++++++++++-- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/src/fixate/drivers/dmm/fluke_8846a.py b/src/fixate/drivers/dmm/fluke_8846a.py index 9677a7b3..a686e8dc 100644 --- a/src/fixate/drivers/dmm/fluke_8846a.py +++ b/src/fixate/drivers/dmm/fluke_8846a.py @@ -2,6 +2,8 @@ from fixate.core.exceptions import InstrumentError, ParameterError from fixate.drivers.dmm.helper import DMM import time +from typing import Literal +import enum class Fluke8846A(DMM): @@ -54,6 +56,10 @@ def __init__(self, instrument, *args, **kwargs): self._default_nplc = 10 # Default NPLC setting as per Fluke 8846A manual self._init_string = "" # Unchanging + # High and low current port definition. Each definition encodes the maximum current able to + # be measured by the port (in amps) + self.current_ports = {"HIGH": 10, "LOW": 400e-3} + @property def samples(self): return self._samples @@ -269,9 +275,43 @@ def voltage_dc(self, _range=None, auto_impedance=False): self._set_measurement_mode("voltage_dc", _range, suffix=command) def current_ac(self, _range=None): + + # Check the requested range is not more than the port capability: + if _range >= self.current_ports[port]: + raise ValueError( + "The selected port and range combination is not available for this instrument." + ) + + # Raise an error if the high port is selected when the low port should be used: + if _range <= self.current_ports["LOW"][1] and port == "HIGH": + raise ValueError( + "High range port selected when the low range port should be used!" + ) + self._set_measurement_mode("current_ac", _range) - def current_dc(self, _range=None): + def current_dc(self, _range, port: Literal["HIGH", "LOW"]): + """ + Set the measurement mode on the DMM to DC current. + + If the range and port selection are not compatible, i.e. someone has requested to measure + 1A on the low range port, an exception is raised. + This however does not account for the fact that the 'range' is not strictly the exact value to be measured. + We can only make the assumption that the measured value is expected to be less than the range value. + """ + + # Check the requested range is not more than the port capability: + if _range >= self.current_ports[port]: + raise ValueError( + "The selected port and range combination is not available for this instrument." + ) + + # Raise an error if the high port is selected when the low port should be used: + if _range <= self.current_ports["LOW"][1] and port == "HIGH": + raise ValueError( + "High range port selected when the low range port should be used!" + ) + self._set_measurement_mode("current_dc", _range) def resistance(self, _range=None): diff --git a/src/fixate/drivers/dmm/keithley_6500.py b/src/fixate/drivers/dmm/keithley_6500.py index 9fe3813a..7ee05e2d 100644 --- a/src/fixate/drivers/dmm/keithley_6500.py +++ b/src/fixate/drivers/dmm/keithley_6500.py @@ -53,6 +53,10 @@ def __init__(self, instrument, *args, **kwargs): self._nplc_default = 1 self._init_string = "" # Unchanging + # High and low current port definition. Each definition encodes the maximum current able to + # be measured by the port (in amps) + self.current_ports = {"HIGH": 10, "LOW": 3} + # Adapted for different DMM behaviour @property def display(self): @@ -303,10 +307,36 @@ def voltage_dc(self, _range=None, auto_impedance=False): command = "; :SENS:VOLT:DC:INP MOHM10" self._set_measurement_mode("voltage_dc", _range, suffix=command) - def current_ac(self, _range=None): + def current_ac(self, _range, port): + + # Check the requested range is not more than the port capability: + if _range >= self.current_ports[port]: + raise ValueError( + "The selected port and range combination is not available for this instrument." + ) + + # Raise an error if the high port is selected when the low port should be used: + if _range <= self.current_ports["LOW"][1] and port == "HIGH": + raise ValueError( + "High range port selected when the low range port should be used!" + ) + self._set_measurement_mode("current_ac", _range) - def current_dc(self, _range=None): + def current_dc(self, _range, port): + + # Check the requested range is not more than the port capability: + if _range >= self.current_ports[port]: + raise ValueError( + "The selected port and range combination is not available for this instrument." + ) + + # Raise an error if the high port is selected when the low port should be used: + if _range <= self.current_ports["LOW"][1] and port == "HIGH": + raise ValueError( + "High range port selected when the low range port should be used!" + ) + self._set_measurement_mode("current_dc", _range) def resistance(self, _range=None): From 2f2ae637594abfa843037e45b0e95b522381b946 Mon Sep 17 00:00:00 2001 From: Jasper-Harvey0 Date: Tue, 8 Jul 2025 15:24:29 +1000 Subject: [PATCH 2/8] Fix function def --- src/fixate/drivers/dmm/fluke_8846a.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fixate/drivers/dmm/fluke_8846a.py b/src/fixate/drivers/dmm/fluke_8846a.py index a686e8dc..35a5c356 100644 --- a/src/fixate/drivers/dmm/fluke_8846a.py +++ b/src/fixate/drivers/dmm/fluke_8846a.py @@ -274,7 +274,7 @@ def voltage_dc(self, _range=None, auto_impedance=False): command = "; :SENS:VOLT:DC:IMP:AUTO OFF" self._set_measurement_mode("voltage_dc", _range, suffix=command) - def current_ac(self, _range=None): + def current_ac(self, _range, port): # Check the requested range is not more than the port capability: if _range >= self.current_ports[port]: From 027a569703b4abcfdd6d5fd7cb304da67bbc56b9 Mon Sep 17 00:00:00 2001 From: Jasper-Harvey0 Date: Wed, 9 Jul 2025 08:07:18 +1000 Subject: [PATCH 3/8] Update doc strings --- src/fixate/drivers/dmm/fluke_8846a.py | 16 +++++++++++++--- src/fixate/drivers/dmm/keithley_6500.py | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/fixate/drivers/dmm/fluke_8846a.py b/src/fixate/drivers/dmm/fluke_8846a.py index 35a5c356..1e151f6b 100644 --- a/src/fixate/drivers/dmm/fluke_8846a.py +++ b/src/fixate/drivers/dmm/fluke_8846a.py @@ -275,6 +275,15 @@ def voltage_dc(self, _range=None, auto_impedance=False): self._set_measurement_mode("voltage_dc", _range, suffix=command) def current_ac(self, _range, port): + """ + Set the measurement mode on the DMM to AC current. + + If the range and port selection are not compatible, i.e. someone has requested to measure + 1 A on the low range port with a maximum capability of 400 mA, an exception is raised. + + If the range requested can be measured by the low port, but the high port is selected, an + exception is raised. + """ # Check the requested range is not more than the port capability: if _range >= self.current_ports[port]: @@ -295,9 +304,10 @@ def current_dc(self, _range, port: Literal["HIGH", "LOW"]): Set the measurement mode on the DMM to DC current. If the range and port selection are not compatible, i.e. someone has requested to measure - 1A on the low range port, an exception is raised. - This however does not account for the fact that the 'range' is not strictly the exact value to be measured. - We can only make the assumption that the measured value is expected to be less than the range value. + 1A on the low range port with a maximum capability of 400 mA, an exception is raised. + + If the range requested can be measured by the low port, but the high port is selected, an + exception is raised. """ # Check the requested range is not more than the port capability: diff --git a/src/fixate/drivers/dmm/keithley_6500.py b/src/fixate/drivers/dmm/keithley_6500.py index 7ee05e2d..2b8b51fb 100644 --- a/src/fixate/drivers/dmm/keithley_6500.py +++ b/src/fixate/drivers/dmm/keithley_6500.py @@ -308,7 +308,15 @@ def voltage_dc(self, _range=None, auto_impedance=False): self._set_measurement_mode("voltage_dc", _range, suffix=command) def current_ac(self, _range, port): + """ + Set the measurement mode on the DMM to AC current. + + If the range and port selection are not compatible, i.e. someone has requested to measure + 1A on the low range port with a maximum capability of 400 mA, an exception is raised. + If the range requested can be measured by the low port, but the high port is selected, an + exception is raised. + """ # Check the requested range is not more than the port capability: if _range >= self.current_ports[port]: raise ValueError( @@ -324,6 +332,15 @@ def current_ac(self, _range, port): self._set_measurement_mode("current_ac", _range) def current_dc(self, _range, port): + """ + Set the measurement mode on the DMM to DC current. + + If the range and port selection are not compatible, i.e. someone has requested to measure + 1A on the low range port with a maximum capability of 400 mA, an exception is raised. + + If the range requested can be measured by the low port, but the high port is selected, an + exception is raised. + """ # Check the requested range is not more than the port capability: if _range >= self.current_ports[port]: From 9a7efc7cdd042a56213c51342b54cc89ea79726e Mon Sep 17 00:00:00 2001 From: Jasper-Harvey0 Date: Wed, 9 Jul 2025 08:51:40 +1000 Subject: [PATCH 4/8] Update helper. Fix range and port check bounds --- src/fixate/drivers/dmm/fluke_8846a.py | 16 ++++++++-------- src/fixate/drivers/dmm/helper.py | 9 +++++++-- src/fixate/drivers/dmm/keithley_6500.py | 12 ++++++------ 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/fixate/drivers/dmm/fluke_8846a.py b/src/fixate/drivers/dmm/fluke_8846a.py index 1e151f6b..b5eb8562 100644 --- a/src/fixate/drivers/dmm/fluke_8846a.py +++ b/src/fixate/drivers/dmm/fluke_8846a.py @@ -286,15 +286,15 @@ def current_ac(self, _range, port): """ # Check the requested range is not more than the port capability: - if _range >= self.current_ports[port]: + if _range > self.current_ports[port]: raise ValueError( - "The selected port and range combination is not available for this instrument." + "The selected port and range combination is not available for this instrument. Consider using a different multimeter" ) # Raise an error if the high port is selected when the low port should be used: - if _range <= self.current_ports["LOW"][1] and port == "HIGH": + if _range < self.current_ports["LOW"] and port == "HIGH": raise ValueError( - "High range port selected when the low range port should be used!" + "High range port selected when the low range port should be used! Consider using a different multimeter." ) self._set_measurement_mode("current_ac", _range) @@ -311,15 +311,15 @@ def current_dc(self, _range, port: Literal["HIGH", "LOW"]): """ # Check the requested range is not more than the port capability: - if _range >= self.current_ports[port]: + if _range > self.current_ports[port]: raise ValueError( - "The selected port and range combination is not available for this instrument." + "The selected port and range combination is not available for this instrument. Consider using a different multimeter" ) # Raise an error if the high port is selected when the low port should be used: - if _range <= self.current_ports["LOW"][1] and port == "HIGH": + if _range < self.current_ports["LOW"] and port == "HIGH": raise ValueError( - "High range port selected when the low range port should be used!" + "High range port selected when the low range port should be used! Consider using a different multimeter." ) self._set_measurement_mode("current_dc", _range) diff --git a/src/fixate/drivers/dmm/helper.py b/src/fixate/drivers/dmm/helper.py index 3e5699e6..fc3d0ea6 100644 --- a/src/fixate/drivers/dmm/helper.py +++ b/src/fixate/drivers/dmm/helper.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from typing import Literal class DMM: @@ -60,10 +61,14 @@ def voltage_dc(self, _range=None, auto_impedance=False): """ raise NotImplementedError - def current_ac(self, _range): + def current_ac(self, _range, port: Literal["HIGH", "LOW"]): + """ + Sets the DMM in AC current measurement mode and puts it in the range given + by the argument _range. Signals expected to be measured must be < _range. + """ raise NotImplementedError - def current_dc(self, _range): + def current_dc(self, _range, port: Literal["HIGH", "LOW"]): """ Sets the DMM in DC current measurement mode and puts it in the range given by the argument _range. Signals expected to be measured must be < _range. diff --git a/src/fixate/drivers/dmm/keithley_6500.py b/src/fixate/drivers/dmm/keithley_6500.py index 2b8b51fb..16720cf7 100644 --- a/src/fixate/drivers/dmm/keithley_6500.py +++ b/src/fixate/drivers/dmm/keithley_6500.py @@ -318,15 +318,15 @@ def current_ac(self, _range, port): exception is raised. """ # Check the requested range is not more than the port capability: - if _range >= self.current_ports[port]: + if _range > self.current_ports[port]: raise ValueError( - "The selected port and range combination is not available for this instrument." + "The selected port and range combination is not available for this instrument. Consider using a different multimeter" ) # Raise an error if the high port is selected when the low port should be used: - if _range <= self.current_ports["LOW"][1] and port == "HIGH": + if _range < self.current_ports["LOW"] and port == "HIGH": raise ValueError( - "High range port selected when the low range port should be used!" + "High range port selected when the low range port should be used! Consider using a different multimeter." ) self._set_measurement_mode("current_ac", _range) @@ -343,13 +343,13 @@ def current_dc(self, _range, port): """ # Check the requested range is not more than the port capability: - if _range >= self.current_ports[port]: + if _range > self.current_ports[port]: raise ValueError( "The selected port and range combination is not available for this instrument." ) # Raise an error if the high port is selected when the low port should be used: - if _range <= self.current_ports["LOW"][1] and port == "HIGH": + if _range < self.current_ports["LOW"] and port == "HIGH": raise ValueError( "High range port selected when the low range port should be used!" ) From 9532f5c3ba4dd13365a9d07566e80fb17ab455b4 Mon Sep 17 00:00:00 2001 From: Jasper-Harvey0 Date: Wed, 9 Jul 2025 08:55:38 +1000 Subject: [PATCH 5/8] Update release notes. --- docs/release-notes.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 67238cce..1943517a 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -2,6 +2,19 @@ Release Notes ================================== +************* +Version 0.6.5 +************* +Release Date xx/xx/xx + +New Features +############ + +Improvements +############ +- DMM current mode functions now raise an exception when an incompatible port combination is used. The range and port parameters are now required parameters. + + ************* Version 0.6.4 ************* From ab363f10cc1c90449428e44982864404ec5baf6d Mon Sep 17 00:00:00 2001 From: Jasper-Harvey0 Date: Wed, 9 Jul 2025 09:09:03 +1000 Subject: [PATCH 6/8] Remove unused import. --- src/fixate/drivers/dmm/fluke_8846a.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/fixate/drivers/dmm/fluke_8846a.py b/src/fixate/drivers/dmm/fluke_8846a.py index b5eb8562..c0da6a13 100644 --- a/src/fixate/drivers/dmm/fluke_8846a.py +++ b/src/fixate/drivers/dmm/fluke_8846a.py @@ -3,7 +3,6 @@ from fixate.drivers.dmm.helper import DMM import time from typing import Literal -import enum class Fluke8846A(DMM): From 087356ced52e86ab22b791c47d65cee4576e66a2 Mon Sep 17 00:00:00 2001 From: Jasper-Harvey0 Date: Mon, 14 Jul 2025 10:45:47 +1000 Subject: [PATCH 7/8] Updates from review comments. Update tests. --- docs/release-notes.rst | 4 +- src/fixate/drivers/dmm/fluke_8846a.py | 28 +-- src/fixate/drivers/dmm/helper.py | 34 +++- src/fixate/drivers/dmm/keithley_6500.py | 29 +-- test/drivers/test_fluke_8846A.py | 59 +++++- test/drivers/test_keithley_6500.py | 242 ++++++++++++++++++++---- 6 files changed, 302 insertions(+), 94 deletions(-) diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 1943517a..8b3c0ba9 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -12,7 +12,7 @@ New Features Improvements ############ -- DMM current mode functions now raise an exception when an incompatible port combination is used. The range and port parameters are now required parameters. +- DMM current mode functions now raise an exception when an incompatible port and range combination is used. The range and port parameters are now required parameters. ************* @@ -22,7 +22,7 @@ Release Date 14/01/25 New Features ############ -- DMM drivers now have a new function to set NPLC (Number of Power Line Cycles) for the DMM. +- DMM drivers now have a new function to set NPLC (Number of Powr Line Cycles) for the DMM. - DMM drivers now have a new function to use the DMM's internal statistics function to take multiple measurements and return the mean, minimum and maximum values. Improvements diff --git a/src/fixate/drivers/dmm/fluke_8846a.py b/src/fixate/drivers/dmm/fluke_8846a.py index c0da6a13..40fbc0c8 100644 --- a/src/fixate/drivers/dmm/fluke_8846a.py +++ b/src/fixate/drivers/dmm/fluke_8846a.py @@ -57,7 +57,7 @@ def __init__(self, instrument, *args, **kwargs): # High and low current port definition. Each definition encodes the maximum current able to # be measured by the port (in amps) - self.current_ports = {"HIGH": 10, "LOW": 400e-3} + self._current_ports = {"HIGH": 10, "LOW": 400e-3} @property def samples(self): @@ -284,17 +284,8 @@ def current_ac(self, _range, port): exception is raised. """ - # Check the requested range is not more than the port capability: - if _range > self.current_ports[port]: - raise ValueError( - "The selected port and range combination is not available for this instrument. Consider using a different multimeter" - ) - - # Raise an error if the high port is selected when the low port should be used: - if _range < self.current_ports["LOW"] and port == "HIGH": - raise ValueError( - "High range port selected when the low range port should be used! Consider using a different multimeter." - ) + # Check the port and range combination is compatible for the instrument: + self._check_current_port_range(_range, port) self._set_measurement_mode("current_ac", _range) @@ -309,17 +300,8 @@ def current_dc(self, _range, port: Literal["HIGH", "LOW"]): exception is raised. """ - # Check the requested range is not more than the port capability: - if _range > self.current_ports[port]: - raise ValueError( - "The selected port and range combination is not available for this instrument. Consider using a different multimeter" - ) - - # Raise an error if the high port is selected when the low port should be used: - if _range < self.current_ports["LOW"] and port == "HIGH": - raise ValueError( - "High range port selected when the low range port should be used! Consider using a different multimeter." - ) + # Check the port and range combination is compatible for the instrument: + self._check_current_port_range(_range, port) self._set_measurement_mode("current_dc", _range) diff --git a/src/fixate/drivers/dmm/helper.py b/src/fixate/drivers/dmm/helper.py index fc3d0ea6..bb00b413 100644 --- a/src/fixate/drivers/dmm/helper.py +++ b/src/fixate/drivers/dmm/helper.py @@ -63,15 +63,21 @@ def voltage_dc(self, _range=None, auto_impedance=False): def current_ac(self, _range, port: Literal["HIGH", "LOW"]): """ - Sets the DMM in AC current measurement mode and puts it in the range given - by the argument _range. Signals expected to be measured must be < _range. + Sets the DMM in AC current measurement mode. + + Args: + _range: The measurement range to set on the instrument. + port: Informs the driver what physical port on the DMM is intended to be used for the measurement """ raise NotImplementedError def current_dc(self, _range, port: Literal["HIGH", "LOW"]): """ - Sets the DMM in DC current measurement mode and puts it in the range given - by the argument _range. Signals expected to be measured must be < _range. + Sets the DMM in DC current measurement mode. + + Args: + _range: The measurement range to set on the instrument. + port: Informs the driver what physical port on the DMM is intended to be used for the measurement """ raise NotImplementedError @@ -134,3 +140,23 @@ def __exit__(self, exc_type, exc_val, exc_tb): def nplc(self, nplc=None): return self._nplc_context_manager(self, nplc) + + def _check_current_port_range(self, range, port): + """ + Checks that the requested port and range are going to be compatible for the instrument. + Raises: + ValueError + Returns: + None + """ + # Check the requested range is not more than the port capability: + if range > self._current_ports[port]: + raise ValueError( + "The selected port and range combination is not available for this instrument. Consider using a different multimeter" + ) + + # Raise an error if the high port is selected when the low port should be used: + if range < self._current_ports["LOW"] and port == "HIGH": + raise ValueError( + "High range port selected when the low range port should be used! Consider using a different multimeter." + ) diff --git a/src/fixate/drivers/dmm/keithley_6500.py b/src/fixate/drivers/dmm/keithley_6500.py index 16720cf7..fbded8c0 100644 --- a/src/fixate/drivers/dmm/keithley_6500.py +++ b/src/fixate/drivers/dmm/keithley_6500.py @@ -55,7 +55,7 @@ def __init__(self, instrument, *args, **kwargs): # High and low current port definition. Each definition encodes the maximum current able to # be measured by the port (in amps) - self.current_ports = {"HIGH": 10, "LOW": 3} + self._current_ports = {"HIGH": 10, "LOW": 3} # Adapted for different DMM behaviour @property @@ -317,17 +317,8 @@ def current_ac(self, _range, port): If the range requested can be measured by the low port, but the high port is selected, an exception is raised. """ - # Check the requested range is not more than the port capability: - if _range > self.current_ports[port]: - raise ValueError( - "The selected port and range combination is not available for this instrument. Consider using a different multimeter" - ) - - # Raise an error if the high port is selected when the low port should be used: - if _range < self.current_ports["LOW"] and port == "HIGH": - raise ValueError( - "High range port selected when the low range port should be used! Consider using a different multimeter." - ) + # Check the port and range combination is compatible for the instrument: + self._check_current_port_range(_range, port) self._set_measurement_mode("current_ac", _range) @@ -341,18 +332,8 @@ def current_dc(self, _range, port): If the range requested can be measured by the low port, but the high port is selected, an exception is raised. """ - - # Check the requested range is not more than the port capability: - if _range > self.current_ports[port]: - raise ValueError( - "The selected port and range combination is not available for this instrument." - ) - - # Raise an error if the high port is selected when the low port should be used: - if _range < self.current_ports["LOW"] and port == "HIGH": - raise ValueError( - "High range port selected when the low range port should be used!" - ) + # Check the port and range combination is compatible for the instrument: + self._check_current_port_range(_range, port) self._set_measurement_mode("current_dc", _range) diff --git a/test/drivers/test_fluke_8846A.py b/test/drivers/test_fluke_8846A.py index 98da3f76..ae5030a9 100644 --- a/test/drivers/test_fluke_8846A.py +++ b/test/drivers/test_fluke_8846A.py @@ -48,8 +48,6 @@ def test_reset(dmm): [ ("voltage_ac", "VOLT:AC"), ("voltage_dc", "VOLT"), - ("current_dc", "CURR"), - ("current_ac", "CURR:AC"), ("resistance", "RES"), ("fresistance", "FRES"), ("period", "PER"), @@ -69,6 +67,63 @@ def test_mode(mode, expected, dmm): assert query.strip('"\r\n') == expected +@pytest.mark.parametrize( + "mode, expected", + [ + ("current_dc", "CURR"), + ("current_ac", "CURR:AC"), + ], +) +@pytest.mark.drivertest +def test_mode_current(mode, expected, dmm): + """ + The current mode has an additional 'port' required parameter. + So we need to test this differently than the other modes. + """ + getattr(dmm, mode)(_range=100e-3, port="LOW") + + query = dmm.instrument.query("SENS:FUNC?") + assert query.strip('"\r\n') == expected + + +@pytest.mark.parametrize( + "mode, expected", + [ + ("current_dc", "CURR"), + ("current_ac", "CURR:AC"), + ], +) +@pytest.mark.drivertest +def test_current_incompatible_port_and_range(mode, expected, dmm): + """ + The current mode has an additional 'port' required parameter. + So we need to test this differently than the other modes. + """ + with pytest.raises(ValueError) as excinfo: + getattr(dmm, mode)(_range=7, port="LOW") + + assert re.search("port and range combination not available", str(excinfo.value)) + + +@pytest.mark.parametrize( + "mode, expected", + [ + ("current_dc", "CURR"), + ("current_ac", "CURR:AC"), + ], +) +@pytest.mark.drivertest +def test_current_should_use_low_port(mode, expected, dmm): + """ + The current mode has an additional 'port' required parameter. + So we need to test this differently than the other modes. + """ + with pytest.raises(ValueError) as excinfo: + getattr(dmm, mode)(_range=100e-3, port="HIGH") + + assert re.search("low range port should be used", str(excinfo.value)) + + @pytest.mark.parametrize("nsample", [50000, 1]) @pytest.mark.drivertest def test_samples(nsample, dmm): diff --git a/test/drivers/test_keithley_6500.py b/test/drivers/test_keithley_6500.py index 0481b2ea..80c725d5 100644 --- a/test/drivers/test_keithley_6500.py +++ b/test/drivers/test_keithley_6500.py @@ -9,7 +9,7 @@ # Test values for measurement functions: # These are mostly defined either by J413 or an arbitrary number I picked. TEST_RESISTANCE = 100 # Resistance in loopback jig for testing -TEST_RESISTANCE_TOL = 1 # 1 Ohm absolute tolerance +TEST_RESISTANCE_TOL = 2 # 1 Ohm absolute tolerance TEST_CAPACITANCE = 4.7e-6 # Capacitance in loopback jig for testing TEST_CAPACITANCE_TOL = 0.5e-6 TEST_VOLTAGE_DC = 100e-3 @@ -48,8 +48,6 @@ def test_reset(dmm): [ ("voltage_ac", "VOLT:AC"), ("voltage_dc", "VOLT:DC"), - ("current_dc", "CURR:DC"), - ("current_ac", "CURR:AC"), ("resistance", "RES"), ("fresistance", "FRES"), ("period", "PER:VOLT"), @@ -57,7 +55,7 @@ def test_reset(dmm): ("capacitance", "CAP"), ("continuity", "CONT"), ("diode", "DIOD"), - ("temperature", "TEMP"), + pytest.param("temperature", "TEMP", marks=pytest.mark.xfail), pytest.param("ftemperature", "TEMP", marks=pytest.mark.xfail), ], ) @@ -69,6 +67,63 @@ def test_mode(mode, expected, dmm): assert query.strip('"\r\n') == expected +@pytest.mark.parametrize( + "mode, expected", + [ + ("current_dc", "CURR:DC"), + ("current_ac", "CURR:AC"), + ], +) +@pytest.mark.drivertest +def test_mode_current(mode, expected, dmm): + """ + The current mode has an additional 'port' required parameter. + So we need to test this differently than the other modes. + """ + getattr(dmm, mode)(_range=100e-3, port="LOW") + + query = dmm.instrument.query("SENS:FUNC?") + assert query.strip('"\r\n') == expected + + +@pytest.mark.parametrize( + "mode", + [ + "current_dc", + "current_ac", + ], +) +@pytest.mark.drivertest +def test_current_incompatible_port_and_range(mode, dmm): + """ + The current mode has an additional 'port' required parameter. + So we need to test this differently than the other modes. + """ + with pytest.raises(ValueError) as excinfo: + getattr(dmm, mode)(_range=7, port="LOW") + + assert re.search("port and range combination is not available", str(excinfo.value)) + + +@pytest.mark.parametrize( + "mode", + [ + "current_dc", + "current_ac", + ], +) +@pytest.mark.drivertest +def test_current_should_use_low_port(mode, dmm): + """ + The current mode has an additional 'port' required parameter. + So we need to test this differently than the other modes. + """ + with pytest.raises(ValueError) as excinfo: + getattr(dmm, mode)(_range=100e-3, port="HIGH") + + assert re.search("low range port should be used", str(excinfo.value)) + + @pytest.mark.parametrize("nsample", [50000, 1]) @pytest.mark.drivertest def test_samples(nsample, dmm): @@ -86,48 +141,137 @@ def test_samples_over_range(nsample, dmm): @pytest.mark.parametrize( - "mode, range", + "mode, args", [ - ("voltage_ac", 1), - ("voltage_ac", 10), - ("voltage_dc", 1), - ("voltage_dc", 10), - ("current_dc", 10e-6), - ("current_dc", 3), - ("current_ac", 100e-6), - ("current_ac", 3), - ("resistance", 10), - ("resistance", 10e6), - ("fresistance", 10), - ("fresistance", 10e6), - ("capacitance", 1e-6), - ("capacitance", 1e-9), + ( + "voltage_ac", + [ + 1, + ], + ), + ( + "voltage_ac", + [ + 10, + ], + ), + ( + "voltage_dc", + [ + 1, + ], + ), + ( + "voltage_dc", + [ + 10, + ], + ), + ("current_dc", [10e-6, "LOW"]), + ("current_dc", [3, "HIGH"]), + ("current_ac", [100e-6, "LOW"]), + ("current_ac", [3, "HIGH"]), + ( + "resistance", + [ + 10, + ], + ), + ( + "resistance", + [ + 10e6, + ], + ), + ( + "fresistance", + [ + 10, + ], + ), + ( + "fresistance", + [ + 10e6, + ], + ), + ( + "capacitance", + [ + 1e-6, + ], + ), + ( + "capacitance", + [ + 1e-9, + ], + ), ], ) @pytest.mark.drivertest -def test_range(mode, range, dmm): - getattr(dmm, mode)(_range=range) +def test_range(mode, args, dmm): + getattr(dmm, mode)(*args) mod = dmm.instrument.query("SENS:FUNC?").strip('"\r\n') query = dmm.instrument.query(mod + ":RANG?") - assert float(query) == pytest.approx(range) + assert float(query) == pytest.approx(args[0]) @pytest.mark.parametrize( - "mode, range", + "mode, args", [ - ("voltage_ac", 10000), - ("voltage_dc", 10000), - ("current_dc", 100), - ("current_ac", 100), - ("resistance", 10e9), - ("fresistance", 10e9), - ("capacitance", 1), + ( + "voltage_ac", + [ + 10000, + ], + ), + ( + "voltage_dc", + [ + 10000, + ], + ), + pytest.param( + "current_dc", + [100, "HIGH"], + marks=pytest.mark.xfail( + raises=ValueError, + reason="API now manages the range checks for the current functions.", + ), + ), + pytest.param( + "current_ac", + [100, "HIGH"], + marks=pytest.mark.xfail( + raises=ValueError, + reason="API now manages the range checks for the current functions.", + ), + ), + ( + "resistance", + [ + 10e9, + ], + ), + ( + "fresistance", + [ + 10e9, + ], + ), + ( + "capacitance", + [ + 1, + ], + ), ], ) @pytest.mark.drivertest -def test_range_over_range(mode, range, dmm): +def test_range_over_range(mode, args, dmm): with pytest.raises(InstrumentError) as excinfo: - getattr(dmm, mode)(_range=range) + getattr(dmm, mode)(*args) assert re.search("Parameter, measure range, expected value", str(excinfo.value)) @@ -251,7 +395,7 @@ def test_measurement_voltage_ac(funcgen, dmm, rm): @pytest.mark.drivertest def test_measurement_current_dc(funcgen, dmm, rm): rm.mux.connectionMap("DMM_R1_2w") - dmm.current_dc(_range=100e-3) + dmm.current_dc(_range=100e-3, port="LOW") idc = dmm.measurement() assert idc == pytest.approx(TEST_CURRENT_DC, abs=TEST_CURRENT_DC_TOL) @@ -261,7 +405,7 @@ def test_measurement_current_dc(funcgen, dmm, rm): @pytest.mark.drivertest def test_measurement_current_ac(funcgen, dmm, rm): rm.mux.connectionMap("DMM_R1_2w") - dmm.current_ac(_range=100e-3) + dmm.current_ac(_range=100e-3, port="LOW") iac = dmm.measurement() assert iac == pytest.approx(TEST_CURRENT_AC, abs=TEST_CURRENT_AC_TOL) @@ -356,7 +500,12 @@ def test_measurement_diode(funcgen, dmm, rm): ) @pytest.mark.drivertest def test_get_nplc(mode, dmm): - getattr(dmm, mode)() + if "current" in mode: + # Current modes have 2 required parameters. + # In this case, the values are un-important + getattr(dmm, mode)(_range=100e-3, port="LOW") + else: + getattr(dmm, mode)() dmm.set_nplc(reset=True) query = dmm.get_nplc() assert query == pytest.approx(1) @@ -394,7 +543,13 @@ def test_get_nplc(mode, dmm): ) @pytest.mark.drivertest def test_set_nplc(mode, dmm): - getattr(dmm, mode)() + if "current" in mode: + # Current modes have 2 required parameters. + # In this case the values are un-important + getattr(dmm, mode)(_range=100e-3, port="LOW") + else: + getattr(dmm, mode)() + dmm.set_nplc(nplc=10) query = dmm.get_nplc() assert query == pytest.approx(10) @@ -440,7 +595,12 @@ def test_set_nplc(mode, dmm): ) @pytest.mark.drivertest def test_nplc_context_manager(mode, dmm): - getattr(dmm, mode)() + if "current" in mode: + # Current modes have 2 required parameters. + # In this case the values are un-important + getattr(dmm, mode)(_range=100e-3, port="LOW") + else: + getattr(dmm, mode)() dmm.set_nplc(nplc=0.2) with dmm.nplc(1): @@ -467,8 +627,12 @@ def test_nplc_context_manager(mode, dmm): ) @pytest.mark.drivertest def test_min_avg_max(mode, samples, nplc, dmm, rm, funcgen): - # dmm.voltage_dc() - getattr(dmm, mode)() + if "current" in mode: + # The range and port are arbitrary. + # In this case the values are un-important + getattr(dmm, mode)(_range=100e-3, port="LOW") + else: + getattr(dmm, mode)() # only set nplc when able (depends on mode) if nplc: From a6cf2f166f39a5e709e67197838793a852d57bae Mon Sep 17 00:00:00 2001 From: Jasper-Harvey0 Date: Mon, 14 Jul 2025 12:34:45 +1000 Subject: [PATCH 8/8] Update tests. --- test/drivers/test_fluke_8846A.py | 181 +++++++++++++++++++++++------ test/drivers/test_keithley_6500.py | 6 +- 2 files changed, 146 insertions(+), 41 deletions(-) diff --git a/test/drivers/test_fluke_8846A.py b/test/drivers/test_fluke_8846A.py index ae5030a9..cb091325 100644 --- a/test/drivers/test_fluke_8846A.py +++ b/test/drivers/test_fluke_8846A.py @@ -102,7 +102,7 @@ def test_current_incompatible_port_and_range(mode, expected, dmm): with pytest.raises(ValueError) as excinfo: getattr(dmm, mode)(_range=7, port="LOW") - assert re.search("port and range combination not available", str(excinfo.value)) + assert re.search("port and range combination is not available", str(excinfo.value)) @pytest.mark.parametrize( @@ -141,50 +141,141 @@ def test_samples_over_range(nsample, dmm): @pytest.mark.parametrize( - "mode, range", + "mode, args", [ - ("voltage_ac", 1), - ("voltage_ac", 10), - ("voltage_dc", 1), - ("voltage_dc", 10), - ("current_dc", 1), - ("current_dc", 10), - ("current_ac", 1), - ("current_ac", 10), - ("resistance", 10), - ("resistance", 10e6), - ("fresistance", 10), - ("fresistance", 10e6), - ("capacitance", 1e-6), - ("capacitance", 1e-9), + ( + "voltage_ac", + [ + 1, + ], + ), + ( + "voltage_ac", + [ + 10, + ], + ), + ( + "voltage_dc", + [ + 1, + ], + ), + ( + "voltage_dc", + [ + 10, + ], + ), + ("current_dc", [1, "HIGH"]), + ("current_dc", [10, "HIGH"]), + ("current_ac", [1, "HIGH"]), + ("current_ac", [10, "HIGH"]), + ( + "resistance", + [ + 10, + ], + ), + ( + "resistance", + [ + 10e6, + ], + ), + ( + "fresistance", + [ + 10, + ], + ), + ( + "fresistance", + [ + 10e6, + ], + ), + ( + "capacitance", + [ + 1e-6, + ], + ), + ( + "capacitance", + [ + 1e-9, + ], + ), ], ) @pytest.mark.drivertest -def test_range(mode, range, dmm): - getattr(dmm, mode)(_range=range) +def test_range(mode, args, dmm): + getattr(dmm, mode)(*args) mod = dmm.instrument.query("SENS:FUNC?").strip('"\r\n') query = dmm.instrument.query(mod + ":RANG?") - assert float(query) == pytest.approx(range) + assert float(query) == pytest.approx(args[0]) # DMM does not return an error for under range. It just clips the value. # It does return an error for over range however.. + + @pytest.mark.parametrize( - "mode, range", + "mode, args", [ - ("voltage_ac", 10000), - ("voltage_dc", 10000), - ("current_dc", 100), - ("current_ac", 100), - ("resistance", 10e9), - ("fresistance", 10e9), - ("capacitance", 1), + ( + "voltage_ac", + [ + 10000, + ], + ), + ( + "voltage_dc", + [ + 10000, + ], + ), + pytest.param( + "current_dc", + [100, "HIGH"], + marks=pytest.mark.xfail( + raises=ValueError, + reason="API now manages the range checks for the current functions.", + ), + ), + pytest.param( + "current_ac", + [100, "HIGH"], + marks=pytest.mark.xfail( + raises=ValueError, + reason="API now manages the range checks for the current functions.", + ), + ), + ( + "resistance", + [ + 10e9, + ], + ), + ( + "fresistance", + [ + 10e9, + ], + ), + ( + "capacitance", + [ + 1, + ], + ), ], ) @pytest.mark.drivertest -def test_range_over_range(mode, range, dmm): +def test_range_over_range(mode, args, dmm): with pytest.raises(InstrumentError) as excinfo: - getattr(dmm, mode)(_range=range) + getattr(dmm, mode)(*args) assert re.search("Invalid parameter", str(excinfo.value)) @@ -298,7 +389,7 @@ def test_measurement_voltage_ac(funcgen, dmm, rm): @pytest.mark.drivertest def test_measurement_current_dc(funcgen, dmm, rm): rm.mux.connectionMap("DMM_R1_2w") - dmm.current_dc(_range=100e-3) + dmm.current_dc(_range=100e-3, port="LOW") idc = dmm.measurement() assert idc == pytest.approx(TEST_CURRENT_DC, abs=TEST_CURRENT_DC_TOL) @@ -308,7 +399,7 @@ def test_measurement_current_dc(funcgen, dmm, rm): @pytest.mark.drivertest def test_measurement_current_ac(funcgen, dmm, rm): rm.mux.connectionMap("DMM_R1_2w") - dmm.current_ac(_range=100e-3) + dmm.current_ac(_range=100e-3, port="LOW") iac = dmm.measurement() assert iac == pytest.approx(TEST_CURRENT_AC, abs=TEST_CURRENT_AC_TOL) @@ -403,7 +494,12 @@ def test_measurement_diode(funcgen, dmm, rm): ) @pytest.mark.drivertest def test_get_nplc(mode, dmm): - getattr(dmm, mode)() + if "current" in mode: + # Range and port are un-important here. + getattr(dmm, mode)(_range=100e-3, port="LOW") + else: + getattr(dmm, mode)() + dmm.set_nplc(reset=True) query = dmm.get_nplc() assert query == pytest.approx(10) @@ -441,7 +537,11 @@ def test_get_nplc(mode, dmm): ) @pytest.mark.drivertest def test_set_nplc(mode, dmm): - getattr(dmm, mode)() + if "current" in mode: + getattr(dmm, mode)(_range=100e-3, port="LOW") + else: + getattr(dmm, mode)() + dmm.set_nplc(nplc=1) query = dmm.get_nplc() assert query == pytest.approx(1) @@ -487,7 +587,10 @@ def test_set_nplc(mode, dmm): ) @pytest.mark.drivertest def test_nplc_context_manager(mode, dmm): - getattr(dmm, mode)() + if "current" in mode: + getattr(dmm, mode)(_range=100e-3, port="LOW") + else: + getattr(dmm, mode)() dmm.set_nplc(nplc=0.2) with dmm.nplc(1): @@ -514,8 +617,10 @@ def test_nplc_context_manager(mode, dmm): ) @pytest.mark.drivertest def test_min_avg_max(mode, samples, nplc, dmm, rm, funcgen): - # dmm.voltage_dc() - getattr(dmm, mode)() + if "current" in mode: + getattr(dmm, mode)(_range=100e-3, port="LOW") + else: + getattr(dmm, mode)() # only set nplc when able (depends on mode) if nplc: @@ -536,7 +641,7 @@ def test_min_avg_max(mode, samples, nplc, dmm, rm, funcgen): avg_val = values.avg max_val = values.max - assert min_val < avg_val < max_val + assert min_val <= avg_val <= max_val v = 100e-3 f = 60 @@ -549,7 +654,7 @@ def test_min_avg_max(mode, samples, nplc, dmm, rm, funcgen): avg_val2 = values.avg max_val2 = values.max - assert min_val2 < avg_val2 < max_val2 + assert min_val2 <= avg_val2 <= max_val2 # check if values from the two runs are different # We can only really do this for certain modes and the checks depend on the mode diff --git a/test/drivers/test_keithley_6500.py b/test/drivers/test_keithley_6500.py index 80c725d5..e7555188 100644 --- a/test/drivers/test_keithley_6500.py +++ b/test/drivers/test_keithley_6500.py @@ -9,7 +9,7 @@ # Test values for measurement functions: # These are mostly defined either by J413 or an arbitrary number I picked. TEST_RESISTANCE = 100 # Resistance in loopback jig for testing -TEST_RESISTANCE_TOL = 2 # 1 Ohm absolute tolerance +TEST_RESISTANCE_TOL = 1 # 1 Ohm absolute tolerance TEST_CAPACITANCE = 4.7e-6 # Capacitance in loopback jig for testing TEST_CAPACITANCE_TOL = 0.5e-6 TEST_VOLTAGE_DC = 100e-3 @@ -653,7 +653,7 @@ def test_min_avg_max(mode, samples, nplc, dmm, rm, funcgen): avg_val = values.avg max_val = values.max - assert min_val < avg_val < max_val + assert min_val <= avg_val <= max_val v = 100e-3 f = 60 @@ -666,7 +666,7 @@ def test_min_avg_max(mode, samples, nplc, dmm, rm, funcgen): avg_val2 = values.avg max_val2 = values.max - assert min_val2 < avg_val2 < max_val2 + assert min_val2 <= avg_val2 <= max_val2 # check if values from the two runs are different # We can only really do this for certain modes and the checks depend on the mode