Error Handling

SharpMeter uses a Result<T> discriminated union instead of exceptions for all protocol operations. This makes error paths explicit and composable.

Result<T>

Every async operation returns Result<T>:

var result = await client.ReadTableAsync(1);

if (result.IsSuccess)
{
    var data = result.Value;
    // Use data
}
else
{
    var error = result.Error;
    Console.WriteLine($"[{error.Code}] {error.Message}");
}

Pattern Matching

var output = result.Match(
    onSuccess: data => $"Read {data.Length} bytes",
    onFailure: error => $"Failed: {error.Message}");

Map / Bind

// Transform the success value
var lengthResult = result.Map(data => data.Length);

// Chain operations
var chainResult = result.Bind(data =>
    data.Length > 0
        ? Result<string>.Success("OK")
        : PsemError.Framing("Empty response"));

Error Types

PsemError categorizes errors by source:

Code Description
TransportError Serial/TCP connection failure
CrcMismatch CRC-16 verification failed
Timeout Operation timed out
ProtocolError Meter returned an error response
FramingError Invalid frame structure
InvalidState Wrong session state
SecurityFailure Authentication failed
ProcedureError Procedure execution failed

Protocol Response Codes

When the meter returns an error, PsemError.ResponseCode contains the L7 code:

if (result.IsFailure && result.Error.ResponseCode.HasValue)
{
    switch (result.Error.ResponseCode.Value)
    {
        case ResponseCode.InsufficientSecurityClearance:
            // Wrong password or access level
            break;
        case ResponseCode.OperationNotPossible:
            // Table doesn't exist
            break;
        case ResponseCode.DeviceBusy:
            // Retry later
            break;
    }
}

Implicit Conversions

Results convert implicitly for cleaner code:

// Success values convert automatically
Result<int> result = 42;

// Errors convert automatically
Result<int> error = PsemError.Timeout("read");
Last updated: 2026-04-08
Was this page helpful?