Skip to content

feat(http): expose probe device model via Devices accessor#185

Merged
PatrickRitchie merged 8 commits into
TrakHound:masterfrom
ottobolyos:feat/issue-176
Jun 3, 2026
Merged

feat(http): expose probe device model via Devices accessor#185
PatrickRitchie merged 8 commits into
TrakHound:masterfrom
ottobolyos:feat/issue-176

Conversation

@ottobolyos
Copy link
Copy Markdown
Contributor

@ottobolyos ottobolyos commented Jun 1, 2026

Summary

Closes #176. Adds a public Devices read-only accessor on MTConnectHttpClient that returns a snapshot of the post-probe device tree, and fixes DeviceReceived to fire for every parsed device. The previous implementation built an empty outputDevices list, populated _devices only, then iterated the empty list, so the event was raised zero times across the lifetime of every client. Together the two changes give consumers two clean paths to the post-processed device model — event-driven via DeviceReceived, snapshot-on-demand via Devices.

Beyond the headline fix, the branch lands the review-finding follow-ups noted by the maintainer on this PR:

  • The probe-time cache is now cleared at the start of every probe, so devices the agent has stopped advertising are evicted before the new probe loads.
  • DeviceReceived is fanned out per-subscriber through an invocation-list walk; a throwing handler can no longer abort the populate loop or starve later subscribers. Each fault is forwarded through InternalError, and a faulting InternalError handler is itself isolated so the fan-out survives.
  • The Devices snapshot is wrapped in ReadOnlyDictionary so a consumer cannot mutate the cache through a downcast.
  • The XML docs for both surfaces are tightened and the corresponding narrative pages (docs/configure/consumer.md, docs/examples/client-http.md) are extended to describe the new accessor and event.

Files changed

  • libraries/MTConnect.NET-HTTP/Clients/MTConnectHttpClient.cs — add public IReadOnlyDictionary<string, IDevice> Devices returning a lock-guarded snapshot; restructure ProcessProbeDocument to clear the cache and raise DeviceReceived inside the same loop that populates _devices, dropping the dead outputDevices list and the never-fired second loop; add RaiseDeviceReceived to walk the invocation list with per-delegate try/catch and route faults through InternalError.
  • tests/MTConnect.NET-HTTP-Tests/Clients/HttpClientDeviceModel.cs — new NUnit fixture pinning the Devices accessor and the DeviceReceived event end to end against the embedded AgentRunner HTTP server. Ten tests cover the positive surface (empty-before-probe, populated-after-probe, snapshot-not-live-view, fires-once-per-device, carries-wired-back-pointers, fires-on-subsequent-probes, evicts-stale-entries-on-reprobe) and the negative surface (throwing handler does not break cache population; throwing handler does not starve later subscribers; throwing InternalError does not break the DeviceReceived fan-out).
  • docs/examples/client-http.md — adds a "Subscribing to the wired device model" subsection showing a DeviceReceived handler walking the wired DataItems and a Devices snapshot read after the first probe completes.
  • docs/configure/consumer.md — names Devices and DeviceReceived alongside the existing CurrentReceived guidance, with a note on the InternalError fault routing.

Test plan

  • dotnet test tests/MTConnect.NET-HTTP-Tests --filter "FullyQualifiedName~HttpClientDeviceModel" — all ten tests pass.
  • dotnet build MTConnect.NET.sln — clean build on the rebased head.
  • dotnet test MTConnect.NET.sln --filter "Category!=E2E&Category!=RequiresDocker&Category!=XsdLoadStrict" — full HTTP-client suite continues to pass alongside the rest of the matrix.

ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Jun 1, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Jun 1, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Jun 2, 2026
The doc-comments commit on MTConnect-Compliance-Tests shifted the line
of the FixtureDirEnv read in CppAgentParityWorkflowTests.cs from 488 to
499. The generated page embeds source line numbers, so the change
desynced docs/reference/environment-variables.md and the in-sync test
in MTConnect.NET-Docs-Tests failed on every integration branch layered
on top of this PR (TrakHound#185, TrakHound#186).
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Jun 2, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Jun 2, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Jun 2, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Jun 2, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Jun 2, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Jun 2, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Jun 2, 2026
The doc-comments commit on MTConnect-Compliance-Tests shifted the line
of the FixtureDirEnv read in CppAgentParityWorkflowTests.cs from 488 to
499. The generated page embeds source line numbers, so the change
desynced docs/reference/environment-variables.md and the in-sync test
in MTConnect.NET-Docs-Tests failed on every integration branch layered
on top of this PR (TrakHound#185, TrakHound#186).
Adds a new HttpClientDeviceModel fixture that drives the full
MTConnectHttpClient against AgentRunner's embedded server, exercising
both surfaces issue TrakHound#176 calls out before they are fixed:

- the public Devices snapshot accessor (currently missing — compile-
  error RED), with separate tests for the pre-probe empty state, the
  post-probe populated state and snapshot-not-live-view semantics;
- the DeviceReceived event (currently raised zero times because
  ProcessProbeDocument iterates an empty list — behavioural RED), with
  one test pinning the per-device fire count on the first probe and
  another pinning the IDataItem Container / Device back-pointers the
  issue calls out as the wired model's selling point.

Both invariants will go green when the matching MTConnectHttpClient
changes land.
Adds a public read-only Devices snapshot accessor on MTConnectHttpClient
and fixes DeviceReceived to actually fire for every parsed device.

The client already cached the wired device model in a private _devices
dictionary populated by ProcessProbeDocument, but never exposed it; the
only public path to the post-probe model was ProbeReceived, which
carries the raw IDevicesResponseDocument before InstanceId stamping.
The new accessor returns an independent Dictionary copy under the same
internal lock the populate path uses, so callers can enumerate without
synchronising against the worker thread.

DeviceReceived previously built an empty outputDevices list, populated
only _devices in the first loop, then iterated the empty list — so the
event was raised zero times across the lifetime of every client. The
fix folds the invocation into the populate loop, keeping the cache and
the event in lockstep and dropping the dead second loop.

The new public Devices member shadows the MTConnect.Devices namespace
inside the class scope, so the two pre-existing in-class references to
that namespace are now fully qualified as MTConnect.Devices.* to keep
their resolution unambiguous.

The matching HttpClientDeviceModel test fixture, added in the previous
commit, transitions from RED to GREEN: the missing accessor was a
compile-error RED, the never-fired event was a behavioural RED, and
both invariants now hold end to end against the AgentRunner-backed
embedded HTTP server.

Closes TrakHound#176.
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Jun 2, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Jun 2, 2026
@ottobolyos ottobolyos marked this pull request as ready for review June 2, 2026 23:49
@ottobolyos ottobolyos marked this pull request as draft June 3, 2026 00:01
Tighten the Devices property XML doc to state that the returned
dictionary is a fresh allocation independent of the cache while the
IDevice values are shared references replaced wholesale on each probe.
Fix the British "synchronising" spelling to "synchronizing" to match
the surrounding American English register of the public XML docs. Add
remarks to DeviceReceived noting that handlers fire in document order
while the cache is still being populated. Add disambiguation comments
at the two MTConnect.Devices fully-qualified sites where the Devices
property shadows the namespace. Trim the five-line historical comment
in ProcessProbeDocument to a single forward-looking line.
Adds /// doc comments to the HttpClientDeviceModel test fixture and
each of its five [Test] methods so the project survives the campaign's
100% XML-doc coverage gate (CS1591 → error). The original block
comment on the class is promoted to a /// summary; each test gains a
one-line summary that pins the behaviour the test name expresses.

The gap surfaced when this PR's branch was union-merged into the
integration branch on top of feat/docs-full-coverage (TrakHound#182), which
turns CS1591 into a build error for every test project.
Lands four review findings on the issue-176 surface:

- Clear the _devices cache at the start of every probe inside the same
  lock block that clears _cachedComponents and _cachedDataItems so
  devices the agent no longer advertises are evicted before the new
  probe is loaded. The Devices accessor's XML doc claimed the snapshot
  reflected the most recent probe; without the clear, stale UUIDs
  persisted indefinitely.

- Wrap DeviceReceived?.Invoke in try/catch and route subscriber
  exceptions through InternalError. A throwing handler previously
  aborted the populate loop mid-stream, leaving the cache half-filled
  and suppressing ProbeReceived for that probe. The Worker loop's
  outer catch around the same fan-out already used this pattern; this
  change extends it to the per-device event.

- Wrap the snapshot in ReadOnlyDictionary so a consumer cannot mutate
  the cache through a downcast. The Devices XML doc promised a
  read-only contract; without the wrapper the runtime type stayed a
  plain Dictionary that downcasts could mutate.

- Tighten the spaced em-dashes in the DeviceReceived summary to the
  unspaced typesetter form so the rendered docfx page matches the
  surrounding prose convention.

Adds three NUnit tests pinning the new invariants and closing the
negative-coverage gaps the review surfaced:

- DeviceReceivedFiresOnEverySubsequentProbe restarts the client so a
  second probe round-trip runs through the same agent and asserts the
  event re-fires for every device on the second probe.
- DevicesAccessorReflectsLatestProbeWithStaleEntriesEvicted pins the
  eviction contract on reprobe.
- DeviceReceivedHandlerThrowingDoesNotBreakCachePopulation pins the
  per-handler isolation contract.
… pages

The Devices accessor and the now-firing DeviceReceived event landed on
this branch with XML doc comments only; the auto-generated docfx API
page picks those up, but the consumer-facing example and configure
pages did not mention either surface. This commit closes that gap so
every new public surface ships with matching narrative docs in the
same change set.

Adds:

- docs/examples/client-http.md gains a "Subscribing to the wired
  device model" subsection under the document-client example, showing
  a DeviceReceived handler walking the wired DataItems and a Devices
  snapshot read after the first probe completes. References issue
  TrakHound#176 for context.
- docs/configure/consumer.md gains a paragraph in the .NET-HTTP-client
  subsection naming Devices and DeviceReceived alongside the existing
  CurrentReceived guidance, plus a note that throwing handlers route
  through InternalError without breaking the cache fill.
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Jun 3, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Jun 3, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Jun 3, 2026
@ottobolyos ottobolyos marked this pull request as ready for review June 3, 2026 00:15
@PatrickRitchie PatrickRitchie moved this from In Progress to Reviewing in MTConnect.NET-Development Jun 3, 2026
@PatrickRitchie PatrickRitchie moved this from Reviewing to Ready to Merge in MTConnect.NET-Development Jun 3, 2026
@PatrickRitchie PatrickRitchie merged commit 089f535 into TrakHound:master Jun 3, 2026
7 checks passed
@github-project-automation github-project-automation Bot moved this from Ready to Merge to Done in MTConnect.NET-Development Jun 3, 2026
@ottobolyos ottobolyos deleted the feat/issue-176 branch June 3, 2026 05:20
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Jun 3, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

Expose the parsed probe device model cached by MTConnectHttpClient

2 participants