Repository navigation
feat(discovery): AllTransportsDeviceFinder + DiscoverAndConnectAsync (closes #338) - #354
Conversation
|
/agentic_review |
…loses #338) "Find any DAQiFi on WiFi or USB" becomes one call instead of the manual instantiate-both / run-both / concatenate / dedupe dance every consumer repeats. - AllTransportsDeviceFinder : IDeviceFinder fans DiscoverAsync out across N transport finders concurrently and returns one deduplicated set. A failing transport (e.g. WiFi with no network) is logged and skipped so the others still return. Dedup reuses ContinuousDeviceFinder.DefaultIdentity (#245); a custom identitySelector can collapse the same unit across transports. Being an IDeviceFinder, wrapping it in ContinuousDeviceFinder yields deduplicated continuous discovery for free. - DaqifiDeviceFactory.DiscoverAndConnectAsync(filter?, timeout?, options?, finder?) — the common "find one and connect" case in a single call. - Docs: the per-transport merge is now the advanced path; the aggregator is the default. - 18 unit tests (fan-out, dedup, per-transport vs custom identity, transport-failure resilience, events, cancellation, dispose ownership, factory selection paths). Bench-validated on Nq1 FW 3.7.2: aggregated discovery found the device over serial (WiFi skipped gracefully); DiscoverAndConnectAsync connected and disconnected cleanly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
81683ab to
1fc2ff0
Compare
|
/agentic_review |
PR Summary by Qodofeat(discovery): add AllTransportsDeviceFinder for multi-transport discovery
AI Description
Diagram
High-Level Assessment
Files changed (2)
|
Code Review by Qodo
Context used 1.
|
…meout non-throwing Addresses two Qodo findings on #354: - Isolate DeviceDiscovered/DiscoveryCompleted subscriber exceptions (RaiseIsolated) so a throwing consumer callback can't abort the dedup loop or skip DiscoveryCompleted. - DiscoverAsync(TimeSpan) now delegates to each finder's own timeout overload instead of routing through a cancellation token, so a timeout is a normal end-of-pass (returns partial results, fires DiscoveryCompleted) rather than propagating OperationCanceledException — matching WiFiDeviceFinder/SerialDeviceFinder. Adds regression tests for both. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Both fixed in dfba82a:
Full suite 1655 pass / 0 fail (net9+net10). |
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit dfba82a |
…ssage Addresses two Qodo findings on #354: - Add SafeTrace (Trace.WriteLine wrapped in try/catch) and route all three of the class's log sites through it, so a throwing TraceListener can't escape the event-isolation / transport-failure catch blocks and break discovery. - Reword the TimeSpan-overload failure log so it no longer implies "timeout" for what may be any transport failure (socket error, disposed resource, etc.). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Both fixed in the latest commit:
Full suite 1655 pass / 0 fail (net9+net10). |
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit 66cd9d9 |
|
Latest Qodo pass clean (0 unresolved threads, updated to 66cd9d9), CI green, full suite 1655 pass, bench-validated on Nq1. Bench-verified, ready for review. Not merging — leaving for you. |
…the reader loop Addresses Qodo round 3 on #415. ReportStreamFault invoked ITransportHealthSink and ErrorOccurred without isolation, so a throwing callback escaped the reader thread — process-fatal, the same shape as the escaping-catch defect fixed last round, one layer up. Concentrating the four fault sites into one helper made a single unguarded callback affect all of them at once. The route is two hops, and the crash trace confirms it exactly: the handler throws on the fault path, the loop's outer catch reports that failure by calling the same handler, and the second throw is inside a catch block with nothing above it. Verified against pre-fix code — the test host dies with the unhandled exception surfacing from ProcessMessages' outer catch. Every callback out of the loop is now isolated via SafeReportIoFault / SafeReportIoSuccess / SafeRaiseError: the two ReportStreamFault callbacks, the per-read success report, the outer catch's error raise, and the error raise in ProcessMessageBuffer's dispatch handler. Plain methods rather than a lambda helper so the once-per-read success path allocates no closure. Swallowed rather than logged, matching the convention already used for a throwing MessageReceived subscriber (#180) and mirrored across RaiseClassifiedEvent (#323), AllTransportsDeviceFinder (#354), DeviceFinderBase and RaiseGapDetected. Two regression tests assert the reader keeps consuming — a real message delivered after the throwing phase — not merely that nothing surfaced. Both verified failing pre-fix: the subscriber case crashes the host, the health-sink case silently stops delivering messages. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Summary
Turns the README's "find any DAQiFi on WiFi or USB" promise into a real one-call API (issue #338), deleting the instantiate-both / run-both / concatenate / dedupe fan-out that the MCP server, desktop, and example CLI otherwise each re-implement.
Changes
AllTransportsDeviceFinder : IDeviceFinder— fansDiscoverAsyncout across N transport finders (WiFi + serial today, mDNS/Add MDnsDeviceFinder for reliable discovery on home/multi-AP networks (broadcast UDP is fragile) #183 later) concurrently and returns one deduplicated set. A transport that throws (e.g. WiFi discovery with no network) is logged and skipped so the others still return. Dedup reusesContinuousDeviceFinder.DefaultIdentity(feat: Continuous/stateful discovery (live deduplicated device set + DeviceLost) #245) — the same unit on two transports stays two connection options; pass a customidentitySelector(e.g. by serial) to collapse them. Because it is anIDeviceFinder, wrapping it inContinuousDeviceFindergives deduplicated continuous discovery for free (covers the one-shot + continuous acceptance criterion).DaqifiDeviceFactory.DiscoverAndConnectAsync(filter?, timeout?, options?, finder?, ct?)— the common "find one and connect" case in a single call; thin wrapper over the aggregator + existingConnectFromDeviceInfoAsync.DEVICE_INTERFACES.md) — new "Discover Across All Transports (Recommended)" section; the per-transport manual merge is reframed as the advanced path.Testing
dotnet test— 1653 passed / 0 failed / 2 skipped (net9.0 + net10.0).DeviceDiscovered/DiscoveryCompletedevents, cancellation propagation,TimeSpanoverload, dispose-ownership (caller-supplied finders not disposed), post-dispose throws, constructor validation, andDiscoverAndConnectAsyncselection paths (no-device / filter-no-match / pre-cancelled throw).AllTransportsDeviceFinder.CreateDefault().DiscoverAsync(3s)found the device over serial while WiFi returned empty without error;DiscoverAndConnectAsync(filter: serial)connected (IsConnected=True) and disconnected cleanly.Acceptance criteria (#338)
ContinuousDeviceFinder)DiscoverAndConnectAsyncwith optional filter/timeout returns a connected deviceNot merging — for review.
🤖 Generated with Claude Code