Skip to content

feat: Add SD card CLI commands - #6

Merged
tylerkron merged 3 commits into
mainfrom
feature/sd-card-commands
Feb 4, 2026
Merged

tylerkron merged 3 commits into
mainfrom
feature/sd-card-commands

Conversation

@tylerkron

@tylerkron tylerkron commented Jan 30, 2026 •

Copy link
Copy Markdown
Contributor

User description

Summary

  • Add --sd-list command to list files on the device SD card
  • Add --sd-log-start command to start SD card logging (supports --duration for auto-stop)
  • Add --sd-log-stop command to stop SD card logging
  • Add help text for the new SD card options

Depends on daqifi/daqifi-core#99 (SD card operations in core library).

Test plan

  • dotnet build -p:DaqifiCoreProjectPath=../daqifi-core/src/Daqifi.Core/Daqifi.Core.csproj compiles
  • Hardware test: dotnet run -- --serial <port> --sd-list
  • Hardware test: dotnet run -- --serial <port> --sd-log-start --duration 5

🤖 Generated with Claude Code


PR Type

Enhancement


Description

  • Add three new SD card CLI commands: --sd-list, --sd-log-start, --sd-log-stop

  • Implement RunSdCardOperationAsync() method for SD card operations

  • Support --duration flag for automatic SD card logging stop

  • Add help text documentation for new SD card options


Diagram Walkthrough

flowchart LR
  CLI["CLI Arguments Parser"] -- "detects SD card flags" --> Router["SD Card Router"]
  Router -- "if SdList" --> ListFiles["List SD Card Files"]
  Router -- "if SdLogStart" --> StartLog["Start SD Card Logging"]
  Router -- "if SdLogStop" --> StopLog["Stop SD Card Logging"]
  StartLog -- "with duration" --> AutoStop["Auto-stop after duration"]
  ListFiles --> Output["Display Results"]
  StopLog --> Output
  AutoStop --> Output
Loading

File Walkthrough

Relevant files
Enhancement
Program.cs
Implement SD card CLI commands and routing logic                 

Program.cs

  • Added using Daqifi.Core.Device.SdCard import for SD card operations
  • Added routing logic in Main() to detect and handle SD card operation
    flags
  • Implemented RunSdCardOperationAsync() method handling --sd-list,
    --sd-log-start, and --sd-log-stop commands
  • Added three new boolean properties to CliOptions class: SdList,
    SdLogStart, SdLogStop
  • Extended argument parser to recognize --sd-list, --sd-log-start, and
    --sd-log-stop flags
  • Added SD Card Options section to help text with descriptions of new
    commands
+150/-0 

Add --sd-list, --sd-log-start, and --sd-log-stop options for SD card
operations via the core library's ISdCardOperations interface.

Depends on daqifi/daqifi-core#feature/sd-card-operations.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@qodo-code-review

qodo-code-review Bot commented Jan 30, 2026 •

Copy link
Copy Markdown

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
⚪
Terminal escape injection

Description: Untrusted SD card metadata (e.g., file.FileName) is printed directly to the terminal via
Console.WriteLine, which can enable terminal/ANSI escape injection if a malicious or
compromised device returns filenames containing control sequences (e.g., \x1b[2J to clear
the screen or crafted hyperlinks), so output should be sanitized/escaped before display.
Program.cs [355-368]

Referred Code
Console.WriteLine("Listing SD card files...");
var files = await streamingDevice.GetSdCardFilesAsync();

if (files.Count == 0)
{
    Console.WriteLine("  (no files found)");
}
else
{
    foreach (var file in files)
    {
        var dateStr = file.CreatedDate?.ToString("yyyy-MM-dd HH:mm:ss") ?? "unknown date";
        Console.WriteLine($"  {file.FileName}  ({dateStr})");
    }
Ticket Compliance
⚪
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
⚪
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing connection validation: RunSdCardOperationAsync attempts a TCP connection using options.IpAddress! when
options.SerialPort is empty, which can throw/null-ref if neither serial nor IP was
provided, instead of returning a clear actionable error to the user.

Referred Code
if (!string.IsNullOrWhiteSpace(options.SerialPort))
{
    device = await DaqifiDeviceFactory.ConnectSerialAsync(
        options.SerialPort,
        options.BaudRate,
        connectionOptions);
    connectionDescription = $"{options.SerialPort} @ {options.BaudRate} baud";
}
else
{
    device = await DaqifiDeviceFactory.ConnectTcpAsync(
        options.IpAddress!,
        options.Port,
        connectionOptions);
    connectionDescription = $"{options.IpAddress}:{options.Port}";
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Conflicting flag handling: The CLI parser allows multiple SD flags (--sd-list, --sd-log-start, --sd-log-stop)
simultaneously, but RunSdCardOperationAsync silently prioritizes via if/else if rather
than validating exclusivity and reporting a user error.

Referred Code
if (options.SdList)
{
    Console.WriteLine("Listing SD card files...");
    var files = await streamingDevice.GetSdCardFilesAsync();

    if (files.Count == 0)
    {
        Console.WriteLine("  (no files found)");
    }
    else
    {
        foreach (var file in files)
        {
            var dateStr = file.CreatedDate?.ToString("yyyy-MM-dd HH:mm:ss") ?? "unknown date";
            Console.WriteLine($"  {file.FileName}  ({dateStr})");
        }
    }

    Console.WriteLine($"Total: {files.Count} file(s)");
}
else if (options.SdLogStart)


 ... (clipped 39 lines)

Learn more about managing compliance generic rules or creating your own custom rules

⚪
  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review

qodo-code-review Bot commented Jan 30, 2026 •

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Enforce single SD operation flag

Add a check to ensure that only one of --sd-list, --sd-log-start, or
--sd-log-stop is specified at a time. If multiple flags are used, report an
error to the user to avoid ambiguity.

Program.cs [85-88]

-if (options.SdList || options.SdLogStart || options.SdLogStop)
+var sdFlagsCount = new[] { options.SdList, options.SdLogStart, options.SdLogStop }.Count(f => f);
+if (sdFlagsCount > 1)
 {
-    return await RunSdCardOperationAsync(options);
+    Console.Error.WriteLine("Specify only one SD card operation at a time.");
+    return 1;
 }
+return await RunSdCardOperationAsync(options);
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This suggestion improves the CLI's robustness and user experience by preventing ambiguous command combinations. Enforcing mutually exclusive flags leads to more predictable behavior, which is a significant improvement.

Medium
Remove redundant manual resource cleanup

Remove the redundant finally block containing device.Disconnect(). The using var
_ = device; statement already ensures the device is properly disposed of, making
the explicit disconnection call unnecessary.

Program.cs [339-430]

     using var _ = device;
 
     try
     {
         Console.WriteLine($"Connected to {connectionDescription}");
 ... (clipped 91 lines)
     catch (Exception ex)
     {
         Console.Error.WriteLine($"Error: {FormatException(ex)}");
         return 1;
     }
-    finally
-    {
-        try
-        {
-            device.Disconnect();
-        }
-        catch (Exception ex)
-        {
-            Console.Error.WriteLine($"Disconnect error: {FormatException(ex)}");
-        }
-    }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: This suggestion correctly points out redundant code. Removing the explicit device.Disconnect() call in the finally block simplifies the code and relies on the idiomatic using statement for resource management, assuming Dispose handles disconnection.

Low
High-level
Refactor device connection logic to avoid duplication

Extract the duplicated device connection logic from RunSdCardOperationAsync and
the existing RunStreamingSessionAsync into a single, reusable helper method.
This will centralize connection handling and improve code maintainability.

Examples:

Program.cs [307-337]
    private static async Task<int> RunSdCardOperationAsync(CliOptions options)
    {
        var connectionOptions = new DeviceConnectionOptions
        {
            ConnectionRetry = new ConnectionRetryOptions
            {
                Enabled = options.ConnectAttempts > 1,
                MaxAttempts = Math.Max(1, options.ConnectAttempts),
                ConnectionTimeout = TimeSpan.FromSeconds(options.ConnectTimeoutSeconds)
            }

 ... (clipped 21 lines)

Solution Walkthrough:

Before:

async Task<int> RunSdCardOperationAsync(CliOptions options) {
    // ... connection options setup ...
    DaqifiDevice device;
    if (options.SerialPort != null) {
        device = await DaqifiDeviceFactory.ConnectSerialAsync(...);
    } else {
        device = await DaqifiDeviceFactory.ConnectTcpAsync(...);
    }
    // ... use device for SD card operations ...
}

async Task<int> RunStreamingSessionAsync(CliOptions options) {
    // ... (assumed) duplicated connection logic ...
    DaqifiDevice device;
    if (options.SerialPort != null) {
        device = await DaqifiDeviceFactory.ConnectSerialAsync(...);
    } else {
        device = await DaqifiDeviceFactory.ConnectTcpAsync(...);
    }
    // ... use device for streaming ...
}

After:

async Task<(DaqifiDevice, string)> ConnectToDeviceAsync(CliOptions options) {
    // ... connection options setup ...
    DaqifiDevice device;
    string connectionDescription;
    if (options.SerialPort != null) {
        device = await DaqifiDeviceFactory.ConnectSerialAsync(...);
        connectionDescription = "...";
    } else {
        device = await DaqifiDeviceFactory.ConnectTcpAsync(...);
        connectionDescription = "...";
    }
    return (device, connectionDescription);
}

async Task<int> RunSdCardOperationAsync(CliOptions options) {
    var (device, _) = await ConnectToDeviceAsync(options);
    // ... use device for SD card operations ...
}

async Task<int> RunStreamingSessionAsync(CliOptions options) {
    var (device, _) = await ConnectToDeviceAsync(options);
    // ... use device for streaming ...
}
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies significant code duplication for device connection logic and proposes a valid refactoring that improves maintainability and adheres to the DRY principle.

Medium
Possible issue
Unregister event handler to prevent leaks

To prevent a memory leak, unregister the Console.CancelKeyPress event handler
after use. Store the handler in a variable and remove it within a finally block
to ensure it is always cleaned up.

Program.cs [378-401]

     if (options.DurationSeconds > 0)
     {
         using var cts = new CancellationTokenSource();
         cts.CancelAfter(TimeSpan.FromSeconds(options.DurationSeconds));
 
-        Console.CancelKeyPress += (_, eventArgs) =>
+        ConsoleCancelEventHandler handler = (_, eventArgs) =>
         {
             eventArgs.Cancel = true;
             cts.Cancel();
         };
+        Console.CancelKeyPress += handler;
 
         try
         {
             Console.WriteLine($"Logging for {options.DurationSeconds} seconds (Ctrl+C to stop early)...");
             await Task.Delay(Timeout.InfiniteTimeSpan, cts.Token);
         }
         catch (OperationCanceledException)
         {
             // Expected
         }
+        finally
+        {
+            Console.CancelKeyPress -= handler;
+        }
 
         await streamingDevice.StopSdCardLoggingAsync();
         Console.WriteLine("SD card logging stopped.");
     }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a memory leak caused by not unregistering a static event handler and provides a robust solution using a finally block, which improves the application's resource management.

Medium
  • Update

@tylerkron
tylerkron merged commit 0a9eabc into main Feb 4, 2026
@tylerkron
tylerkron deleted the feature/sd-card-commands branch March 20, 2026 19:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant