diff --git a/README.md b/README.md index adf0e6c..74fa879 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,8 @@ whether the handshake may answer status 2 ("code ignored"; one reference accepts source accepts it); the first frame id; and the maximum frame size. Two further things are worth a look on a board even though the mock's header does not flag them: the calibration memory layout and its coefficient type, and the status word's bits, in particular whether the "scan running" bit stays set while the -board waits for a trigger (the source restarts a scan that reads as stopped when auto-restart is on). The DIFF input +board waits for a trigger (with auto-restart on, the source restarts a scan that reads as stopped only when no +trigger is configured, so as not to reset the wait for the edge; this is unverified on hardware). The DIFF input numbering (channel + 8) and the filler byte after scan start are agreed by both references. The scan overrun count is approximate: the board does not say how many scans it dropped, so the frame after a restart reports one lost. Errors (no device, a rejected connection code, a timeout, the scan socket closing) are written to stderr as diff --git a/docs/claude/architecture.md b/docs/claude/architecture.md index 2106f7c..297f01b 100644 --- a/docs/claude/architecture.md +++ b/docs/claude/architecture.md @@ -48,7 +48,7 @@ What feeds `Acquisition` is a `DataSource`, one of the registered *kinds*. `e1608_source` is the `DataSource`; `e1608_kind` and `e1608_panels` are the UI. A change to the wire never reaches the UI files. - The E-1608 source runs **everything inside `poll()`** on the producer thread: posted commands, the - connect state machine (Down, Scanning, Halted), the scan read, the 500 ms status check, the digital + connect state machine (Down, Scanning, Halted), the scan read, the 500 ms status check (its not-running restart is skipped when a hardware trigger is configured), the digital input/counter poll and the 20 ms waveform tick. There is no second thread. A connect attempt (discovery, handshake, calibration read, scan start) can block `poll()` for as long as the command timeout allows each step; that delays only the command queue. Every exception is caught inside `poll()`: an escape would end diff --git a/src/source/e1608/e1608_source.cpp b/src/source/e1608/e1608_source.cpp index 2a42cdd..81f5565 100644 --- a/src/source/e1608/e1608_source.cpp +++ b/src/source/e1608/e1608_source.cpp @@ -186,6 +186,7 @@ void E1608Source::open(SourceSink& sink) { void E1608Source::close() { if (state_ == State::Scanning) dev_.scan_stop(); + scan_started_ = false; link_.close(); connected_.store(false, std::memory_order_relaxed); } @@ -212,6 +213,15 @@ void E1608Source::note(const std::string& msg) { } void E1608Source::fail(const std::string& msg, bool fatal) { + if (scan_started_) { + // The scan may have started before a later connect step failed: tell the board to stop, so it + // is not left scanning with nobody reading. Best effort; it must never throw out of here. + try { + dev_.scan_stop(); + } catch (...) { + } + scan_started_ = false; + } link_.close(); const bool was_up = connected_.exchange(false, std::memory_order_relaxed); note(msg); @@ -283,6 +293,7 @@ void E1608Source::connect_and_start() { } link_.connect(host, cfg_.port, cfg_.code, cfg_.timeout_ms); + scan_started_ = false; cal_ = Calibration{}; if (cfg_.calibrate && !dev_.read_calibration(cal_)) note("calibration memory refused; using nominal values"); @@ -419,13 +430,16 @@ void E1608Source::check_status() { sink_->on_event(SourceEvent::Error, "scan overrun; restarting"); restart_scan(); pending_lost_ = 1; - } else if (!(st & 2) && cfg_.auto_restart) { + } else if (!(st & 2) && cfg_.auto_restart && cfg_.trigger == Trigger::None) { + // With a hardware trigger the board may report "not running" while it waits for the edge; + // restarting then would reset the wait forever, so only the overrun bit restarts the scan. restart_scan(); } } void E1608Source::restart_scan() { dev_.scan_stop(); + scan_started_ = false; pending_.clear(); for (auto& r : ready_) r.clear(); start_scan(); @@ -437,6 +451,7 @@ void E1608Source::start_scan() { throw LinkError(LinkError::Kind::Command, "the aggregate rate exceeds 250 kS/s"); try { dev_.scan_start(queue_, cfg_.aggregate(), cfg_.ext_clock, cfg_.trigger); + scan_started_ = true; } catch (...) { // The board may have accepted the start (0x11) before a later step failed (the filler byte): // tell it to stop, so it is not left scanning with nobody reading. @@ -444,6 +459,7 @@ void E1608Source::start_scan() { dev_.scan_stop(); } catch (...) { } + scan_started_ = false; throw; } } diff --git a/src/source/e1608/e1608_source.hpp b/src/source/e1608/e1608_source.hpp index a25ab60..81bb1e7 100644 --- a/src/source/e1608/e1608_source.hpp +++ b/src/source/e1608/e1608_source.hpp @@ -124,6 +124,7 @@ class E1608Source final : public DataSource { E1608Settings cfg_; SourceSink* sink_ = nullptr; State state_ = State::Down; + bool scan_started_ = false; ///< a scan start succeeded and no stop has followed Link link_; Device dev_{link_}; Calibration cal_; diff --git a/tools/mock_e1608.py b/tools/mock_e1608.py index d63a340..c2195f9 100755 --- a/tools/mock_e1608.py +++ b/tools/mock_e1608.py @@ -327,7 +327,9 @@ class Board: self.first_conn = time.monotonic() close_quietly(old) if first and self.args.drop_after > 0: - threading.Timer(self.args.drop_after, self.drop).start() + timer = threading.Timer(self.args.drop_after, self.drop) + timer.daemon = True + timer.start() try: while True: b = conn.recv(1)