diff --git a/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs b/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs index 90bd9e6..14f42f0 100644 --- a/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs +++ b/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs @@ -113,11 +113,26 @@ 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] + 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] @@ -182,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] @@ -285,11 +301,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 +322,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] @@ -695,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(); @@ -903,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) { @@ -978,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 c05f84b..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 @@ -399,9 +406,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 +455,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;