<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: jashopcenter-stack</title>
    <description>The latest articles on DEV Community by jashopcenter-stack (@jashopcenterstack).</description>
    <link>https://dev.to/jashopcenterstack</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4211605%2F57d2f058-be61-4662-a980-36fa37483b3c.png</url>
      <title>DEV Community: jashopcenter-stack</title>
      <link>https://dev.to/jashopcenterstack</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jashopcenterstack"/>
    <language>en</language>
    <item>
      <title>I automated my test bench with Python: a YAML test sequencer for SCPI instruments</title>
      <dc:creator>jashopcenter-stack</dc:creator>
      <pubDate>Tue, 27 Oct 2026 19:07:19 +0000</pubDate>
      <link>https://dev.to/jashopcenterstack/i-automated-my-test-bench-with-python-a-yaml-test-sequencer-for-scpi-instruments-k05</link>
      <guid>https://dev.to/jashopcenterstack/i-automated-my-test-bench-with-python-a-yaml-test-sequencer-for-scpi-instruments-k05</guid>
      <description>&lt;p&gt;I write test software for electromechanical parts for a living, and I kept noticing the same thing: every bench task started as a quick PyVISA script and slowly turned into a mess. Hard-coded limits, no report, copy-paste between instruments, and nothing you could hand to someone else. So I built a small tool to fix that for myself, and I open-sourced it. This is how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea: the test is data, not code
&lt;/h2&gt;

&lt;p&gt;Instead of writing a Python script per test, you describe the test in a YAML file — which instruments, which SCPI commands, and the pass/fail limits — and a runner executes it and writes a report.&lt;/p&gt;

&lt;p&gt;Here's a trimmed plan that checks a DC power supply's output with a DMM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DC Power Supply Check&lt;/span&gt;
&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;v_set&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;5.0&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;i_lim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;1.0&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;instruments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;psu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TCPIP0::192.168.1.20::inst0::INSTR"&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;dmm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;USB0::0x2A8D::0x1301::MY00000001::INSTR"&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;instrument&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;psu&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;write&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APPL&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;CH1,{v_set},{i_lim}"&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;instrument&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;psu&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;write&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OUTP&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;CH1,ON"&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;No-load voltage&lt;/span&gt;
    &lt;span class="na"&gt;instrument&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dmm&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MEAS:VOLT:DC?&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;10,0.0001"&lt;/span&gt;
    &lt;span class="na"&gt;units&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;V&lt;/span&gt;
    &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;nominal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{v_set}"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;tol_pct&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;0.5&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;teardown&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;instrument&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;psu&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;write&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OUTP&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;CH1,OFF"&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it, tagging the unit's serial number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; benchplan run psu.yaml &lt;span class="nt"&gt;--serial&lt;/span&gt; SN-0042 &lt;span class="nt"&gt;--operator&lt;/span&gt; me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get a colour PASS/FAIL on the console, a self-contained HTML report, a JSON record, and a row per step appended to &lt;code&gt;results.csv&lt;/code&gt; — each with the measured value, the limit, the serial number and a timestamp. The exit code is 0 for pass and 1 for fail, so it drops straight into a fixture controller or CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I didn't expect to like: it runs with no hardware
&lt;/h2&gt;

&lt;p&gt;Every instrument can be &lt;em&gt;simulated inside the plan file&lt;/em&gt; — you write a few regex rules that answer the SCPI queries, with noise if you want it. That means you can write and demo an entire test before the DUT or the instruments exist, and the whole thing is unit-testable. &lt;code&gt;python -m benchplan demo&lt;/code&gt; runs a full power-supply acceptance test on three simulated instruments and opens a report.&lt;/p&gt;

&lt;p&gt;That simulation turned out to be the feature I use the most: I develop the plan on my laptop, then swap &lt;code&gt;sim://psu&lt;/code&gt; for a real VISA address at the bench and delete the sim block.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits that match how datasheets actually read
&lt;/h2&gt;

&lt;p&gt;Limits aren't just min/max. You can write a target with tolerance (&lt;code&gt;nominal: 5.0, tol_pct: 0.5&lt;/code&gt;), an exact match (&lt;code&gt;equals: CC&lt;/code&gt;), a set (&lt;code&gt;one_of: [CV, CC]&lt;/code&gt;), or a regex, and they can reference variables and earlier measurements — so load regulation is just a derived step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Load regulation&lt;/span&gt;
  &lt;span class="na"&gt;expr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(v_nl&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;v_fl)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;v_nl&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;100"&lt;/span&gt;
  &lt;span class="na"&gt;units&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%"&lt;/span&gt;
  &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;0.5&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  It's open source
&lt;/h2&gt;

&lt;p&gt;benchplan is MIT-licensed and on GitHub. It's pure Python (PyYAML only for the core), and the expression evaluator is sandboxed so a plan file can't run arbitrary code. 99 tests.&lt;/p&gt;

&lt;p&gt;👉 Repo: &lt;a href="https://github.com/jashopcenter-stack/benchplan" rel="noopener noreferrer"&gt;https://github.com/jashopcenter-stack/benchplan&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you'd rather not start from a blank file, I also put together a paid &lt;strong&gt;Instrument Automation Starter Kit&lt;/strong&gt; that bundles benchplan with ready-to-use drivers for Rigol, Keysight and Siglent instruments, four runnable test plans, example scripts, and a 20-page PDF guide. It's the batteries-included version: &lt;a href="https://shopcentered.gumroad.com/l/xeqouf" rel="noopener noreferrer"&gt;https://shopcentered.gumroad.com/l/xeqouf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Either way — I'd genuinely like feedback from other people who automate benches. What instruments should I add a driver for next?&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
      <category>mojo</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
