From 9491482d775e20842ec315a1d1d94d8292c2deef Mon Sep 17 00:00:00 2001 From: Tyler Kron Date: Sat, 18 Jul 2026 15:19:22 -0600 Subject: [PATCH 1/2] feat(device): validate StreamingFrequency + return SD logging filename (closes #336, #337) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #336: StreamingFrequency was an unvalidated auto-property whose value was sent to the hardware verbatim, so a bad rate (0, negative, 50 kHz) reached the device silently and every consumer re-declared the 1-1000 Hz limit. Validate the setter against Metadata.Capabilities.MaxSamplingRate (not a hardcoded constant), throwing ArgumentOutOfRangeException with the valid range — matching the client-side guards Core already applies for PWM (#306) and channel bounds (#300). The MCP server's duplicated HardwareMaxSampleRateHz constant is removed; it now reads the device's advertised max and its --max-sample-rate-hz option still clamps below it. #337: StartSdCardLoggingAsync returned bare Task, so a caller passing fileName:null never learned the log_{timestamp} name Core generated and had to duplicate the convention to report/download/delete the log. It now returns SdCardLoggingSession { FileName, Format } carrying the effective on-card name for both explicit and auto-generated cases. The MCP server drops its duplicated filename generation (and the now-dead ExtensionFor helper) and uses the returned value. Bench-tested on real hardware (Nq1, FW 3.7.2): max read as 1000 Hz, out-of-range rejected; auto + explicit SD names returned and confirmed present in the card's file list. Co-Authored-By: Claude Opus 4.8 --- .../Device/DaqifiStreamingDeviceTests.cs | 57 +++++++++++++++++++ .../Device/SdCard/SdCardOperationsTests.cs | 36 ++++++++++++ .../Device/DaqifiStreamingDevice.cs | 38 +++++++++++-- .../Device/SdCard/ISdCardOperations.cs | 8 ++- .../Device/SdCard/SdCardLoggingSession.cs | 36 ++++++++++++ src/Daqifi.Mcp/DaqifiAgent.cs | 32 ++++------- 6 files changed, 179 insertions(+), 28 deletions(-) create mode 100644 src/Daqifi.Core/Device/SdCard/SdCardLoggingSession.cs diff --git a/src/Daqifi.Core.Tests/Device/DaqifiStreamingDeviceTests.cs b/src/Daqifi.Core.Tests/Device/DaqifiStreamingDeviceTests.cs index 9bcb763a..486393b9 100644 --- a/src/Daqifi.Core.Tests/Device/DaqifiStreamingDeviceTests.cs +++ b/src/Daqifi.Core.Tests/Device/DaqifiStreamingDeviceTests.cs @@ -20,6 +20,63 @@ public void Constructor_InitializesStreamingFrequency() Assert.False(device.IsStreaming); } + [Theory] + [InlineData(1)] + [InlineData(500)] + [InlineData(1000)] // MaxSamplingRate for all Nyquist types + public void StreamingFrequency_WithinRange_IsAccepted(int frequency) + { + // Arrange + var device = new DaqifiStreamingDevice("TestDevice"); + + // Act + device.StreamingFrequency = frequency; + + // Assert + Assert.Equal(frequency, device.StreamingFrequency); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + [InlineData(1001)] // MaxSamplingRate + 1 + [InlineData(50000)] + public void StreamingFrequency_OutOfRange_ThrowsArgumentOutOfRangeException(int frequency) + { + // Arrange + var device = new DaqifiStreamingDevice("TestDevice"); + + // Act & Assert + var exception = Assert.Throws(() => device.StreamingFrequency = frequency); + Assert.Contains("1000", exception.Message); // valid range surfaced from DeviceCapabilities.MaxSamplingRate + } + + [Fact] + public void StreamingFrequency_OutOfRange_LeavesPreviousValueUnchanged() + { + // Arrange + var device = new DaqifiStreamingDevice("TestDevice") { StreamingFrequency = 250 }; + + // Act + Assert.Throws(() => device.StreamingFrequency = 5000); + + // Assert + Assert.Equal(250, device.StreamingFrequency); + } + + [Fact] + public void StreamingFrequency_UsesCapabilitiesMaxSamplingRate_NotAHardcodedConstant() + { + // Arrange: lower the device's advertised max and confirm the guard tracks it. + var device = new DaqifiStreamingDevice("TestDevice"); + device.Metadata.Capabilities.MaxSamplingRate = 200; + + // Act & Assert: 200 is now the ceiling, 201 is rejected. + device.StreamingFrequency = 200; + Assert.Equal(200, device.StreamingFrequency); + Assert.Throws(() => device.StreamingFrequency = 201); + } + [Fact] public void StartStreaming_WhenConnected_SendsCorrectCommandAndSetsIsStreaming() { diff --git a/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs b/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs index c7ed38b9..d4b1da91 100644 --- a/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs +++ b/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs @@ -336,6 +336,42 @@ public async Task StartSdCardLoggingAsync_WithNullFileName_GeneratesTimestampedN Assert.Contains(".bin", loggingCommand); } + [Fact] + public async Task StartSdCardLoggingAsync_WithCustomFileName_ReturnsSessionWithThatName() + { + // Arrange + var device = new TestableSdCardStreamingDevice("TestDevice"); + device.Connect(); + + // Act + var session = await device.StartSdCardLoggingAsync("custom_data.bin", format: SdCardLogFormat.Protobuf); + + // Assert: the returned name is exactly what was sent to the device. + var sentCommands = device.SentMessages.Select(m => m.Data).ToList(); + Assert.Equal("custom_data.bin", session.FileName); + Assert.Equal(SdCardLogFormat.Protobuf, session.Format); + Assert.Contains($"SYSTem:STORage:SD:FILE \"{session.FileName}\"", sentCommands); + } + + [Fact] + public async Task StartSdCardLoggingAsync_WithNullFileName_ReturnsGeneratedNameSentToDevice() + { + // Arrange + var device = new TestableSdCardStreamingDevice("TestDevice"); + device.Connect(); + + // Act + var session = await device.StartSdCardLoggingAsync(format: SdCardLogFormat.Json); + + // Assert: the auto-generated name the caller receives is the one that reached the device, + // so consumers no longer have to re-derive Core's naming convention. + var sentCommands = device.SentMessages.Select(m => m.Data).ToList(); + Assert.StartsWith("log_", session.FileName); + Assert.EndsWith(".json", session.FileName); + Assert.Equal(SdCardLogFormat.Json, session.Format); + Assert.Contains($"SYSTem:STORage:SD:FILE \"{session.FileName}\"", sentCommands); + } + [Fact] public async Task StartSdCardLoggingAsync_SetsIsLoggingToTrue() { diff --git a/src/Daqifi.Core/Device/DaqifiStreamingDevice.cs b/src/Daqifi.Core/Device/DaqifiStreamingDevice.cs index 4196f66a..7526da51 100644 --- a/src/Daqifi.Core/Device/DaqifiStreamingDevice.cs +++ b/src/Daqifi.Core/Device/DaqifiStreamingDevice.cs @@ -86,10 +86,35 @@ public class DaqifiStreamingDevice : DaqifiDevice, IStreamingDevice, INetworkCon /// public bool IsStreaming { get; private set; } + private int _streamingFrequency; + /// - /// Gets or sets the streaming frequency in Hz (samples per second). + /// Gets or sets the streaming frequency in Hz (samples per second). The value is + /// validated against the device's advertised maximum sampling rate + /// () so a silently-wrong rate never + /// reaches the hardware — consistent with the client-side guards Core already applies + /// to PWM (#306) and channel bounds (#300). /// - public int StreamingFrequency { get; set; } + /// + /// Thrown when the value is less than 1 or greater than the device's maximum sampling rate. + /// + public int StreamingFrequency + { + get => _streamingFrequency; + set + { + var maxSamplingRate = Metadata.Capabilities.MaxSamplingRate; + if (value < 1 || value > maxSamplingRate) + { + throw new ArgumentOutOfRangeException( + nameof(StreamingFrequency), + value, + $"Streaming frequency must be between 1 and {maxSamplingRate} Hz (the device's maximum sampling rate)."); + } + + _streamingFrequency = value; + } + } /// /// Gets a value indicating whether the device is currently logging data to the SD card. @@ -1329,10 +1354,13 @@ public void SetSdCardMinimumFreeSpace(long bytes) /// /// The logging format to use. Defaults to . /// A cancellation token to observe while waiting for the task to complete. - /// A task that represents the asynchronous operation. + /// + /// A task that resolves to an carrying the effective on-card + /// file name (supplied or auto-generated) and the logging format. + /// /// Thrown when the device is not connected. /// Thrown when the operation is canceled. - public async Task StartSdCardLoggingAsync(string? fileName = null, string? channelMask = null, SdCardLogFormat format = SdCardLogFormat.Protobuf, CancellationToken cancellationToken = default) + public async Task StartSdCardLoggingAsync(string? fileName = null, string? channelMask = null, SdCardLogFormat format = SdCardLogFormat.Protobuf, CancellationToken cancellationToken = default) { if (!IsConnected) { @@ -1392,6 +1420,8 @@ public async Task StartSdCardLoggingAsync(string? fileName = null, string? chann _isLoggingToSdCard = true; IsStreaming = true; + + return new SdCardLoggingSession(logFileName, format); } /// diff --git a/src/Daqifi.Core/Device/SdCard/ISdCardOperations.cs b/src/Daqifi.Core/Device/SdCard/ISdCardOperations.cs index f3b42bc8..3f0c63a4 100644 --- a/src/Daqifi.Core/Device/SdCard/ISdCardOperations.cs +++ b/src/Daqifi.Core/Device/SdCard/ISdCardOperations.cs @@ -109,9 +109,13 @@ Task CheckSdCardSpaceAsync( /// /// The logging format to use. Defaults to . /// A cancellation token to observe while waiting for the task to complete. - /// A task that represents the asynchronous operation. + /// + /// A task that resolves to an carrying the effective on-card + /// file name (supplied or auto-generated) and the logging format, so callers can report, download, + /// or later delete the log without re-deriving Core's naming convention. + /// /// Thrown when the device is not connected. - Task StartSdCardLoggingAsync(string? fileName = null, string? channelMask = null, SdCardLogFormat format = SdCardLogFormat.Protobuf, CancellationToken cancellationToken = default); + Task StartSdCardLoggingAsync(string? fileName = null, string? channelMask = null, SdCardLogFormat format = SdCardLogFormat.Protobuf, CancellationToken cancellationToken = default); /// /// Stops logging data to the SD card. diff --git a/src/Daqifi.Core/Device/SdCard/SdCardLoggingSession.cs b/src/Daqifi.Core/Device/SdCard/SdCardLoggingSession.cs new file mode 100644 index 00000000..8b7c1437 --- /dev/null +++ b/src/Daqifi.Core/Device/SdCard/SdCardLoggingSession.cs @@ -0,0 +1,36 @@ +namespace Daqifi.Core.Device.SdCard +{ + /// + /// Describes an SD-card logging session that was just started: the effective on-card file + /// name (whether supplied by the caller or auto-generated by Core) and the format it is + /// being written in. Returned by + /// + /// so callers can report, download, or later delete the log without re-deriving Core's + /// naming convention. + /// + public sealed class SdCardLoggingSession + { + /// + /// Initializes a new instance of the class. + /// + /// The effective on-card file name. + /// The logging format the file is written in. + public SdCardLoggingSession(string fileName, SdCardLogFormat format) + { + FileName = fileName; + Format = format; + } + + /// + /// Gets the effective on-card file name — the value sent to the device, whether it was + /// supplied by the caller or auto-generated as log_YYYYMMDD_HHMMSS with the + /// format-appropriate extension. + /// + public string FileName { get; } + + /// + /// Gets the logging format the file is written in. + /// + public SdCardLogFormat Format { get; } + } +} diff --git a/src/Daqifi.Mcp/DaqifiAgent.cs b/src/Daqifi.Mcp/DaqifiAgent.cs index 62029c1f..843644e6 100644 --- a/src/Daqifi.Mcp/DaqifiAgent.cs +++ b/src/Daqifi.Mcp/DaqifiAgent.cs @@ -20,9 +20,6 @@ namespace Daqifi.Mcp; /// public sealed class DaqifiAgent { - /// The maximum sample rate the Nyquist hardware accepts (SCPI range is 1–1000 Hz). - public const int HardwareMaxSampleRateHz = 1000; - private readonly ServerOptions _options; private readonly ConcurrentDictionary _discovered = new(StringComparer.Ordinal); private readonly ConcurrentDictionary _connected = new(StringComparer.Ordinal); @@ -357,13 +354,15 @@ public async Task SetSampleRateAsync(string deviceId, int rate try { RequireControl(); - var (_, streaming) = RequireStreaming(deviceId); + var (device, streaming) = RequireStreaming(deviceId); - // The hardware ceiling (1000 Hz) always applies; --max-sample-rate-hz can only lower it. + // The device's advertised hardware ceiling always applies (Core validates + // StreamingFrequency against it too); --max-sample-rate-hz can only lower it. // Guard against a non-positive cap so the applied rate is always a valid >= 1 value. + var hardwareMax = Math.Max(1, device.Metadata.Capabilities.MaxSamplingRate); var cap = _options.MaxSampleRateHz is { } max - ? Math.Clamp(max, 1, HardwareMaxSampleRateHz) - : HardwareMaxSampleRateHz; + ? Math.Clamp(max, 1, hardwareMax) + : hardwareMax; var applied = Math.Min(rateHz, cap); streaming.StreamingFrequency = applied; @@ -393,17 +392,13 @@ public async Task StartLoggingAsync( var (device, streaming) = RequireStreaming(deviceId); var sd = RequireSdCard(device); - // Generate the name in this layer so the result reports the real on-card filename. - // Core honors a non-empty fileName verbatim; channels use the device's current config. - var effectiveName = string.IsNullOrWhiteSpace(fileName) - ? $"log_{DateTime.Now:yyyyMMdd_HHmmss}{ExtensionFor(fmt)}" - : fileName!; - - await sd.StartSdCardLoggingAsync(effectiveName, channelMask: null, format: fmt, cancellationToken) + // Core owns the naming convention and reports the effective on-card filename back to + // us, so we no longer duplicate the log_{timestamp} generation here. + var session = await sd.StartSdCardLoggingAsync(fileName, channelMask: null, format: fmt, cancellationToken) .ConfigureAwait(false); return new StartLoggingResult( - deviceId, effectiveName, fmt.ToString(), streaming.StreamingFrequency, EnabledAnalog(device)); + deviceId, session.FileName, session.Format.ToString(), streaming.StreamingFrequency, EnabledAnalog(device)); } finally { @@ -545,13 +540,6 @@ private static IChannel RequireDigitalChannel(DaqifiDevice device, int channelNu _ => throw new InvalidOperationException($"Unknown direction '{direction}'. Use 'input' or 'output'."), }; - private static string ExtensionFor(SdCardLogFormat format) => format switch - { - SdCardLogFormat.Json => ".json", - SdCardLogFormat.Csv => ".csv", - _ => ".bin", - }; - private static SdCardLogFormat ParseFormat(string? format) => (format ?? string.Empty).Trim().ToLowerInvariant() switch { "" or "protobuf" or "bin" or "binary" => SdCardLogFormat.Protobuf, From 7e608f42f62dde3adf7ce850dd9ecaea2a365f94 Mon Sep 17 00:00:00 2001 From: Tyler Kron Date: Sat, 18 Jul 2026 15:35:16 -0600 Subject: [PATCH 2/2] =?UTF-8?q?fix(device):=20address=20Qodo=20review=20?= =?UTF-8?q?=E2=80=94=20sanitize=20sampling-rate=20ceiling,=20keep=20SD=20A?= =?UTF-8?q?PI=20non-breaking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - StreamingFrequency setter now sanitizes the ceiling with Math.Max(1, MaxSamplingRate), so an invalid/uninitialized MaxSamplingRate (0 or negative) can't produce an impossible "1..0" range that rejects every frequency (Qodo #1). Added a test for max<=0. - Restore the original `Task StartSdCardLoggingAsync(...)` signature (v1.x public API) as a compatibility overload and add the effective-filename variant as a new `Task StartSdCardLoggingSessionAsync(...)` instead of changing the released return type (Qodo #2). The old method delegates to the new one; MCP uses the new one. Avoids the semver break flagged for a post-1.0 library. Co-Authored-By: Claude Opus 4.8 --- .../Device/DaqifiStreamingDeviceTests.cs | 18 ++++++++++ .../Device/SdCard/SdCardOperationsTests.cs | 4 +-- .../Device/DaqifiStreamingDevice.cs | 26 ++++++++++++--- .../Device/SdCard/ISdCardOperations.cs | 33 ++++++++++++++++--- src/Daqifi.Mcp/DaqifiAgent.cs | 2 +- 5 files changed, 72 insertions(+), 11 deletions(-) diff --git a/src/Daqifi.Core.Tests/Device/DaqifiStreamingDeviceTests.cs b/src/Daqifi.Core.Tests/Device/DaqifiStreamingDeviceTests.cs index 486393b9..bfb445dd 100644 --- a/src/Daqifi.Core.Tests/Device/DaqifiStreamingDeviceTests.cs +++ b/src/Daqifi.Core.Tests/Device/DaqifiStreamingDeviceTests.cs @@ -77,6 +77,24 @@ public void StreamingFrequency_UsesCapabilitiesMaxSamplingRate_NotAHardcodedCons Assert.Throws(() => device.StreamingFrequency = 201); } + [Theory] + [InlineData(0)] + [InlineData(-5)] + public void StreamingFrequency_InvalidCapabilitiesMax_SanitizesCeilingToOne(int invalidMax) + { + // Arrange: MaxSamplingRate is a mutable, unvalidated public property. An invalid value + // must not produce an impossible range that rejects every frequency. + var device = new DaqifiStreamingDevice("TestDevice"); + device.Metadata.Capabilities.MaxSamplingRate = invalidMax; + + // Act & Assert: the ceiling is sanitized to 1, so 1 is accepted and 2 is rejected. + device.StreamingFrequency = 1; + Assert.Equal(1, device.StreamingFrequency); + + var ex = Assert.Throws(() => device.StreamingFrequency = 2); + Assert.Contains("1 and 1", ex.Message); // range reported with the sanitized max, not "1..0" + } + [Fact] public void StartStreaming_WhenConnected_SendsCorrectCommandAndSetsIsStreaming() { diff --git a/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs b/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs index d4b1da91..9219d4c2 100644 --- a/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs +++ b/src/Daqifi.Core.Tests/Device/SdCard/SdCardOperationsTests.cs @@ -344,7 +344,7 @@ public async Task StartSdCardLoggingAsync_WithCustomFileName_ReturnsSessionWithT device.Connect(); // Act - var session = await device.StartSdCardLoggingAsync("custom_data.bin", format: SdCardLogFormat.Protobuf); + var session = await device.StartSdCardLoggingSessionAsync("custom_data.bin", format: SdCardLogFormat.Protobuf); // Assert: the returned name is exactly what was sent to the device. var sentCommands = device.SentMessages.Select(m => m.Data).ToList(); @@ -361,7 +361,7 @@ public async Task StartSdCardLoggingAsync_WithNullFileName_ReturnsGeneratedNameS device.Connect(); // Act - var session = await device.StartSdCardLoggingAsync(format: SdCardLogFormat.Json); + var session = await device.StartSdCardLoggingSessionAsync(format: SdCardLogFormat.Json); // Assert: the auto-generated name the caller receives is the one that reached the device, // so consumers no longer have to re-derive Core's naming convention. diff --git a/src/Daqifi.Core/Device/DaqifiStreamingDevice.cs b/src/Daqifi.Core/Device/DaqifiStreamingDevice.cs index 7526da51..bd6ff43f 100644 --- a/src/Daqifi.Core/Device/DaqifiStreamingDevice.cs +++ b/src/Daqifi.Core/Device/DaqifiStreamingDevice.cs @@ -103,7 +103,10 @@ public int StreamingFrequency get => _streamingFrequency; set { - var maxSamplingRate = Metadata.Capabilities.MaxSamplingRate; + // MaxSamplingRate is a mutable, unvalidated public property; sanitize the ceiling so + // an uninitialized/invalid capabilities value (0 or negative) can't produce an + // impossible range like "1..0" that rejects every valid frequency. + var maxSamplingRate = Math.Max(1, Metadata.Capabilities.MaxSamplingRate); if (value < 1 || value > maxSamplingRate) { throw new ArgumentOutOfRangeException( @@ -1340,12 +1343,27 @@ public void SetSdCardMinimumFreeSpace(long bytes) } /// - /// Starts logging data to the SD card. + /// Starts logging data to the SD card. Compatibility overload preserving the original + /// return; use to also learn + /// the effective on-card file name. + /// + /// The log file name, or null/empty to auto-generate a timestamped name. + /// Optional decimal channel bitmask; null/empty uses the current config. + /// The logging format to use. Defaults to . + /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. + /// Thrown when the device is not connected. + /// Thrown when the operation is canceled. + public Task StartSdCardLoggingAsync(string? fileName = null, string? channelMask = null, SdCardLogFormat format = SdCardLogFormat.Protobuf, CancellationToken cancellationToken = default) + => StartSdCardLoggingSessionAsync(fileName, channelMask, format, cancellationToken); + + /// + /// Starts logging data to the SD card and returns the effective session details. /// /// /// The name of the log file. If null or empty, a timestamped name is generated automatically /// using the pattern "log_YYYYMMDD_HHMMSS" with an extension matching - /// (.bin for Protobuf, .json for JSON, .dat for TestData). + /// (.bin for Protobuf, .json for JSON, .csv for CSV). /// /// /// Optional decimal bitmask string to enable specific ADC channels (e.g. "3" enables channels 0 and 1). @@ -1360,7 +1378,7 @@ public void SetSdCardMinimumFreeSpace(long bytes) /// /// Thrown when the device is not connected. /// Thrown when the operation is canceled. - public async Task StartSdCardLoggingAsync(string? fileName = null, string? channelMask = null, SdCardLogFormat format = SdCardLogFormat.Protobuf, CancellationToken cancellationToken = default) + public async Task StartSdCardLoggingSessionAsync(string? fileName = null, string? channelMask = null, SdCardLogFormat format = SdCardLogFormat.Protobuf, CancellationToken cancellationToken = default) { if (!IsConnected) { diff --git a/src/Daqifi.Core/Device/SdCard/ISdCardOperations.cs b/src/Daqifi.Core/Device/SdCard/ISdCardOperations.cs index 3f0c63a4..55f97822 100644 --- a/src/Daqifi.Core/Device/SdCard/ISdCardOperations.cs +++ b/src/Daqifi.Core/Device/SdCard/ISdCardOperations.cs @@ -109,13 +109,38 @@ Task CheckSdCardSpaceAsync( /// /// The logging format to use. Defaults to . /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. + /// + /// To learn the effective on-card file name (especially when it was auto-generated), call + /// instead, which returns an + /// . + /// + /// Thrown when the device is not connected. + Task StartSdCardLoggingAsync(string? fileName = null, string? channelMask = null, SdCardLogFormat format = SdCardLogFormat.Protobuf, CancellationToken cancellationToken = default); + + /// + /// Starts logging data to the SD card and returns the effective session details. Behaves + /// exactly like but resolves to an + /// carrying the effective on-card file name (supplied or + /// auto-generated) and the logging format, so callers can report, download, or later delete + /// the log without re-deriving Core's naming convention. + /// + /// + /// The name of the log file. If null or empty, a timestamped name is generated automatically + /// using the pattern "log_YYYYMMDD_HHMMSS" with a format-appropriate extension. + /// + /// + /// Optional decimal bitmask string to enable specific ADC channels (e.g. "3" enables channels 0 and 1). + /// If null or empty, the current device channel configuration is used. + /// + /// The logging format to use. Defaults to . + /// A cancellation token to observe while waiting for the task to complete. /// - /// A task that resolves to an carrying the effective on-card - /// file name (supplied or auto-generated) and the logging format, so callers can report, download, - /// or later delete the log without re-deriving Core's naming convention. + /// A task that resolves to an with the effective on-card + /// file name and logging format. /// /// Thrown when the device is not connected. - Task StartSdCardLoggingAsync(string? fileName = null, string? channelMask = null, SdCardLogFormat format = SdCardLogFormat.Protobuf, CancellationToken cancellationToken = default); + Task StartSdCardLoggingSessionAsync(string? fileName = null, string? channelMask = null, SdCardLogFormat format = SdCardLogFormat.Protobuf, CancellationToken cancellationToken = default); /// /// Stops logging data to the SD card. diff --git a/src/Daqifi.Mcp/DaqifiAgent.cs b/src/Daqifi.Mcp/DaqifiAgent.cs index 843644e6..b064d8b5 100644 --- a/src/Daqifi.Mcp/DaqifiAgent.cs +++ b/src/Daqifi.Mcp/DaqifiAgent.cs @@ -394,7 +394,7 @@ public async Task StartLoggingAsync( // Core owns the naming convention and reports the effective on-card filename back to // us, so we no longer duplicate the log_{timestamp} generation here. - var session = await sd.StartSdCardLoggingAsync(fileName, channelMask: null, format: fmt, cancellationToken) + var session = await sd.StartSdCardLoggingSessionAsync(fileName, channelMask: null, format: fmt, cancellationToken) .ConfigureAwait(false); return new StartLoggingResult(