Skip to content

Hardware Testing Guide for Belay.NET ​

This guide provides comprehensive instructions for validating the Belay.NET implementation with real MicroPython and CircuitPython devices after completing foundation unit tests.

Prerequisites ​

  • Completed Belay.NET foundation implementation with all unit tests passing
  • .NET 6+ SDK installed on target testing machine
  • Access to physical MicroPython/CircuitPython hardware

Required Hardware ​

Primary Testing Hardware (Required) ​

Raspberry Pi Pico ​

  • MicroPython Version: Raspberry Pi Pico with MicroPython firmware v1.20+
  • CircuitPython Version: Raspberry Pi Pico with CircuitPython firmware v8.0+
  • Rationale: Most widely used, stable platform with excellent USB CDC support
  • Connection: USB-C to USB-A cable
  • Identification: Appears as /dev/ttyACM0 (Linux), COMx (Windows), /dev/cu.usbmodem* (macOS)

ESP32 Development Board ​

  • Model: ESP32-DevKitC or ESP32-WROOM-32 based board
  • MicroPython Version: v1.20+ with ESP32 port
  • CircuitPython Version: v8.0+ (limited ESP32 support)
  • Rationale: Tests wireless-capable platform with different USB-Serial implementation
  • Connection: USB-C or Micro-USB cable (board dependent)
  • Identification: Appears as /dev/ttyUSB0 (Linux), COMx (Windows), /dev/cu.usbserial-* (macOS)

STM32-based Pyboard ​

  • Model: Pyboard v1.1 or MicroPython Pyboard D-series
  • MicroPython Version: v1.20+
  • Rationale: Reference implementation platform with native USB CDC
  • Connection: Micro-USB cable
  • Special Features: Tests original MicroPython platform design

Adafruit CircuitPython Boards ​

  • Models: Feather M4 Express, Metro M4 Express, or similar
  • CircuitPython Version: v8.0+
  • Rationale: Tests CircuitPython-specific protocol variations
  • Connection: USB-C or Micro-USB (board dependent)

Hardware Setup Procedures ​

1. Firmware Installation ​

MicroPython Installation ​

bash
# Download firmware from https://micropython.org/download/
# For Raspberry Pi Pico:
# 1. Hold BOOTSEL button while connecting USB
# 2. Copy .uf2 file to mounted RPI-RP2 drive
# 3. Device will reboot automatically

# For ESP32:
pip install esptool
esptool.py --chip esp32 --port /dev/ttyUSB0 erase_flash
esptool.py --chip esp32 --port /dev/ttyUSB0 write_flash -z 0x1000 firmware.bin

CircuitPython Installation ​

bash
# Download firmware from https://circuitpython.org/downloads
# For Raspberry Pi Pico:
# 1. Hold BOOTSEL button while connecting USB
# 2. Copy .uf2 file to mounted RPI-RP2 drive
# 3. Device will reboot and appear as CIRCUITPY drive

2. Connection Verification ​

Linux Connection Test ​

bash
# List available serial ports
ls -la /dev/tty* | grep -E "(USB|ACM)"

# Test basic communication
screen /dev/ttyACM0 115200
# Press Ctrl-C to get REPL prompt
# Type: print("Hello from MicroPython")
# Press Ctrl-A, K, Y to exit screen

Windows Connection Test ​

powershell
# List COM ports
Get-WmiObject Win32_SerialPort | Select-Object DeviceID,Description

# Use PuTTY or Windows Terminal for testing:
# Connect to appropriate COMx port at 115200 baud
# Press Ctrl-C to get REPL prompt
# Type: print("Hello from MicroPython")

macOS Connection Test ​

bash
# List available serial ports
ls -la /dev/cu.*

# Test basic communication
screen /dev/cu.usbmodem* 115200
# Press Ctrl-C to get REPL prompt  
# Type: print("Hello from MicroPython")
# Press Ctrl-A, K, Y to exit screen

3. Device Configuration ​

Enable Raw REPL Testing ​

Create /test_setup.py on the device root filesystem:

python
# test_setup.py - MicroPython/CircuitPython test configuration
import sys
import gc

# Display system information for validation
print(f"MicroPython Version: {sys.version}")
print(f"Platform: {sys.platform}")
print(f"Implementation: {sys.implementation.name}")
print(f"Free Memory: {gc.mem_free()} bytes")

# Test functions for validation
def test_basic_execution():
    return "basic_execution_success"

def test_exception_handling():
    raise ValueError("Test exception for error handling validation")

def test_large_output():
    return "x" * 1000  # Test protocol buffer handling

def test_multiline_code():
    result = []
    for i in range(5):
        result.append(f"Line {i}")
    return result

# Memory and performance test functions
def test_memory_usage():
    import gc
    gc.collect()
    return gc.mem_free()

def test_timing():
    import time
    start = time.ticks_ms()
    # Simulate some work
    for i in range(1000):
        pass
    end = time.ticks_ms()
    return time.ticks_diff(end, start)

Testing Protocols and Validation Procedures ​

1. Device Factory Pattern Validation ​

Test Connection String Parsing ​

csharp
// Test file: DeviceFactoryHardwareTests.cs
[Test]
[Category("Hardware")]
public async Task TestSerialConnectionStringParsing()
{
    // Windows format
    var device1 = Device.Create("serial://COM3?baudrate=115200");
    Assert.IsInstanceOf<SerialDevice>(device1);
    
    // Linux format  
    var device2 = Device.Create("serial:///dev/ttyACM0?baudrate=115200");
    Assert.IsInstanceOf<SerialDevice>(device2);
    
    // macOS format
    var device3 = Device.Create("serial:///dev/cu.usbmodem143201?baudrate=115200");
    Assert.IsInstanceOf<SerialDevice>(device3);
}

Test Device Discovery ​

csharp
[Test]
[Category("Hardware")]
public void TestDeviceDiscovery()
{
    var devices = Device.DiscoverSerialDevices();
    Assert.IsTrue(devices.Count > 0, "No MicroPython devices found");
    
    foreach (var port in devices)
    {
        Console.WriteLine($"Found device: {port}");
    }
}

2. Raw REPL Protocol Communication Validation ​

Protocol State Management Tests ​

csharp
[Test]
[Category("Hardware")]
public async Task TestRawReplStateTransitions()
{
    using var device = Device.Create(GetTestDeviceConnectionString());
    await device.ConnectAsync();
    
    // Test entering raw mode
    await device.EnterRawModeAsync();
    Assert.AreEqual(ReplState.Raw, device.CurrentState);
    
    // Test executing code in raw mode
    var result = await device.ExecuteAsync<string>("print('raw_mode_test')");
    Assert.AreEqual("raw_mode_test", result);
    
    // Test exiting raw mode
    await device.ExitRawModeAsync();
    Assert.AreEqual(ReplState.Normal, device.CurrentState);
}

Flow Control Validation ​

csharp
[Test]
[Category("Hardware")]
public async Task TestFlowControlHandling()
{
    using var device = Device.Create(GetTestDeviceConnectionString());
    await device.ConnectAsync();
    
    // Test large code execution requiring flow control
    var largeCode = "result = " + string.Join(" + ", Enumerable.Range(1, 100));
    var result = await device.ExecuteAsync<int>(largeCode + "; print(result)");
    
    Assert.AreEqual(5050, result); // Sum of 1 to 100
}

Raw-Paste Mode Validation ​

csharp
[Test]
[Category("Hardware")]
public async Task TestRawPasteModeProtocol()
{
    using var device = Device.Create(GetTestDeviceConnectionString());
    await device.ConnectAsync();
    
    // Multi-line code requiring raw-paste mode
    var multilineCode = @"
def calculate_fibonacci(n):
    if n <= 1:
        return n
    return calculate_fibonacci(n-1) + calculate_fibonacci(n-2)

result = calculate_fibonacci(10)
print(result)";
    
    var result = await device.ExecuteAsync<int>(multilineCode);
    Assert.AreEqual(55, result); // 10th Fibonacci number
}

3. Serial Device Communication Implementation Tests ​

Connection Resilience ​

csharp
[Test]
[Category("Hardware")]
public async Task TestConnectionResilience()
{
    using var device = Device.Create(GetTestDeviceConnectionString());
    
    // Test initial connection
    await device.ConnectAsync();
    Assert.IsTrue(device.IsConnected);
    
    // Simulate disconnect/reconnect cycle
    await device.DisconnectAsync();
    Assert.IsFalse(device.IsConnected);
    
    await device.ConnectAsync();
    Assert.IsTrue(device.IsConnected);
    
    // Verify device still works after reconnection
    var result = await device.ExecuteAsync<string>("print('reconnect_test')");
    Assert.AreEqual("reconnect_test", result);
}

Timeout and Cancellation Handling ​

csharp
[Test]
[Category("Hardware")]
public async Task TestTimeoutHandling()
{
    using var device = Device.Create(GetTestDeviceConnectionString());
    await device.ConnectAsync();
    
    // Test operation timeout
    var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
    
    var ex = await Assert.ThrowsAsync<OperationCanceledException>(async () =>
    {
        // Code that takes longer than 2 seconds
        await device.ExecuteAsync("import time; time.sleep(5)", cts.Token);
    });
}

4. Error Handling and Reconnection Logic Tests ​

Exception Mapping Validation ​

csharp
[Test]
[Category("Hardware")]
public async Task TestPythonExceptionMapping()
{
    using var device = Device.Create(GetTestDeviceConnectionString());
    await device.ConnectAsync();
    
    // Test ValueError mapping
    var ex = await Assert.ThrowsAsync<DeviceExecutionException>(async () =>
    {
        await device.ExecuteAsync("raise ValueError('Test error')");
    });
    
    Assert.Contains("ValueError", ex.Message);
    Assert.Contains("Test error", ex.Message);
}

Automatic Reconnection Logic ​

csharp
[Test]
[Category("Hardware")]
public async Task TestAutomaticReconnection()
{
    var deviceConfig = new DeviceConfiguration
    {
        ConnectionString = GetTestDeviceConnectionString(),
        AutoReconnect = true,
        ReconnectAttempts = 3,
        ReconnectDelay = TimeSpan.FromSeconds(1)
    };
    
    using var device = Device.Create(deviceConfig);
    await device.ConnectAsync();
    
    // Simulate connection loss (would need physical disconnect/reconnect in real test)
    // This test validates the retry logic configuration
    Assert.IsTrue(device.IsConfiguredForAutoReconnect);
}

Platform-Specific Considerations ​

Windows Serial Port Handling ​

COM Port Enumeration ​

csharp
// Windows-specific device discovery
public static class WindowsDeviceDiscovery
{
    public static List<string> GetMicroPythonPorts()
    {
        var ports = new List<string>();
        var searcher = new ManagementObjectSearcher(
            "SELECT * FROM Win32_PnPEntity WHERE Caption like '%(COM%'");
        
        foreach (ManagementObject queryObj in searcher.Get())
        {
            var caption = queryObj["Caption"]?.ToString();
            if (caption != null && (caption.Contains("USB") || caption.Contains("Serial")))
            {
                var match = Regex.Match(caption, @"COM(\d+)");
                if (match.Success)
                {
                    ports.Add($"COM{match.Groups[1].Value}");
                }
            }
        }
        return ports;
    }
}

Windows Permission Issues ​

  • Requirement: Run tests with appropriate user permissions
  • Issue: COM port access may require administrator privileges
  • Solution: Add user to "Dialout" equivalent group or run as administrator

Linux Serial Port Handling ​

Device Permission Configuration ​

bash
# Add user to dialout group for serial port access
sudo usermod -a -G dialout $USER
# Logout and login again for changes to take effect

# Alternative: Set specific device permissions
sudo chmod 666 /dev/ttyACM0
sudo chmod 666 /dev/ttyUSB0

udev Rules for Consistent Device Naming ​

bash
# Create /etc/udev/rules.d/99-micropython.rules
SUBSYSTEM=="tty", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0005", SYMLINK+="micropython-pico"
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="micropython-esp32"

# Reload udev rules
sudo udevadm control --reload-rules
sudo udevadm trigger

macOS Serial Port Handling ​

System Extension Requirements ​

  • Issue: macOS may block access to USB serial devices
  • Solution: Grant terminal emulator applications full disk access in System Preferences

Device Path Patterns ​

csharp
// macOS device path detection
public static class MacOSDeviceDiscovery
{
    public static List<string> GetMicroPythonPorts()
    {
        var ports = Directory.GetFiles("/dev", "cu.*")
            .Where(p => p.Contains("usbmodem") || p.Contains("usbserial"))
            .ToList();
        
        return ports;
    }
}

Common Hardware Issues and Troubleshooting ​

Connection Issues ​

Device Not Detected ​

Symptoms: Device doesn't appear in port listings Causes:

  • USB cable is power-only (no data lines)
  • Driver issues on Windows
  • Insufficient permissions on Linux/macOS
  • Device in bootloader mode instead of runtime mode

Solutions:

bash
# Linux: Check for device detection
dmesg | tail -20
lsusb | grep -i "micro\|circuit\|pi"

# Windows: Check Device Manager for unknown devices
# macOS: Check System Information > USB

Permission Denied Errors ​

Symptoms: "Access denied" or "Permission denied" when opening serial port Solutions:

bash
# Linux: Add user to dialout group
sudo usermod -a -G dialout $(whoami)
# Logout/login required

# Temporary fix
sudo chmod 666 /dev/ttyACM0

Communication Protocol Issues ​

Raw REPL Entry Failures ​

Symptoms: Device doesn't respond to Ctrl-A sequence Causes:

  • Device already in raw mode
  • Incorrect baud rate
  • Device is executing blocking code

Solutions:

csharp
// Recovery sequence
await device.SendBreakAsync();           // Interrupt any running code
await device.SendAsync("\x02");         // Exit raw mode if already in it
await Task.Delay(100);
await device.SendAsync("\x01");         // Enter raw mode

Flow Control Problems ​

Symptoms: Large code transfers fail or timeout Causes:

  • Window size mismanagement
  • Missing flow control byte handling
  • Buffer overflow on device

Solutions:

  • Implement proper window size tracking
  • Respect device flow control signals
  • Add configurable timeouts for different operation types

Character Encoding Issues ​

Symptoms: Non-ASCII characters corrupted or cause errors Solutions:

csharp
// Always use UTF-8 encoding for MicroPython communication
var encoding = new UTF8Encoding(false); // No BOM
var bytes = encoding.GetBytes(pythonCode);

Device-Specific Quirks ​

Raspberry Pi Pico ​

  • Reset Behavior: Automatically resets when serial connection opens
  • Boot Delay: May require 1-2 second delay after connection before communication
  • Memory Constraints: Limited heap space for large operations

ESP32 Boards ​

  • Boot Messages: Sends boot information over UART0, may interfere with REPL
  • Reset Requirements: May need hardware reset (EN pin) for reliable operation
  • Flash Mode: Can accidentally enter download mode if boot pins are held

CircuitPython Differences ​

  • REPL Prompt: Shows ">>>" instead of ">>>" after some operations
  • Auto-reload: May restart when files change on CIRCUITPY drive
  • Module Differences: Some MicroPython modules unavailable

Validation Test Scenarios ​

Manual Testing Procedures ​

1. Basic Communication Test ​

bash
# Connect to device via terminal
screen /dev/ttyACM0 115200

# Test sequence:
1. Press Ctrl-C (should see REPL prompt)
2. Type: print("Hello World")
3. Press Enter (should see output)
4. Press Ctrl-A (should see "raw REPL; CTRL-B to exit")
5. Type: print("Raw mode test")
6. Press Ctrl-D (should see output and "OK")
7. Press Ctrl-B (should return to normal REPL)

2. Belay.NET Integration Test ​

csharp
// Create comprehensive integration test
[Test]
[Category("Hardware")]
[Category("Manual")]
public async Task ComprehensiveDeviceValidation()
{
    var connectionString = GetTestDeviceConnectionString(); // Set manually for target device
    
    using var device = Device.Create(connectionString);
    
    // Test 1: Connection
    await device.ConnectAsync();
    Assert.IsTrue(device.IsConnected);
    
    // Test 2: Basic execution
    var result = await device.ExecuteAsync<string>("print('integration_test')");
    Assert.AreEqual("integration_test", result);
    
    // Test 3: Complex data types
    var listResult = await device.ExecuteAsync<List<int>>("print([1, 2, 3, 4, 5])");
    Assert.AreEqual(new List<int> {1, 2, 3, 4, 5}, listResult);
    
    // Test 4: Error handling
    var ex = await Assert.ThrowsAsync<DeviceExecutionException>(
        () => device.ExecuteAsync("raise RuntimeError('Test error')"));
    Assert.Contains("RuntimeError", ex.Message);
    
    // Test 5: Large data transfer
    var largeData = string.Join("", Enumerable.Range(0, 1000).Select(i => i.ToString()));
    var largeResult = await device.ExecuteAsync<string>($"print('{largeData}')");
    Assert.AreEqual(largeData, largeResult);
    
    // Test 6: Multiple rapid operations
    var tasks = Enumerable.Range(0, 10)
        .Select(i => device.ExecuteAsync<int>($"print({i})"))
        .ToArray();
    
    var results = await Task.WhenAll(tasks);
    Assert.AreEqual(Enumerable.Range(0, 10).ToArray(), results);
}

3. Performance Benchmark Test ​

csharp
[Test]
[Category("Hardware")]
[Category("Performance")]
public async Task PerformanceBenchmarks()
{
    using var device = Device.Create(GetTestDeviceConnectionString());
    await device.ConnectAsync();
    
    // Measure simple execution latency
    var stopwatch = Stopwatch.StartNew();
    for (int i = 0; i < 100; i++)
    {
        await device.ExecuteAsync<int>($"print({i})");
    }
    stopwatch.Stop();
    
    var averageLatency = stopwatch.ElapsedMilliseconds / 100.0;
    Console.WriteLine($"Average execution latency: {averageLatency:F2}ms");
    
    // Measure throughput with large data
    stopwatch.Restart();
    var largeCode = "result = " + string.Join(" + ", Enumerable.Range(1, 1000));
    await device.ExecuteAsync<int>(largeCode + "; print(result)");
    stopwatch.Stop();
    
    Console.WriteLine($"Large operation time: {stopwatch.ElapsedMilliseconds}ms");
}

Automated Hardware Test Suite ​

Create a test configuration file for different hardware setups:

json
{
  "hardware_configurations": [
    {
      "name": "RaspberryPi_Pico_MicroPython",
      "connection_string": "serial:///dev/ttyACM0?baudrate=115200",
      "firmware": "MicroPython v1.20+",
      "expected_platform": "rp2",
      "test_categories": ["basic", "performance", "memory"]
    },
    {
      "name": "ESP32_MicroPython", 
      "connection_string": "serial:///dev/ttyUSB0?baudrate=115200",
      "firmware": "MicroPython v1.20+",
      "expected_platform": "esp32",
      "test_categories": ["basic", "performance"]
    }
  ]
}

Test Execution Commands ​

Run Hardware Tests ​

bash
# Run all hardware tests
dotnet test --filter "Category=Hardware" --logger console

# Run tests for specific device type
dotnet test --filter "Category=Hardware&Category=RaspberryPi" --logger console

# Run performance benchmarks
dotnet test --filter "Category=Performance" --logger console

# Generate detailed test report
dotnet test --filter "Category=Hardware" --logger "trx;LogFileName=hardware_tests.trx"

Continuous Hardware Testing ​

bash
# Set up for CI/CD with hardware in the loop
export BELAY_TEST_DEVICE="serial:///dev/ttyACM0?baudrate=115200"
dotnet test --filter "Category=Hardware" --configuration Release

Success Criteria ​

A successful hardware validation should demonstrate:

  1. Connection Management: Reliable connection/disconnection cycles
  2. Protocol Compliance: Proper Raw REPL and Raw-Paste mode implementation
  3. Error Handling: Correct mapping of device exceptions to .NET exceptions
  4. Performance: Acceptable latency (<100ms for simple operations)
  5. Reliability: 100% success rate for basic operations over 100 iterations
  6. Cross-Platform: Tests pass on Windows, Linux, and macOS
  7. Multi-Device: Tests pass on both MicroPython and CircuitPython devices
  8. Recovery: Graceful handling of connection loss and automatic reconnection

After successful hardware validation, the implementation is ready for integration into larger applications and advanced feature development.

Released under the Apache License 2.0.