Emulator Customization
The SharpMeter emulator supports custom table data, procedure behavior, and full state inspection for advanced testing scenarios.
Custom Table Data
Pre-populating Tables
Use TableStore to seed tables before connecting:
var emulator = new MeterEmulator(config, logger);
// Add a custom manufacturing table (MT64)
var mt64Data = new byte[104];
mt64Data[0] = 0x02; // Equation: 2-Element 3W
mt64Data[1] = 0x01; // Line frequency: 60 Hz
emulator.Tables.WriteTable(2112, mt64Data); // MT64 = 2048 + 64
// Override a standard table
var customSt5 = System.Text.Encoding.ASCII.GetBytes("CUSTOM_SERIAL_00001\0");
emulator.Tables.WriteTable(5, customSt5);
Dynamic Tables
Tables can be added or modified at any point — even mid-session:
await client.ConnectAsync(password: null);
// Simulate a meter event by modifying ST3 (status flags)
var st3 = emulator.Tables.ReadTable(3) ?? new byte[5];
st3[1] |= 0x01; // Set UNPROGRAMMED_FLAG
emulator.Tables.WriteTable(3, st3);
// Now the client reads the updated status
var result = await client.ReadTableAsync(3);
Inspecting Tables After Writes
Verify what the client wrote:
await client.WriteTableAsync(100, new byte[] { 0xAA, 0xBB, 0xCC });
// Inspect from the emulator side
var written = emulator.Tables.ReadTable(100);
Assert.Equal(new byte[] { 0xAA, 0xBB, 0xCC }, written);
Listing All Tables
foreach (var tableId in emulator.Tables.TableIds)
{
var data = emulator.Tables.ReadTable(tableId);
var prefix = tableId >= 2048 ? $"MT{tableId - 2048}" : $"ST{tableId}";
Console.WriteLine($"{prefix}: {data?.Length ?? 0} bytes");
}
Partial Table Reads
The emulator supports offset reads for testing chunked/streaming access:
// Write a 256-byte table
var bigTable = new byte[256];
for (int i = 0; i < 256; i++) bigTable[i] = (byte)i;
emulator.Tables.WriteTable(200, bigTable);
// Read in chunks via the client
await foreach (var chunk in client.StreamTableAsync(200, chunkSize: 64, totalSize: 256))
{
Assert.True(chunk.IsSuccess);
// First chunk: bytes 0-63
// Second chunk: bytes 64-127
// etc.
}
Procedure Handling
All procedures written to ST7 are automatically "completed" with ProcedureResult.Completed in ST8. You can inspect what was executed:
// Execute a standard procedure
await client.ExecuteStandardProcedureAsync(0); // Cold Start
// Check what was written to ST7
var st7 = emulator.Tables.ReadTable(7);
// st7[0..1] = procedure ID
// Check the response in ST8
var st8 = emulator.Tables.ReadTable(8);
// st8[3] = ProcedureResult.Completed (0x00)
State Machine Inspection
Monitor the emulator state for test assertions:
Assert.Equal(EmulatorState.Idle, emulator.State);
await client.ConnectAsync(password: null);
Assert.Equal(EmulatorState.Authenticated, emulator.State);
Assert.Equal(AccessLevel.Master, emulator.CurrentAccessLevel);
await client.DisconnectAsync();
Assert.Equal(EmulatorState.Idle, emulator.State);
Security Testing
Password Enforcement
var config = new EmulatorConfiguration
{
Password = new byte[] { 0x01, 0x02, 0x03, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
ReadAccessLevel = AccessLevel.Reader,
WriteAccessLevel = AccessLevel.Master,
ProcedureAccessLevel = AccessLevel.Master
};
var emulator = new MeterEmulator(config, logger);
Access Level Testing
// Connect without password — gets NoAccess after logon
var result = await client.ConnectAsync(password: null);
// Reads work if ReadAccessLevel is NoAccess
var table = await client.ReadTableAsync(1);
Assert.True(table.IsSuccess);
// Writes fail if WriteAccessLevel requires Master
var writeResult = await client.WriteTableAsync(100, new byte[] { 0x01 });
Assert.True(writeResult.IsFailure);
Pre-populated Standard Tables
The emulator initializes these tables from EmulatorConfiguration:
| Table | Name | Source |
|---|---|---|
| ST0 | General Configuration | Target field from config |
| ST1 | Manufacturer ID | Manufacturer, ModelString, SerialNumber, FirmwareVersion |
| ST3 | Mode/Status | All zeros (normal operation) |
| ST5 | Device Identification | SerialNumber (20 chars) |
| ST7 | Procedure Initiation | Empty (64 bytes, write target) |
| ST8 | Procedure Response | Empty (16 bytes, read after procedure) |
| ST52 | Clock | Current UTC time |
| MT0 | Device Table | Model, Mode, Upgrades bitfield |
Last updated: 2026-04-08