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