Skip to content

Commit 3499ca2

Browse files
Anai-Guoclaude
andcommitted
Quality hardening: 131 tests, 8 skills, driver mocks, error recovery, web API tests
Driver mock tests (22 new): - Keithley 2400: connect, configure, set_current, measure_voltage/iv, enable/disable output, shutdown — all SCPI sequences verified - Keithley 6221: configure delta, arm/start/abort, read delta — verified - Lakeshore 335: temperature, setpoint, heater, ramp, shutdown — verified - All tests use mock VISA (no real instruments needed) Measurement skills (6 new, 8 total): - hall.md: van der Pauw, carrier density/mobility - iv.md: voltage sweep, compliance, junction analysis - rt.md: temperature ramp with stabilization, TCR detection - cv.md: LCR AC+DC sweep, Mott-Schottky analysis - mr.md: bidirectional field sweep with hysteresis - delta.md: K6221+K2182A thermal EMF elimination Error recovery: - VisaDriver.auto_reconnect: disconnect/connect/retry on exhausted retries - VisaDriver.is_connected(): active test via *IDN? query Web API tests (6 new): - /api/health, /api/templates, /api/templates/{type}, template not found, dashboard page, monitor page — all via httpx AsyncClient 131 tests passing (up from 103). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 11f0a6c commit 3499ca2

9 files changed

Lines changed: 642 additions & 4 deletions

File tree

skills/cv.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
name: CV Measurement
3+
description: Capacitance-voltage profiling for semiconductor doping and interface characterization
4+
measurement_type: CV
5+
instruments: [lcr_meter, dc_bias_source]
6+
version: "1.0"
7+
---
8+
9+
## Protocol
10+
11+
1. **Connect** instruments and verify identity via *IDN?
12+
2. **Configure** LCR meter:
13+
- AC test signal: frequency = {frequency} Hz (typical: 1 MHz for standard CV)
14+
- AC amplitude = {ac_level} V (typical: 10-50 mV, small-signal regime)
15+
- Measurement mode: Cp-D or Cs-Rs (parallel or series model)
16+
3. **Configure** DC bias source or LCR internal bias
17+
4. **Sweep** DC bias from {v_start} V to {v_stop} V in {v_step} V steps:
18+
- At each bias point:
19+
a. Set DC bias voltage
20+
b. Wait {settling_time} s for depletion region equilibration
21+
c. Measure capacitance C and dissipation factor D (or conductance G)
22+
d. Record bias voltage, C, D
23+
5. **Optional**: Reverse sweep for hysteresis (interface traps indicator)
24+
6. **Optional**: Multi-frequency CV sweep at {freq_list} Hz for frequency dispersion
25+
7. **Zero** DC bias
26+
8. **Save** data to {output_dir} as CSV
27+
28+
## Expected Output
29+
30+
- C vs V curve showing accumulation, depletion, and inversion regions
31+
- 1/C^2 vs V (Mott-Schottky plot) with linear fit
32+
- Doping concentration N_d or N_a from Mott-Schottky slope
33+
- Built-in potential V_bi from x-intercept
34+
- Oxide capacitance C_ox and oxide thickness (for MOS structures)
35+
- Flatband voltage V_fb and interface trap density D_it (if multi-frequency)

skills/delta.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
name: Delta Mode Measurement
3+
description: Ultra-low resistance measurement using K6221+K2182A delta mode for thermal EMF elimination
4+
measurement_type: Delta
5+
instruments: [k6221_current_source, k2182a_nanovoltmeter]
6+
version: "1.0"
7+
---
8+
9+
## Protocol
10+
11+
1. **Connect** K6221 and K2182A via Trigger Link cable and RS-232/GPIB
12+
2. **Verify** identity of both instruments via *IDN?
13+
3. **Configure** K2182A nanovoltmeter:
14+
- Range: auto or fixed {voltage_range} V (typical: 10 mV for low-R)
15+
- NPLC = {nplc} (typical: 5 for noise rejection)
16+
- Analog filter: ON
17+
4. **Configure** K6221 delta mode:
18+
- Current amplitude: {current} A (typical: 1-100 mA)
19+
- Delta delay: {delta_delay} s (typical: auto or 2-5 ms)
20+
- Number of delta readings: {count} (typical: 100)
21+
- Compliance voltage: {compliance} V
22+
5. **Arm** K6221 delta mode: `SOUR:DELT:ARM`
23+
6. **Start** measurement: K6221 alternates +I / -I, K2182A captures voltage at each polarity
24+
7. **Read** delta values: V_delta = (V(+I) - V(-I)) / 2 eliminates thermal EMF
25+
8. **Calculate** resistance: R = V_delta / I
26+
9. **Repeat** or sweep external parameter (temperature, field) as needed
27+
10. **Disable** output on K6221
28+
11. **Save** data to {output_dir} as CSV
29+
30+
## Expected Output
31+
32+
- Mean resistance R with standard deviation
33+
- Individual delta readings for noise assessment
34+
- R vs time (for stability monitoring)
35+
- Thermal EMF estimate: V_offset = (V(+I) + V(-I)) / 2
36+
- Resistance resolution typically < 1 uOhm with proper setup

skills/hall.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
name: Hall Effect Measurement
3+
description: Standard Hall effect measurement for carrier density and mobility extraction
4+
measurement_type: Hall
5+
instruments: [source_meter, dmm, gaussmeter, switch_matrix]
6+
version: "1.0"
7+
---
8+
9+
## Protocol
10+
11+
1. **Connect** instruments and verify identity via *IDN?
12+
2. **Configure** switch matrix for van der Pauw geometry (or Hall bar contacts)
13+
3. **Set** source current to {current} A (typical: 10-100 uA for semiconductors)
14+
4. **Apply** perpendicular magnetic field {field} Oe (fixed or swept)
15+
5. For each measurement configuration:
16+
a. Source current I+ through contacts 1-3, measure V across 2-4 (V_24)
17+
b. Reverse current I- through contacts 3-1, measure V across 2-4
18+
c. Average: V_H = (V_24(+I) - V_24(-I)) / 2
19+
6. **Repeat** at reversed field (-B) for offset elimination:
20+
- R_H = [V_H(+B) - V_H(-B)] / (2 * I * B) * d
21+
7. **Optional**: Sweep field from {field_start} to {field_stop} for linearity check
22+
8. **Measure** sheet resistance via van der Pauw method (rotate contacts)
23+
9. **Disable** source output and zero field
24+
10. **Save** data to {output_dir} as CSV
25+
26+
## Expected Output
27+
28+
- Hall coefficient R_H (m^3/C)
29+
- Carrier density n = 1 / (R_H * e) (cm^-3)
30+
- Sheet resistance R_s (Ohm/sq)
31+
- Hall mobility mu_H = R_H / (rho * d) (cm^2/V-s)
32+
- V_H vs B plot for linearity verification

skills/iv.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
name: IV Curve Measurement
3+
description: Current-voltage characteristic measurement for devices and junctions
4+
measurement_type: IV
5+
instruments: [source_meter, dmm]
6+
version: "1.0"
7+
---
8+
9+
## Protocol
10+
11+
1. **Connect** instruments and verify identity via *IDN?
12+
2. **Configure** source meter: voltage source mode, set compliance current to {compliance} A
13+
3. **Set** measurement speed/integration time (NPLC = {nplc}, typical: 1-10)
14+
4. **Sweep** voltage from {v_start} V to {v_stop} V in {v_step} V steps:
15+
- At each voltage point:
16+
a. Set source voltage
17+
b. Wait {settling_time} s for stabilization
18+
c. Measure current (average {num_averages} readings)
19+
d. Record voltage readback and current
20+
5. **Optional**: Reverse sweep from {v_stop} to {v_start} for hysteresis check
21+
6. **Optional**: Repeat sweep at different temperatures for T-dependent IV
22+
7. **Disable** source meter output
23+
8. **Save** data to {output_dir} as CSV
24+
25+
## Expected Output
26+
27+
- I vs V curve (forward and optional reverse)
28+
- Differential conductance dI/dV vs V
29+
- For diodes: ideality factor n, series resistance R_s, saturation current I_0
30+
- For resistors: resistance R from linear fit slope
31+
- For tunneling junctions: barrier height from Simmons/Brinkman fit

skills/mr.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
name: Magnetoresistance Measurement
3+
description: Magnetoresistance measurement for field-dependent transport characterization
4+
measurement_type: MR
5+
instruments: [source_meter, dmm, gaussmeter, electromagnet_supply]
6+
version: "1.0"
7+
---
8+
9+
## Protocol
10+
11+
1. **Connect** instruments and verify identity via *IDN?
12+
2. **Configure** source meter: DC current mode, set compliance voltage
13+
3. **Set** source current to {current} A (typical: 10 uA - 1 mA for thin films)
14+
4. **Configure** electromagnet power supply for field sweep
15+
5. **Sweep** magnetic field from {field_start} to {field_stop} Oe in {field_step} Oe steps:
16+
- At each field point:
17+
a. Set field and wait {settling_time} s for stabilization
18+
b. Read actual field from gaussmeter
19+
c. Measure longitudinal voltage V_xx (4-wire)
20+
d. Calculate R_xx = V_xx / I
21+
e. Record field, R_xx
22+
6. **Reverse** sweep direction (field_stop -> field_start) and repeat
23+
7. **Optional**: Rotate sample for angular-dependent MR (AMR)
24+
8. **Optional**: Measure at multiple temperatures for T-dependent MR
25+
9. **Zero** magnetic field
26+
10. **Disable** source meter output
27+
11. **Save** data to {output_dir} as CSV
28+
29+
## Expected Output
30+
31+
- R vs H curve (up-sweep and down-sweep)
32+
- MR ratio = [R(H) - R(0)] / R(0) * 100 (%)
33+
- Coercive field H_c from resistance switching
34+
- For AMR: R vs angle plot, AMR ratio
35+
- For GMR/TMR: parallel and antiparallel resistance states, MR ratio

skills/rt.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
name: R-T Measurement
3+
description: Resistance vs temperature measurement for phase transitions and TCR characterization
4+
measurement_type: RT
5+
instruments: [source_meter, dmm, temperature_controller]
6+
version: "1.0"
7+
---
8+
9+
## Protocol
10+
11+
1. **Connect** instruments and verify identity via *IDN?
12+
2. **Configure** source meter: DC current mode, set compliance voltage to {compliance} V
13+
3. **Set** excitation current to {current} A (small enough to avoid self-heating)
14+
4. **Configure** temperature controller: set ramp rate to {ramp_rate} K/min
15+
5. **Ramp** temperature from {t_start} K to {t_stop} K:
16+
- At each temperature point (step = {t_step} K or continuous logging):
17+
a. Wait for temperature stabilization (within {t_tolerance} K for {dwell_time} s)
18+
b. Read actual temperature from sensor
19+
c. Measure voltage across sample (4-wire preferred)
20+
d. Calculate R = V / I
21+
e. Record timestamp, T, V, I, R
22+
6. **Optional**: Cool-down sweep for hysteresis detection
23+
7. **Disable** source output
24+
8. **Set** temperature controller to safe standby temperature
25+
9. **Save** data to {output_dir} as CSV
26+
27+
## Expected Output
28+
29+
- R vs T curve (warming and optional cooling)
30+
- TCR = (1/R) * dR/dT at specified temperature
31+
- Transition temperature T_c (if applicable, from dR/dT peak)
32+
- Residual resistance ratio RRR = R(300K) / R(base)
33+
- Activation energy E_a from Arrhenius fit (if semiconducting)

src/lab_harness/drivers/base.py

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,11 @@ class VisaDriver(InstrumentDriver):
9999
"""Base driver for VISA-compatible instruments (GPIB, USB, serial).
100100
101101
Provides connect/disconnect/query/write with automatic retry.
102+
Supports auto-reconnect on communication failure.
102103
"""
103104

105+
auto_reconnect: bool = True
106+
104107
def connect(self) -> None:
105108
import pyvisa
106109

@@ -126,15 +129,58 @@ def reset(self) -> None:
126129
self.write("*RST")
127130

128131
@retry(max_attempts=3, delay=0.5)
129-
def query(self, command: str) -> str:
130-
"""Send a query and return the response. Auto-retries on failure."""
132+
def _query_inner(self, command: str) -> str:
133+
"""Inner query with retry. Called by query()."""
131134
return self._instrument.query(command).strip()
132135

136+
def query(self, command: str) -> str:
137+
"""Send a query and return the response.
138+
139+
Auto-retries on failure. If retries are exhausted and
140+
auto_reconnect is enabled, attempts to reconnect and retry once.
141+
"""
142+
try:
143+
return self._query_inner(command)
144+
except Exception:
145+
if not self.auto_reconnect:
146+
raise
147+
logger.info("Retries exhausted for query, attempting reconnect to %s", self.resource)
148+
self.disconnect()
149+
self.connect()
150+
return self._instrument.query(command).strip()
151+
133152
@retry(max_attempts=3, delay=0.5)
134-
def write(self, command: str) -> None:
135-
"""Send a command. Auto-retries on failure."""
153+
def _write_inner(self, command: str) -> None:
154+
"""Inner write with retry. Called by write()."""
136155
self._instrument.write(command)
137156

157+
def write(self, command: str) -> None:
158+
"""Send a command.
159+
160+
Auto-retries on failure. If retries are exhausted and
161+
auto_reconnect is enabled, attempts to reconnect and retry once.
162+
"""
163+
try:
164+
self._write_inner(command)
165+
except Exception:
166+
if not self.auto_reconnect:
167+
raise
168+
logger.info("Retries exhausted for write, attempting reconnect to %s", self.resource)
169+
self.disconnect()
170+
self.connect()
171+
self._instrument.write(command)
172+
173+
def is_connected(self) -> bool:
174+
"""Test if instrument is still responding."""
175+
if not self._connected or not self._instrument:
176+
return False
177+
try:
178+
self._instrument.query("*IDN?")
179+
return True
180+
except Exception:
181+
self._connected = False
182+
return False
183+
138184
def read_float(self, command: str) -> float:
139185
"""Query and parse as float."""
140186
return float(self.query(command))

0 commit comments

Comments
 (0)