A modern, high-performance .NET library for smart meter communication supporting both ANSI C12.18/C12.19 PSEM and DLMS/COSEM (IEC 62056) protocols.
- Dual Protocol — ANSI C12.18/C12.19 PSEM and DLMS/COSEM (IEC 62056)
- Fully Async — All I/O uses async/await with
CancellationToken - Zero-Allocation —
Span<T>,ArrayPool<T>,System.IO.Pipelineson hot paths - Pluggable Transport —
ITransportinterface for serial, TCP, loopback, or custom - Table Deserialization — Three strategies: fluent builder, attribute mapping, JSON schema
- Meter Emulator — Full PSEM emulator with configurable tables, state machine, and security
- Result<T> — No exceptions for control flow; explicit error paths
- IAsyncEnumerable — Stream large table reads as async enumerables
- ILogger<T> — Microsoft.Extensions.Logging with source-generated delegates
- Multi-Target — net9.0 and net10.0
| Package | Description |
|---|---|
| SharpMeter.Core | Protocol types, CRC-16, Result<T>, enums, table definitions and deserializer |
| SharpMeter.Transport | ITransport interface, serial, TCP, and loopback transports |
| SharpMeter.Client | PSEM client — session, table, and procedure services |
| SharpMeter.Dlms | DLMS/COSEM — HDLC framing, OBIS codes, A-XDR codec, GET/SET/ACTION |
| SharpMeter.Emulator | Full PSEM meter emulator with in-memory table store |
dotnet add package SharpMeter.Client # PSEM (ANSI C12.18)
dotnet add package SharpMeter.Dlms # DLMS/COSEM (IEC 62056)
dotnet add package SharpMeter.Emulator # Testingusing SharpMeter.Client;
using SharpMeter.Transport;
using Microsoft.Extensions.Logging.Abstractions;
var options = new TransportOptions { PortName = "COM3", BaudRate = 9600 };
await using var transport = new SerialTransport(options, NullLogger<SerialTransport>.Instance);
await using var client = new PsemClient(transport, NullLogger<PsemClient>.Instance);
var result = await client.ConnectAsync(userId: 2, userName: "SharpMeter", password: password);
if (result.IsSuccess)
{
var table = await client.ReadTableAsync(1); // ST1: Manufacturer ID
await client.DisconnectAsync();
}Raw table bytes to named, typed fields — three ways:
using SharpMeter.Core.Tables;
// 1. Built-in definitions (ST0, ST1, ST3, ST5, ST8, ST52)
var registry = new TableRegistry();
var parsed = registry.Deserialize(1, tableData);
Console.WriteLine(parsed.Value.GetValue("MANUFACTURER")); // "GE"
// 2. Attribute mapping — strongly typed
[PsemTable(1, "Manufacturer ID", ExpectedSize = 32)]
public sealed record MfgTable
{
[TableField(0, 4, FieldType.Ascii)]
public string Manufacturer { get; init; } = "";
}
var typed = TableMapper.Deserialize<MfgTable>(tableData);
// 3. JSON schema — external table packs
registry.LoadFromJsonFile("tables/vendor-tables.json");
// 4. Fluent builder — inline definitions
registry.Register(TableDefinitionBuilder.Manufacturing(64, "Config")
.WithSize(104)
.UInt8("MODE").Ascii("SERIAL", 16)
.Build());using SharpMeter.Dlms;
using SharpMeter.Dlms.Enums;
var dlmsOptions = new DlmsClientOptions
{
Authentication = AuthenticationMechanism.LowLevelSecurity,
Password = "password"u8.ToArray()
};
await using var transport = new TcpTransport(
new TransportOptions { Host = "192.168.1.100", Port = 4059 },
NullLogger<TcpTransport>.Instance);
await using var client = new DlmsClient(transport, dlmsOptions, NullLogger<DlmsClient>.Instance);
await client.ConnectAsync();
var energy = await client.GetAttributeAsync(3, ObisCode.ActiveEnergyImportTotal, 2);
await client.DisconnectAsync();using SharpMeter.Client;
using SharpMeter.Emulator;
var emulator = new MeterEmulator(
new EmulatorConfiguration { Manufacturer = "TEST", SerialNumber = "TEST0000000000001" },
NullLogger<MeterEmulator>.Instance);
await using var transport = new EmulatorTransport(emulator, NullLogger<EmulatorTransport>.Instance);
await using var client = new PsemClient(transport, NullLogger<PsemClient>.Instance);
await client.ConnectAsync(password: null);
var table = await client.ReadTableAsync(1);
await client.DisconnectAsync();Full documentation is available at jacobwi.github.io/SharpMeter — built with MokaDocs.
Includes:
- Getting Started guide
- PSEM Protocol reference (session lifecycle, table reads, procedures)
- Reading & Parsing Tables (ST vs MT, fluent builder, attribute mapping, JSON schema)
- DLMS/COSEM guide (HDLC, OBIS, A-XDR, authentication)
- Transport layer (serial, TCP, loopback, custom)
- Meter emulator customization
- Error handling with Result<T>
- Binary serialization patterns
- Full API reference (auto-generated)
dotnet restore
dotnet build
dotnet test
# Serve documentation locally
dotnet tool install --global mokadocs
mokadocs serveMIT