Oliver's Instrument Library (pyoil)
Oliver's Instrument Library is an open-source collection of test-equipment driver code that saves me from having to write the same instrument control layer over and over. It is built around PyVISA, but presents supported instrument features as Pythonic getters and setters so normal use does not require hand-crafted SCPI commands.
The PyPI distribution is named pyoil; import it as oil.
python -m pip install pyoil
Supported instruments
- Rohde & Schwarz SMR20 signal generator
- Keysight E5071C vector network analyser
- Keysight N9030 signal analyser
- Dual-SP8T RF switch matrix
The supported surface is deliberately small and explicit. Instrument- and firmware-specific behaviour still needs a physical-instrument smoke test before it is relied on in production.
Instrument control
Create a driver with its VISA resource string, then use properties rather than composing SCPI messages yourself:
from oil.core import ip_address_string
from oil.sig_gens import SMR20
signal_generator = SMR20(ip_address_string("192.168.1.20"))
signal_generator.frequency = 1_000_000
signal_generator.power = -10
signal_generator.rf_enable = True
print(signal_generator.frequency) # 1000000.0
Product-based test runs
TestManager turns sweeps into the Cartesian product of their values. For
example, two frequencies and three power levels create six sequential test
iterations. Each action receives the current TestIteration, which provides a
data dictionary for passing settings and measurements to later actions.
This example sets an SMR20 and N9030 to each frequency, sweeps generator power, peak-searches the analyser, and records the result:
from oil.analyzers import N9030
from oil.core import ip_address_string
from oil.sig_gens import SMR20
from oil.test_manager import ListSweep, ParameterSweep, TestAction, TestManager, TestRecord
generator = SMR20(ip_address_string("192.168.1.20"))
analyser = N9030(ip_address_string("192.168.1.21"))
record = TestRecord(
"measured_power.csv",
["frequency_hz", "generator_power_dbm", "measured_power_dbm"],
)
def set_frequency(iteration, frequency_hz):
generator.frequency = frequency_hz
analyser.frequency_center = frequency_hz
iteration.data["frequency_hz"] = frequency_hz
def set_power(iteration, power_dbm):
generator.power = power_dbm
iteration.data["generator_power_dbm"] = power_dbm
def measure_and_record(iteration, _unused):
marker = analyser.marker[1]
marker.enabled = True
marker.peak_search()
iteration.data["measured_power_dbm"] = marker.power
record.write(iteration.data)
test = TestManager([
ParameterSweep("frequency", set_frequency, 1_000_000, 2_000_000, 1_000_000),
ListSweep("power", set_power, [-20, -10, 0]),
TestAction("measure", 0, measure_and_record),
])
generator.rf_enable = True
test.run()
generator.rf_enable = False
ParameterSweep includes both endpoints when the interval is divisible by the
step. ListSweep preserves the supplied order. Use TestAction for a single
operation that should be included in every product combination.
Test records
TestRecord creates a CSV with the columns you specify plus timestamp. It
opens the file only while writing a row, which keeps each completed row on disk
if the process stops unexpectedly. Extra keys in iteration.data are ignored;
declared columns without a value are written as empty cells.
The example above produces rows shaped like:
frequency_hz,generator_power_dbm,measured_power_dbm,timestamp
1000000,-20,-20.14,2026-09-07-22:32:08
Test logging
Each TestManager configures an oil logger. Informational progress is shown
on the console; a timestamped log file is written under logs/ in the current
working directory and includes debug-level messages. Use the manager's helper
methods from actions when a run needs contextual messages:
def verify_power(iteration, _unused):
measured = iteration.data["measured_power_dbm"]
if measured < -25:
iteration.test_manager.error(f"Power too low: {measured:.2f} dBm")
else:
iteration.test_manager.info(f"Power OK: {measured:.2f} dBm")
Virtual instruments and unit tests
Virtual versions of the supported instruments run the same driver-level API without a VISA connection. They make unit tests deterministic and let driver tests assert the SCPI contract at the virtual boundary; they are not a substitute for hardware verification.
from oil.sig_gens import VirtualSMR20
generator = VirtualSMR20()
generator.frequency = 2_400_000_000
generator.power = -5
assert generator.frequency == 2_400_000_000
assert generator.power == -5
Run the project unit suite from the repository root:
python -m pytest
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyoil-0.0.15.tar.gz.
File metadata
- Download URL: pyoil-0.0.15.tar.gz
- Upload date:
- Size: 21.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b7d9cd4922554d7c0e37461e9723b050d733a0a09b189fbba6c5d4261075faa1
|
|
| MD5 |
cbe2f300e6ba464d4ffa9ce60768a592
|
|
| BLAKE2b-256 |
19bc4a4685eba82eafe4adce57431b42cfa7fe7f7190dc13fc419a850bf1e40d
|
Provenance
The following attestation bundles were made for pyoil-0.0.15.tar.gz:
Publisher:
publish-pypi.yml on ok65/oil
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyoil-0.0.15.tar.gz -
Subject digest:
b7d9cd4922554d7c0e37461e9723b050d733a0a09b189fbba6c5d4261075faa1 - Sigstore transparency entry: 2753806938
- Sigstore integration time:
-
Permalink:
ok65/oil@c82dda37dc0f38570c701978fead8664d7efb66e -
Branch / Tag:
refs/tags/v0.0.15 - Owner: https://github.com/ok65
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@c82dda37dc0f38570c701978fead8664d7efb66e -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyoil-0.0.15-py3-none-any.whl.
File metadata
- Download URL: pyoil-0.0.15-py3-none-any.whl
- Upload date:
- Size: 26.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
396c999bf504c8b358eed83b2b047f90b50ca264ec321a714c4e80f679df3d5b
|
|
| MD5 |
24094b01a559c2158362a832c90a9840
|
|
| BLAKE2b-256 |
a1d52faccd940f704ed58601f98970707e5cb9e4820091b57dc1377cba5b30f6
|
Provenance
The following attestation bundles were made for pyoil-0.0.15-py3-none-any.whl:
Publisher:
publish-pypi.yml on ok65/oil
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyoil-0.0.15-py3-none-any.whl -
Subject digest:
396c999bf504c8b358eed83b2b047f90b50ca264ec321a714c4e80f679df3d5b - Sigstore transparency entry: 2753806957
- Sigstore integration time:
-
Permalink:
ok65/oil@c82dda37dc0f38570c701978fead8664d7efb66e -
Branch / Tag:
refs/tags/v0.0.15 - Owner: https://github.com/ok65
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@c82dda37dc0f38570c701978fead8664d7efb66e -
Trigger Event:
push
-
Statement type: