Skip to content

Commit 8f4ad50

Browse files
safer
1 parent 57fb893 commit 8f4ad50

4 files changed

Lines changed: 101 additions & 4 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,4 @@ pica/CONTRIBUTING.md
124124
pica/keysight/data_file_for_ref/
125125
.claude/settings.json
126126
pica/novocontrol/data_file_for_ref/
127-
gpib_lifeline_report_20260710_002203.txt
127+
gpib_lifeline_report_*.txt

docs/Novocontrol_GPIB_Runbook.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,35 @@ card). WinDETA already works there, so the card, driver, and cable are good.
5151
6. **Restart the GUI after any driver install** — the GPIB backend is
5252
chosen once per process.
5353

54+
## Safety — instrument and system
55+
56+
Instrument (the Alpha can never be harmed by the bring-up itself):
57+
58+
- The **only traffic any PICA tool sends automatically is `*IDN?`** — a
59+
read-only IEEE-488.2 identification query. Scans never write settings.
60+
- The whole bring-up needs only read-only queries: `*IDN?`, `INTTYP?`.
61+
- In the lifeline terminal: queries send freely; any state-changing write
62+
asks for confirmation; **DCV/DCE (DC bias) are blocked outright** — this
63+
mainframe has no bias hardware and `pica/novocontrol` never sends them.
64+
`:safe` parks the analyzer with the documented sequence
65+
`MBK, ACV=0, ZCONSPL=0`.
66+
- In the SCPI Console GUI, DCV/DCE/RSTH writes pop a confirmation dialog
67+
(default No) before anything reaches the bus.
68+
- `*RST` is safe on the Alpha: it parks the generator and **preserves** the
69+
stored calibration tables (per the Alpha manual and `pica/novocontrol`).
70+
- Never run WinDETA and Python at the same time — one program on the bus.
71+
72+
System (the PC that WinDETA depends on):
73+
74+
- Python-side fixes (`pip uninstall pygpib`, running the scripts) touch
75+
nothing outside Python — the lifeline only *reads* DLLs and files.
76+
- The one system-level change is the **driver install**. Before it:
77+
create a Windows restore point. Prefer adding the NI-488.2 compatibility
78+
component of the *same* ines driver version already installed over
79+
upgrading the whole driver, so the stack WinDETA uses stays untouched.
80+
- Acceptance test after any driver change: **start WinDETA and confirm it
81+
still talks to the Alpha**, then close it and continue with Python.
82+
5483
## If it still fails
5584

5685
Run `python pica/utils/GPIB_Lifeline_CLI.py --deep` (walks all of `C:\` for

pica/utils/GPIB_Lifeline_CLI.py

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@
2323
exists, this same file runs unmodified on a bare 32-bit
2424
Python (py -3-32) with no pip and no internet.
2525
26-
SAFETY: sends nothing but *IDN? probes and what you type.
26+
SAFETY: automatically sends nothing but *IDN? (a read-only
27+
IEEE-488.2 query). In the terminal, queries pass freely,
28+
state-changing writes require an explicit yes, DCV/DCE (DC
29+
bias -- no bias hardware on this mainframe) are blocked
30+
outright, and ':safe' parks the analyzer with the documented
31+
sequence MBK, ACV=0, ZCONSPL=0.
2732
2833
USAGE: python GPIB_Lifeline_CLI.py
2934
python GPIB_Lifeline_CLI.py --deep
@@ -75,6 +80,16 @@
7580

7681
DLL_BASENAMES = ("gpib-32.dll", "gpib32.dll", "ni4882.dll")
7782

83+
# --- Instrument safety -------------------------------------------------------
84+
# DC-bias writes are hard-blocked: the lab's Alpha-AN mainframe has no bias
85+
# hardware, and pica/novocontrol never transmits DCV/DCE either. Every other
86+
# state-changing write requires an explicit yes; queries ('?') pass freely.
87+
BLOCKED_WRITE_PREFIXES = ("DCV", "DCE")
88+
# Documented Novocontrol Alpha safe idle state, same order as
89+
# pica/novocontrol safe_state(): abort task, park generator, current input
90+
# to high impedance.
91+
SAFE_STATE_SEQUENCE = ("MBK", "ACV=0", "ZCONSPL=0")
92+
7893

7994
def iberr_text(code):
8095
if code is None:
@@ -399,8 +414,12 @@ def scan_bus(gpib, boards, probe_timeout_code):
399414
def repl(gpib, handle, resource_label):
400415
print(f"\n== Interactive terminal on {resource_label} ==")
401416
print(" Command ending in '?' -> write + read; otherwise write only.")
402-
print(" ':read' forces a read, ':quit' exits. (Novocontrol Alpha: try "
403-
"*IDN? then INTTYP?)")
417+
print(" SAFETY: queries send freely; state-changing writes ask for "
418+
"confirmation; DCV/DCE (DC bias) writes are blocked outright.")
419+
print(" ':safe' parks a Novocontrol Alpha (MBK, ACV=0, ZCONSPL=0), "
420+
"':read' forces a read, ':quit' exits.")
421+
print(" (Novocontrol Alpha bring-up: *IDN? then INTTYP? -- both are "
422+
"read-only.)")
404423
while True:
405424
try:
406425
command = input(f"{resource_label} > ").strip()
@@ -415,6 +434,31 @@ def repl(gpib, handle, resource_label):
415434
if command.lower() in (":quit", ":q", "quit", "exit"):
416435
break
417436

437+
if command.lower() == ":safe":
438+
for step in SAFE_STATE_SEQUENCE:
439+
sta = gpib.write(handle, step)
440+
verdict = ("ok" if not (sta & ERR)
441+
else f"FAILED {sta_text(sta)}; "
442+
f"{iberr_text(gpib.error_code())}")
443+
print(f" {step:<10} -> {verdict}")
444+
continue
445+
446+
if command.lower() != ":read" and not command.endswith("?"):
447+
if command.upper().startswith(BLOCKED_WRITE_PREFIXES):
448+
print(" BLOCKED: DCV/DCE set the DC bias; this mainframe "
449+
"has no bias hardware and PICA never sends them.")
450+
continue
451+
try:
452+
answer = input(
453+
f" '{command}' changes instrument state. Send? [y/N] ")
454+
except (EOFError, KeyboardInterrupt):
455+
print()
456+
break
457+
sys.stdout.file.write(answer + "\n")
458+
if answer.strip().lower() not in ("y", "yes"):
459+
print(" not sent")
460+
continue
461+
418462
started = time.perf_counter()
419463
if command.lower() == ":read":
420464
data, sta = gpib.read(handle)

pica/utils/SCPI_Console_GUI.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@
8888
# Commands that perturb instrument state -- highlighted in the library panel.
8989
SCPI_WARN_COMMANDS = {"*RST", "*SAV 0", "*RCL 0", "ABOR"}
9090

91+
# Writes that can drive hardware into a damaging state get a confirmation
92+
# dialog before anything reaches the bus. On a Novocontrol Alpha, DCV/DCE
93+
# set the DC bias (the lab mainframe has no bias hardware and PICA never
94+
# sends them) and RSTH is a hard reset.
95+
RISKY_WRITE_PREFIXES = ("DCV", "DCE", "RSTH")
96+
9197
# Label separator used to render "address -> IDN" in the instrument combobox.
9298
ADDR_SEP = " -> "
9399

@@ -893,7 +899,25 @@ def read_only(self):
893899
return
894900
self._dispatch('read', None)
895901

902+
def _confirm_if_risky(self, command):
903+
"""True when the command may be sent. Pure queries pass freely;
904+
bias / hard-reset writes need an explicit yes."""
905+
if command.endswith('?'):
906+
return True
907+
if not command.upper().lstrip().startswith(RISKY_WRITE_PREFIXES):
908+
return True
909+
return messagebox.askyesno(
910+
"Confirm instrument write",
911+
f"'{command}' can change the instrument's output or bias "
912+
"state.\n\nOn a Novocontrol Alpha, DCV/DCE drive the DC bias "
913+
"(this mainframe has no bias hardware) and RSTH is a hard "
914+
"reset.\n\nSend it anyway?",
915+
icon='warning', default='no')
916+
896917
def _dispatch(self, mode, command):
918+
if command is not None and not self._confirm_if_risky(command):
919+
self.log("info", f"Cancelled: '{command}' was not sent.")
920+
return
897921
if command is not None:
898922
self._remember(command)
899923
self.command_entry.delete(0, tk.END)

0 commit comments

Comments
 (0)