You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Capture the 54845A's screen, and stop a failed binary read blocking the server - #169
Fixes screen capture on the HP 54845A Infiniium, and stops a failed binary read from blocking the server.
The capture
Wrong command: the profile sent :WAVeform:DATA?, which returns the waveform as ASCII. It now sends :DISPlay:DATA? GIF,SCReen (Programmer's Quick Reference Guide 54810-97065, p. 2-20).
Seeded copies: the instrument database is copied to the user's folder on first run, so the bundled file lists the old blocks under supersedes. Otherwise existing installs would keep the wrong command.
Headers on: with :SYSTem:HEADer ON the scope sends :DISP:DATA before the block. The block reader now skips one such word, so a capture neither depends on that setting nor changes it. This also covers visa_query_binary and other models. Text that only looks like a name is still reported as not being a block.
Error queue:ErrorQueue.IsNoError treated :SYST:ERR 0,"No error" as an error, for the same reason.
Failed binary reads
A rejected query held the bus for 135 s on a 60 s timeout, with every later call queued behind it. NI-488 timeouts snap up to a step and overrun: over a GPIB-ENET/1000, 3000 ms waits 4.2 s and 5000 ms waits 16.8 s.
The first read waits a short time for the first byte (firstReplyTimeoutMs, 3000 for the 54845A). It's one read, limited to a timeout step whose measured wait fits in half the time for reading, so a driver's step can't carry it past the limit. It must not be split into short reads: that was tried after review, and on the 54845A every capture failed and the scope stopped answering, serial poll and device clear included, until it was power-cycled.
first_reply_timeout_ms asked for by name isn't capped by timeout_ms.
After that, reads are 1 s slices, and only silence counts as a stall.
The whole call has a budget: 25 s by default and 55 s at most, matching gpib_batch.
On failure the server drains the reply, sends a device clear if the model allows it, and reports the error queue. Device clear is opt-in per model (deviceClearSafe) because it presets the HP 8560 series.
Recovery stops reading the error queue when the call's time is up, and says so. The drain treats a read that timed out with data as a slow transfer, so a reply that stops part way still gets the device clear, and keeps only the first bytes of what it discards. A reply that started and went silent without END isn't counted as finished, and an instrument that may still be sending isn't asked for its errors.
Not in this PR: the silence allowance after the first byte is counted in whole 1 s slices, so timeout_ms = 1500 stalls after 1 s. Fixing it changes mid-transfer timing, so it waits for a hardware test.
ErrorQueue moved from Tools to Instruments so InstrumentManager can use it.
Tested on a real 54845A (A.04.50, GPIB-ENET/1000, HP 37204A extender)
Headers off: 11 of 11 captures at about 2 s, valid GIFs.
Headers on, set for the test and put back after: the raw reply starts :DISP:DATA , and instrument_capture_screen, visa_query_binary and instrument_identify all work.
A rejected command fails in about 4.25 s and names the error (-224), and the next call takes 12 ms.
After the review fixes (power-cycled first): three captures at about 2 s, a rejected dump in 4.3 s with -108 and -420 read back, then *IDN? in 12 ms, headers still off and the error queue empty. The same again after the second round of fixes.
:SYSTem:SETup? is byte-identical before and after a device clear.
With headers on, the error queue's reply was read directly but not through the failure path, which is covered by unit tests. Other tools that parse values, such as gpib_batch, weren't tried with headers on.
New since 1.3.0: VS Code's Copilot now loads gpib_batch - its 'steps'
schema says what a step is, and every tool's arrays now say what their
items are (#164). The installer's sandbox tests also close themselves
when they finish (#163). Local builds now report 1.3.1; the release
workflow stamps the tag's version as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he server
The 54845A's profile sent :WAVeform:DATA?, which isn't a screen dump: it
returns the waveform as ASCII, hence "Header received: '-'". It now sends
:DISPlay:DATA? GIF,SCReen (Programmer's Quick Reference Guide p. 2-20).
Arguments are positional and there is no palette one, so the GIF,SCR,COL
tried in Claude Desktop was rejected with -224 and no reply ever came. The
scope's user copies are seeded on first run and would keep the old blocks,
so the bundled file lists them under supersedes.
That rejected query held the bus for 135 s on a 60 s timeout, with every
later call queued behind it. NI-488 timeouts snap up to a step and overrun
it: over a GPIB-ENET/1000, 3000 ms waits 4.2 s and 5000 ms waits 16.8 s.
So QueryBinary now waits a short while for the first byte, then reads in
1 s slices, the only step that is exact, and counts only silence as a
stall. A whole call stops at a limit (25 s by default, 55 at most, as for
gpib_batch; Desktop cuts calls off at about 30 s, or 60 s over its link).
After a failure, under the same lock, it discards the rest of the reply,
device-clears a model marked deviceClearSafe, and reads the error queue
into the message, so the instrument's own error comes back in seconds.
Nothing is sent ahead of the read to check a reply is coming: another
command would interrupt the query (-410), and MAV differs by instrument.
Device clear is per model because it presets the 8560 series.
Tested on the 54845A at GPIB0::7::INSTR over stdio: 11 of 11 captures gave
a PNG in about 2 s, and the bad command failed in 4.3 s with -224, with
the next call answered 12 ms later. :SYSTem:SETup? was byte-identical
before and after the device clears.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With :SYSTem:HEADer ON the 54845A puts the reply's name in front of the block
(":DISP:DATA #515766..."), so the capture failed on the first byte. The block
reader now skips that one word, which leaves the scope's setting alone and covers
visa_query_binary and every other model too. Text that only looks like a name is
still reported as not being a block.
The error queue had the same gap: ":SYST:ERR 0,\"No error\"" read as an error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Blocking reads and recovery can exceed the call budget, and partial-read timeouts can skip device clear while unread reply data remains, contaminating subsequent queries.
This PR corrects HP 54845A screen capture and adds timeout and recovery controls to the shared binary-read path to reduce server blocking after failed queries.
Changes:
Corrects the 54845A GIF capture command and migrates previously seeded profiles.
Handles header-prefixed replies and adds binary-read budgets, model-aware recovery, and error reporting.
Adds regression tests, documents the new controls, and bumps the version to 1.3.1.
File
Description
tests/GpibMcp.Tests/InstrumentManagerTests.cs
Tests binary-read timeouts, slow transfers, and recovery.
tests/GpibMcp.Tests/FakeInstrumentManager.cs
Records binary-read options for assertions.
tests/GpibMcp.Tests/DatabaseToolsTests.cs
Tests header-prefixed error-queue replies.
tests/GpibMcp.Tests/BinaryToolsTests.cs
Tests option propagation, corrected profiles, and seeded-copy migration.
tests/GpibMcp.Tests/BinaryBlockTests.cs
Tests reply-name prefixes and malformed input.
src/GpibMcp.Core/Tools/InstrumentTools.cs
Supplies model context to binary-tool registration.
src/GpibMcp.Core/Tools/CaptureTools.cs
Passes capture limits and recovery options to binary reads.
src/GpibMcp.Core/Tools/BinaryTools.cs
Exposes timing controls and resolves model-specific options.
From the review of #169:
- The wait for the first byte stays one read, but is limited to about half
the time for reading, since a driver rounds it up a step (20 s waits about
42 s over a GPIB-ENET/1000). Splitting it into short reads failed every
capture on the 54845A and left the scope unresponsive until power-cycled.
- first_reply_timeout_ms asked for by name is no longer capped by timeout_ms.
- Recovery stops reading the error queue once the call's time is up, and
says so.
- The drain treats a read that timed out with data as a slow transfer, so a
reply that stops part way still gets the device clear, and it keeps only
the first bytes of what it discards.
Tested on the 54845A: three captures (about 2 s each), a rejected dump
(4.3 s, -108 and -420 read back), then *IDN? in 12 ms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Unresolved critical timeout handling can exceed whole-call budgets and hold _gate during recovery, while additional findings identify incomplete-reply handling and reads issued beyond the remaining timeout allowance.
The stall timeout is rounded down to whole seconds here, while every later read still requests 1000 ms. Consequently, timeout_ms=1500 fails after only one silent second, and timeout_ms=500 requests twice the specified wait. Track the remaining inactivity allowance and limit each slice to it, rather than counting a floored number of fixed-size slices.
Require END before treating a started reply as complete
An empty drain does not prove that the reply ended: the parser may already have consumed an invalid byte such as - without END. If the next read times out empty, this returns true and skips an allowed device clear, leaving the transfer unfinished. Carry the original read's EndOfMessage state into recovery and require END before declaring a started reply finished.
…ply finished
From Copilot's second review of #169:
- The single first read is limited to a timeout step whose measured wait
fits in half the time for reading. Halving the request wasn't enough:
5000 ms on a 15 s budget waits about 16.8 s over a GPIB-ENET/1000.
- A reply that started and went silent without END isn't treated as
finished, so it still gets the device clear.
- An instrument that may still be sending isn't asked for its errors: the
query would only interrupt the reply.
The 54845A's default capture still asks for 3000 ms. Tested on it: three
captures at about 2 s, a rejected dump in 4.3 s with -108 and -420 read
back, then *IDN? in 13 ms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes screen capture on the HP 54845A Infiniium, and stops a failed binary read from blocking the server.
The capture
:WAVeform:DATA?, which returns the waveform as ASCII. It now sends:DISPlay:DATA? GIF,SCReen(Programmer's Quick Reference Guide 54810-97065, p. 2-20).supersedes. Otherwise existing installs would keep the wrong command.:SYSTem:HEADer ONthe scope sends:DISP:DATAbefore the block. The block reader now skips one such word, so a capture neither depends on that setting nor changes it. This also coversvisa_query_binaryand other models. Text that only looks like a name is still reported as not being a block.ErrorQueue.IsNoErrortreated:SYST:ERR 0,"No error"as an error, for the same reason.Failed binary reads
A rejected query held the bus for 135 s on a 60 s timeout, with every later call queued behind it. NI-488 timeouts snap up to a step and overrun: over a GPIB-ENET/1000, 3000 ms waits 4.2 s and 5000 ms waits 16.8 s.
firstReplyTimeoutMs, 3000 for the 54845A). It's one read, limited to a timeout step whose measured wait fits in half the time for reading, so a driver's step can't carry it past the limit. It must not be split into short reads: that was tried after review, and on the 54845A every capture failed and the scope stopped answering, serial poll and device clear included, until it was power-cycled.first_reply_timeout_msasked for by name isn't capped bytimeout_ms.gpib_batch.deviceClearSafe) because it presets the HP 8560 series.timeout_ms = 1500stalls after 1 s. Fixing it changes mid-transfer timing, so it waits for a hardware test.ErrorQueuemoved from Tools to Instruments soInstrumentManagercan use it.Tested on a real 54845A (A.04.50, GPIB-ENET/1000, HP 37204A extender)
:DISP:DATA, andinstrument_capture_screen,visa_query_binaryandinstrument_identifyall work.-224), and the next call takes 12 ms.-108and-420read back, then*IDN?in 12 ms, headers still off and the error queue empty. The same again after the second round of fixes.:SYSTem:SETup?is byte-identical before and after a device clear.gpib_batch, weren't tried with headers on.🤖 Generated with Claude Code