Skip to content

fix(channels): a device that reports channels already enabled leaves Start Logging permanently disabled #165

Closed

Description

@tylerkron

Parity gap #1 from the 2026-09-01 audit of upstream b469ca1b..36995fed. Full ranked list: #141.

What a user can do in the WPF app that they cannot do here

In daqifi-desktop, connecting a device leaves every channel tile inactive, so the user picks channels and presses Start Logging.

Here, on a device whose firmware reports a non-empty enabled-channel mask, every analog tile renders active the moment the first status message lands, "Select All" does nothing, no channel is ever subscribed, and Start Logging stays disabled with no way for the user to enable it. The app cannot log data at all until the device is put back into a state where it reports nothing enabled.

Upstream hit exactly this (their issue #811) and fixed it in d801c348.

Why it happens here

Four things line up, all confirmed by hand:

file what it does
1 Daqifi.Avalonia/Daqifi.Desktop/Device/AbstractStreamingDevice.cs (Connect, ~:412) calls OnCoreDeviceInitialized() and returns — no adoption step
2 Daqifi.Avalonia/Daqifi.Desktop/Channel/AnalogChannel.cs:86 IsActive => _coreChannel.IsEnabled — reads straight through to Core
3 Daqifi.Avalonia/Daqifi.Desktop/ViewModels/ChannelsPaneViewModel.cs (SelectAll) tiles.Where(t => !t.IsActive) — skips tiles already reporting active
4 Daqifi.Avalonia/Daqifi.Desktop/ViewModels/DaqifiViewModel.cs:1801 CanToggleLogging = ActiveChannels.Count > 0, fed from LoggingManager.SubscribedChannels

Core 1.7.0 resyncs analog IsEnabled from analog_in_port_enabled on every status message, so (2) is live, not a one-shot.

This is worse here than it ever was upstream: upstream added the guard for Core 1.4.0, and this app pins 1.7.0 without it.

Upstream's fix

daqifi-desktop/Daqifi.Desktop/Device/AbstractStreamingDevice.cs:

  • AdoptDeviceChannelSet(coreDevice) — called at :382, immediately after InitializeAsync(); defined :485. If coreDevice.Channels.Count(c => c.IsEnabled) > 0, it calls coreDevice.DisableAllChannels() and logs how many it cleared.
  • IsConnectionFatal(coreDevice, ex) — :528, used as an exception filter so a device merely refusing the command is survivable (the user can still pick by hand) while a dropped transport propagates and Connect() correctly returns false rather than handing ConnectionManager a device that is already gone.

Read both in full before porting — the exception-filter shape is load-bearing, not stylistic.

Trigger, honestly stated

Not reproduced on the bench. The DAQiFi Nq1 at /dev/cu.usbmodem1101 on firmware 3.7.2 reported enabledAnalogChannels: [] immediately after connect — before and after enabling three channels and reconnecting. So on that board today the mask does not survive a reconnect and the failure does not appear. The missing guard is confirmed by reading the code; the device state that triggers it is one this board did not present. Whoever picks this up should find a device or firmware that does persist the mask, or reproduce it by injection.

Acceptance criterion

Connecting a device whose Core channel set reports one or more channels already enabled leaves the app's active set empty: every tile inactive, SubscribedChannels empty, Select All enables all of them, and Start Logging becomes available once at least one is selected. A device that refuses DisableAllChannels logs a warning and still connects; a device whose transport dropped during the attempt fails the connection as it does today.

Platform matrix

Desktop, Android, iOS — all three. Nothing here is platform-conditional; it is on the shared connect path.

Notes for whoever takes it

  • AnalogChannel.ReplaceCoreChannel (:174) has a wasEnabled save/restore that looks like it preserves the app's enable intent and does not — it reads back whatever Core just resynced from the device mask. Do not read it and conclude this is already handled.
  • Upstream dropped ReplaceCoreChannel entirely in 05f0b8e1; that is a separate, lower-priority cleanup (see Upstream sync digest — daqifi-desktop -> port #141).

Activity

  1. tylerkron commented on Sep 1, 2026

    @tylerkron
    ContributorAuthor

    Correction: the trigger DOES reproduce on the bench Nq1. This issue's "Trigger, honestly stated" section says it did not; that was a false negative, and PR #174 has the A/B.

    The audit's probe read the enabled mask through a different path and saw enabledAnalogChannels: [] after a reconnect. Driving the app's own connect path (SerialStreamingDevice.Connect()) against the same board at /dev/cu.usbmodem1101 (fw 3.7.2), under the board lock, the mask does come back:

    • merge-base build (no adoption step — verified by the absence of AdoptDeviceChannelSet in the DLL): enable AI0–AI2, disconnect, reconnect → After Connect(): 32 channels, 3 active [AI0, AI1, AI2], with nothing subscribed to LoggingManager. That is exactly the state in which Select All is a no-op and Start Logging never becomes available.
    • fixed build, same board, same protocol, back to back → After Connect(): 32 channels, 0 active [], and the app log carries Cleared 3 channel(s) the device reported as already enabled …. Confirmed twice, ~4 minutes apart.

    The reason a single probe was not enough: on this board the mask is not reliably empty after a reconnect — it is empty on some reconnects and carries the previous session's set on others. So the audit's observation was real, just not general. Worth remembering for the next "could not reproduce" on this hardware.

    Everything else in the issue — the four lining-up causes, the upstream fix, the acceptance criterion — held up exactly as written.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions