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
feat: eliminate the DaqifiStreamingDevice downcast — fix factory return types and promote Channels/Metadata onto the interfaces #333
The first thing every consumer hits is a cast. All eight public DaqifiDeviceFactory.Connect* methods are typed Task<DaqifiDevice> / DaqifiDevice, but the constructed instance is always a DaqifiStreamingDevice (src/Daqifi.Core/Device/DaqifiDeviceFactory.cs). The README has to explain the downcast twice (lines 38-40 and 170-171), docs/DEVICE_INTERFACES.md pattern-matches if (device is IStreamingDevice ...) in four separate sections, and the in-repo MCP consumer wraps it in RequireStreaming/RequireSdCard helpers (src/Daqifi.Mcp/DaqifiAgent.cs:484-502).
Worse, the interfaces can't stand alone: every enable/disable/DIO/PWM method on IStreamingDevice documents that the channel "must belong to this device's Channels collection" (src/Daqifi.Core/Device/IStreamingDevice.cs), yet neither IDevice nor IStreamingDevice exposes Channels, GetChannelsSnapshot(), Metadata, or Capabilities — those live only on the concrete DaqifiDevice. A consumer holding the interface literally cannot obtain a channel to pass into the interface's own methods (DEVICE_INTERFACES.md admits this at line ~397).
Proposed fix (simplest path):
Change the factory return types to DaqifiStreamingDevice (source-compatible narrowing for existing callers; note the binary break in release notes).
Promote GetChannelsSnapshot(), Metadata, and ChannelsPopulated onto IStreamingDevice (or IDevice where they belong).
Acceptance Criteria
DaqifiDeviceFactory.Connect* returns the streaming type — no cast needed for streaming, SD, network, or diagnostics APIs
IStreamingDevice (and/or IDevice) exposes channel access and metadata so the interface is usable in isolation
README and DEVICE_INTERFACES.md updated — the downcast caveats deleted
MCP server's RequireStreaming/RequireSdCard helpers simplified or removed
Value
Removes the single biggest piece of friction in the public API, makes the interfaces mockable/usable on their own, and directly supports the daqifi-desktop thin-wrapper migration.
Adding a data point for whatever type surface this settles on: #381's DaqifiDeviceRegistry is typed on DaqifiDevice, so it inherits this decision.
That typing is fine for Core's own consumers (the in-repo MCP server holds DaqifiDevice directly and ported cleanly), but it blocks the reference consumer this feature was built from. daqifi-desktop's AbstractStreamingDevice doesn't extend a Core device — it wraps one (AbstractStreamingDevice.cs:139, protected CoreStreamingDevice? CoreDevice) — and its ConnectionManager.ConnectedDevices is a List<IStreamingDevice> of desktop's interface. So desktop cannot put its devices in the registry; registering the inner Core device instead would leave it maintaining a side-map back to the wrapper, which is the bookkeeping the registry exists to remove. Tracked as daqifi-desktop#752 stage 2.
Two ways out, and this ticket is where the choice belongs:
Desktop's device types collapse onto Core's — the thin-wrapper end state. Then the registry works as-is and no Core change is needed.
Type the registry on an interface (IDevice, or whatever this ticket promotes Channels/Metadata onto) so a consumer wrapping Core devices can register its own type.
Option 1 is the better end state and makes option 2 unnecessary, so this is a note rather than a request — worth knowing that the registry is now a second consumer of the same decision, not an argument for generics on its own.
Verification pass against current main (2026-08-06) — still fully live, and the gap is wider than the body describes:
The factory now has 11 public construction/connect entry points returning the concrete DaqifiDevice/DaqifiStreamingDevice types (it has grown since filing).
The promotion list is much longer than GetChannelsSnapshot()/Metadata/ChannelsPopulated. On DaqifiDevice but on no interface: Metadata, Channels/GetChannelsSnapshot(), IsFirmwareVersionSupported, Supports/EnsureSupported(DeviceFeature), TimestampFrequency, State, PreserveActiveStream, the StatusMessageReceived/StreamMessageReceived/ChannelsPopulated/SendFailed events, RunExclusiveAsync, DrainErrorQueueAsync, ReadCapabilityDocumentAsync, the entire reconnect surface (ReconnectAttempt/Reconnected/ReconnectFailed/IsReconnecting/CancelReconnect), and InitializeAsync.
On DaqifiStreamingDevice but not on IStreamingDevice: StreamSamplesAsync — the documented recommended consumption API is interface-invisible — plus the StreamFrameDiscarded/GapDetected events, DiscardedStreamFrameCount/DecodeFailureCount, IsUsbConnection, the live-sample buffer knobs, SetFriendlyNameAsync, and the PWM frequency constants (which IStreamingDevice's own xml-doc <see cref>s on the concrete class).
Interface-shape problems found in the same pass — the all-sync IStreamingDevice, the ConnectAsync/DisconnectAsync DIM shims, and IDevice lacking IDisposable/IAsyncDisposable — are split out to #460 so this ticket stays scoped to member promotion + factory return types. Suggest treating the inventory above as the working checklist for the second acceptance criterion, and sequencing this before further #344 extraction so collaborators get carved along interface seams.
Activity
tylerkron commentedon Jul 22, 2026
Adding a data point for whatever type surface this settles on: #381's
DaqifiDeviceRegistryis typed onDaqifiDevice, so it inherits this decision.That typing is fine for Core's own consumers (the in-repo MCP server holds
DaqifiDevicedirectly and ported cleanly), but it blocks the reference consumer this feature was built from.daqifi-desktop'sAbstractStreamingDevicedoesn't extend a Core device — it wraps one (AbstractStreamingDevice.cs:139,protected CoreStreamingDevice? CoreDevice) — and itsConnectionManager.ConnectedDevicesis aList<IStreamingDevice>of desktop's interface. So desktop cannot put its devices in the registry; registering the inner Core device instead would leave it maintaining a side-map back to the wrapper, which is the bookkeeping the registry exists to remove. Tracked as daqifi-desktop#752 stage 2.Two ways out, and this ticket is where the choice belongs:
IDevice, or whatever this ticket promotesChannels/Metadataonto) so a consumer wrapping Core devices can register its own type.Option 1 is the better end state and makes option 2 unnecessary, so this is a note rather than a request — worth knowing that the registry is now a second consumer of the same decision, not an argument for generics on its own.
tylerkron commentedon Aug 6, 2026
Verification pass against current main (2026-08-06) — still fully live, and the gap is wider than the body describes:
DaqifiDevice/DaqifiStreamingDevicetypes (it has grown since filing).GetChannelsSnapshot()/Metadata/ChannelsPopulated. OnDaqifiDevicebut on no interface:Metadata,Channels/GetChannelsSnapshot(),IsFirmwareVersionSupported,Supports/EnsureSupported(DeviceFeature),TimestampFrequency,State,PreserveActiveStream, theStatusMessageReceived/StreamMessageReceived/ChannelsPopulated/SendFailedevents,RunExclusiveAsync,DrainErrorQueueAsync,ReadCapabilityDocumentAsync, the entire reconnect surface (ReconnectAttempt/Reconnected/ReconnectFailed/IsReconnecting/CancelReconnect), andInitializeAsync.DaqifiStreamingDevicebut not onIStreamingDevice:StreamSamplesAsync— the documented recommended consumption API is interface-invisible — plus theStreamFrameDiscarded/GapDetectedevents,DiscardedStreamFrameCount/DecodeFailureCount,IsUsbConnection, the live-sample buffer knobs,SetFriendlyNameAsync, and the PWM frequency constants (whichIStreamingDevice's own xml-doc<see cref>s on the concrete class).(DaqifiDevice device, IStreamingDevice streaming)tuple (src/Daqifi.Mcp/DaqifiAgent.cs:468) because neither type alone is sufficient. test(mcp): Daqifi.Mcp is 1,185 lines with one 165-line test file — contract tests for the agent tool layer #465 (MCP contract tests) gets simpler once this lands.Interface-shape problems found in the same pass — the all-sync
IStreamingDevice, theConnectAsync/DisconnectAsyncDIM shims, andIDevicelackingIDisposable/IAsyncDisposable— are split out to #460 so this ticket stays scoped to member promotion + factory return types. Suggest treating the inventory above as the working checklist for the second acceptance criterion, and sequencing this before further #344 extraction so collaborators get carved along interface seams.