From 3cabae604ae9ee20768256b65e334f4a47c7143f Mon Sep 17 00:00:00 2001 From: Tyler Kron Date: Fri, 20 Mar 2026 13:00:27 -0600 Subject: [PATCH 1/2] fix: add LAN disable and SD stream interface setup to StartSdCardLoggingAsync SD card and LAN share the SPI bus on the hardware. StartSdCardLoggingAsync was missing the DisableNetworkLan and SetStreamInterface(SdCard) commands that the desktop app was sending via its PrepareSdInterface() workaround. This caused SD card logging to silently produce no files when called from Core alone (e.g. via the example CLI app). StopSdCardLoggingAsync now also restores the stream interface to USB for USB connections so subsequent non-SD operations work correctly. Co-Authored-By: Claude Opus 4.6 Entire-Checkpoint: a0fb8798807b --- .../Device/SdCard/SdCardOperationsTests.cs | 36 +++++++++++-------- .../Device/DaqifiStreamingDevice.cs | 15 ++++++++ 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs b/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs index 90bd9e6..7318799 100644 --- a/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs +++ b/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs @@ -113,11 +113,13 @@ public async Task StartSdCardLoggingAsync_SendsCorrectCommandSequence() // Assert var sentCommands = device.SentMessages.Select(m => m.Data).ToList(); - Assert.Equal(4, sentCommands.Count); - Assert.Equal("SYSTem:STORage:SD:ENAble 1", sentCommands[0]); - Assert.Equal("SYSTem:STORage:SD:LOGging \"mylog.bin\"", sentCommands[1]); - Assert.Equal("SYSTem:STReam:FORmat 0", sentCommands[2]); - Assert.Equal("SYSTem:StartStreamData 100", sentCommands[3]); + Assert.Equal(6, sentCommands.Count); + Assert.Equal("SYSTem:COMMunicate:LAN:ENAbled 0", sentCommands[0]); + Assert.Equal("SYSTem:STORage:SD:ENAble 1", sentCommands[1]); + Assert.Equal("SYSTem:STReam:INTerface 2", sentCommands[2]); + Assert.Equal("SYSTem:STORage:SD:LOGging \"mylog.bin\"", sentCommands[3]); + Assert.Equal("SYSTem:STReam:FORmat 0", sentCommands[4]); + Assert.Equal("SYSTem:StartStreamData 100", sentCommands[5]); } [Fact] @@ -285,11 +287,13 @@ public async Task StartSdCardLoggingAsync_WithJsonFormat_SendsJsonFormatCommand( // Assert var sentCommands = device.SentMessages.Select(m => m.Data).ToList(); - Assert.Equal(4, sentCommands.Count); - Assert.Equal("SYSTem:STORage:SD:ENAble 1", sentCommands[0]); - Assert.Equal("SYSTem:STORage:SD:LOGging \"mylog.json\"", sentCommands[1]); - Assert.Equal("SYSTem:STReam:FORmat 1", sentCommands[2]); - Assert.Equal("SYSTem:StartStreamData 100", sentCommands[3]); + Assert.Equal(6, sentCommands.Count); + Assert.Equal("SYSTem:COMMunicate:LAN:ENAbled 0", sentCommands[0]); + Assert.Equal("SYSTem:STORage:SD:ENAble 1", sentCommands[1]); + Assert.Equal("SYSTem:STReam:INTerface 2", sentCommands[2]); + Assert.Equal("SYSTem:STORage:SD:LOGging \"mylog.json\"", sentCommands[3]); + Assert.Equal("SYSTem:STReam:FORmat 1", sentCommands[4]); + Assert.Equal("SYSTem:StartStreamData 100", sentCommands[5]); } [Fact] @@ -304,11 +308,13 @@ public async Task StartSdCardLoggingAsync_WithCsvFormat_SendsCsvFormatCommand() // Assert var sentCommands = device.SentMessages.Select(m => m.Data).ToList(); - Assert.Equal(4, sentCommands.Count); - Assert.Equal("SYSTem:STORage:SD:ENAble 1", sentCommands[0]); - Assert.Equal("SYSTem:STORage:SD:LOGging \"mylog.csv\"", sentCommands[1]); - Assert.Equal("SYSTem:STReam:FORmat 2", sentCommands[2]); - Assert.Equal("SYSTem:StartStreamData 100", sentCommands[3]); + Assert.Equal(6, sentCommands.Count); + Assert.Equal("SYSTem:COMMunicate:LAN:ENAbled 0", sentCommands[0]); + Assert.Equal("SYSTem:STORage:SD:ENAble 1", sentCommands[1]); + Assert.Equal("SYSTem:STReam:INTerface 2", sentCommands[2]); + Assert.Equal("SYSTem:STORage:SD:LOGging \"mylog.csv\"", sentCommands[3]); + Assert.Equal("SYSTem:STReam:FORmat 2", sentCommands[4]); + Assert.Equal("SYSTem:StartStreamData 100", sentCommands[5]); } [Fact] diff --git a/src/Daqifi.Core/Device/DaqifiStreamingDevice.cs b/src/Daqifi.Core/Device/DaqifiStreamingDevice.cs index c05f84b..258b323 100644 --- a/src/Daqifi.Core/Device/DaqifiStreamingDevice.cs +++ b/src/Daqifi.Core/Device/DaqifiStreamingDevice.cs @@ -399,9 +399,18 @@ public async Task StartSdCardLoggingAsync(string? fileName = null, string? chann // SdCardLogFormat integer values map 1:1 to SYSTem:STReam:FORmat SCPI arguments var formatCommand = new ScpiMessage($"SYSTem:STReam:FORmat {(int)format}"); + // SD card and LAN share the SPI bus on the hardware, so LAN must be + // disabled before the SD card can be used. + Send(ScpiMessageProducer.DisableNetworkLan); + await Task.Delay(100, cancellationToken); + Send(ScpiMessageProducer.EnableStorageSd); await Task.Delay(100, cancellationToken); + // Route the data stream to the SD card interface. + Send(ScpiMessageProducer.SetStreamInterface(StreamInterface.SdCard)); + await Task.Delay(100, cancellationToken); + Send(ScpiMessageProducer.SetSdLoggingFileName(logFileName)); await Task.Delay(100, cancellationToken); @@ -439,6 +448,12 @@ public Task StopSdCardLoggingAsync(CancellationToken cancellationToken = default StopStreaming(); Send(ScpiMessageProducer.DisableStorageSd); + // Restore stream interface to USB so subsequent non-SD operations work. + if (IsUsbConnection) + { + Send(ScpiMessageProducer.SetStreamInterface(StreamInterface.Usb)); + } + _isLoggingToSdCard = false; return Task.CompletedTask; From 5c1e1c6077f6de2222ba8e3a6ed2d5a1bea90834 Mon Sep 17 00:00:00 2001 From: Tyler Kron Date: Fri, 20 Mar 2026 13:22:36 -0600 Subject: [PATCH 2/2] fix: guard StartSdCardLoggingAsync against non-USB connections SD card logging requires USB because SD and LAN share the SPI bus. Add an early guard that throws InvalidOperationException for non-USB connections, matching the existing pattern in DownloadSdCardFileAsync. Also fixes StopSdCardLoggingAsync test to expect the new SetStreamInterface(Usb) restore command, and updates the download non-USB test to use a dedicated TestableNonUsbStreamingDevice. Co-Authored-By: Claude Opus 4.6 Entire-Checkpoint: a0fb8798807b --- .../Device/SdCard/SdCardOperationsTests.cs | 42 +++++++++++++++++-- .../Device/DaqifiStreamingDevice.cs | 7 ++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs b/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs index 7318799..14f42f0 100644 --- a/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs +++ b/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs @@ -122,6 +122,19 @@ public async Task StartSdCardLoggingAsync_SendsCorrectCommandSequence() Assert.Equal("SYSTem:StartStreamData 100", sentCommands[5]); } + [Fact] + public async Task StartSdCardLoggingAsync_OverNonUsbConnection_ThrowsInvalidOperationException() + { + // Arrange — use a device that reports IsUsbConnection = false + var device = new TestableNonUsbStreamingDevice("TestDevice"); + device.Connect(); + + // Act & Assert + var ex = await Assert.ThrowsAsync( + () => device.StartSdCardLoggingAsync("test.bin")); + Assert.Contains("USB", ex.Message); + } + [Fact] public async Task StartSdCardLoggingAsync_WithCustomFileName_UsesProvidedName() { @@ -184,9 +197,10 @@ public async Task StopSdCardLoggingAsync_SendsCorrectCommands() // Assert var sentCommands = device.SentMessages.Select(m => m.Data).ToList(); - Assert.Equal(2, sentCommands.Count); + Assert.Equal(3, sentCommands.Count); Assert.Equal("SYSTem:StopStreamData", sentCommands[0]); Assert.Equal("SYSTem:STORage:SD:ENAble 0", sentCommands[1]); + Assert.Equal("SYSTem:STReam:INTerface 0", sentCommands[2]); // Restore USB } [Fact] @@ -701,8 +715,8 @@ await Assert.ThrowsAsync( [Fact] public async Task DownloadSdCardFileAsync_WhenNotSerialTransport_Throws() { - // Arrange — use the testable device which has no transport (simulates non-USB) - var device = new TestableSdCardStreamingDevice("TestDevice"); + // Arrange — use a device that reports IsUsbConnection = false + var device = new TestableNonUsbStreamingDevice("TestDevice"); device.Connect(); using var stream = new MemoryStream(); @@ -909,6 +923,11 @@ private class TestableSdCardStreamingDevice : DaqifiStreamingDevice public List> SentMessages { get; } = new(); public List CannedTextResponse { get; set; } = new(); + /// + /// Simulates a USB connection so SD card operations are allowed. + /// + public override bool IsUsbConnection => true; + public TestableSdCardStreamingDevice(string name, IPAddress? ipAddress = null) : base(name, ipAddress) { @@ -984,5 +1003,22 @@ protected override async Task ExecuteRawCaptureAsync( await rawAction(fakeStream, cancellationToken); } } + /// + /// A testable device that reports IsUsbConnection = false to verify + /// that SD card operations reject non-USB connections. + /// + private class TestableNonUsbStreamingDevice : DaqifiStreamingDevice + { + public TestableNonUsbStreamingDevice(string name, IPAddress? ipAddress = null) + : base(name, ipAddress) + { + } + + public override bool IsUsbConnection => false; + + public override void Send(IOutboundMessage message) + { + } + } } } diff --git a/src/Daqifi.Core/Device/DaqifiStreamingDevice.cs b/src/Daqifi.Core/Device/DaqifiStreamingDevice.cs index 258b323..a4cd405 100644 --- a/src/Daqifi.Core/Device/DaqifiStreamingDevice.cs +++ b/src/Daqifi.Core/Device/DaqifiStreamingDevice.cs @@ -381,6 +381,13 @@ public async Task StartSdCardLoggingAsync(string? fileName = null, string? chann throw new InvalidOperationException("Device is not connected."); } + if (!IsUsbConnection) + { + throw new InvalidOperationException( + "SD card logging requires a USB/serial connection. " + + "The SD card and WiFi/LAN share the SPI bus, so SD operations cannot be performed over a network connection."); + } + cancellationToken.ThrowIfCancellationRequested(); var extension = format switch