Class Sealed
public sealed class TableStore

Namespace: SharpMeter.Emulator

In-memory storage for PSEM table data used by the emulator.

Constructors

NameDescription
TableStore(EmulatorConfiguration config) Initializes a new TableStore and populates standard tables from config.

TableStore(EmulatorConfiguration config)

TableStore.TableStore(EmulatorConfiguration config)

Initializes a new TableStore and populates standard tables from config.

Parameters

NameTypeDescription
configSharpMeter.Emulator.EmulatorConfigurationThe emulator configuration.

Properties

NameDescription
TableIds Gets all table IDs currently stored.

TableIds

IEnumerable<ushort> TableStore.TableIds { get; }

Gets all table IDs currently stored.

Methods

NameDescription
HasTable(ushort tableId) Checks whether a table exists in the store.
ReadTable(ushort tableId) Reads the full data for a table.
ReadTableOffset(…) Reads a portion of a table at the given offset.
WriteTable(ushort tableId, byte[] data) Writes full data to a table.
WriteTableOffset(…) Writes data at a specific offset within a table.

HasTable(ushort tableId)

bool TableStore.HasTable(ushort tableId)

Checks whether a table exists in the store.

ReadTable(ushort tableId)

byte[]? TableStore.ReadTable(ushort tableId)

Reads the full data for a table.

Parameters

NameTypeDescription
tableIdushortThe table ID.

Returns: The table data, or null if the table doesn't exist.

ReadTableOffset(ushort tableId, int offset, int count)

byte[]? TableStore.ReadTableOffset(ushort tableId, int offset, int count)

Reads a portion of a table at the given offset.

Parameters

NameTypeDescription
tableIdushortThe table ID.
offsetintThe byte offset.
countintThe number of bytes to read.

Returns: The partial table data, or null if table doesn't exist or range is invalid.

WriteTable(ushort tableId, byte[] data)

void TableStore.WriteTable(ushort tableId, byte[] data)

Writes full data to a table.

Parameters

NameTypeDescription
tableIdushortThe table ID.
databyte[]The data to write.

WriteTableOffset(ushort tableId, int offset, ReadOnlySpan data)

bool TableStore.WriteTableOffset(ushort tableId, int offset, ReadOnlySpan<byte> data)

Writes data at a specific offset within a table.

Parameters

NameTypeDescription
tableIdushortThe table ID.
offsetintThe byte offset.
dataReadOnlySpan<byte>The data to write.

Returns: True if successful, false if the table doesn't exist or offset is invalid.

View Source
/// <summary>
///     In-memory storage for PSEM table data used by the emulator.
/// </summary>
public sealed class TableStore
{
#region Constructor
    /// <summary>
    ///     Initializes a new <see cref = "TableStore"/> and populates standard tables from config.
    /// </summary>
    /// <param name = "config">The emulator configuration.</param>
    public TableStore(EmulatorConfiguration config)
    {
        _config = config;
        InitializeStandardTables();
    }

#endregion
#region Private Methods
    private void InitializeStandardTables()
    {
        // ST0: General Configuration Table (94 bytes)
        var st0 = new byte[94];
        st0[4] = (byte)_config.Target; // NAMEPLATE_TYPE
        _tables[0] = st0;
        // ST1: General Manufacturer ID Table (32 bytes)
        var st1 = new byte[32];
        Encoding.ASCII.GetBytes(_config.Manufacturer.PadRight(4)[..4]).CopyTo(st1, 0);
        Encoding.ASCII.GetBytes(_config.ModelString.PadRight(8)[..8]).CopyTo(st1, 4);
        st1[12] = _config.HardwareVersion;
        st1[13] = _config.HardwareRevision;
        st1[14] = _config.FirmwareVersion.Length > 0 ? _config.FirmwareVersion[0] : (byte)0;
        st1[15] = _config.FirmwareVersion.Length > 1 ? _config.FirmwareVersion[1] : (byte)0;
        Encoding.ASCII.GetBytes(_config.SerialNumber.PadRight(16)[..16]).CopyTo(st1, 16);
        _tables[1] = st1;
        // ST3: ED Mode Status Table (5 bytes) - all zeros = normal operation
        _tables[3] = new byte[5];
        // ST5: Device Identification Table (20 bytes)
        var st5 = new byte[20];
        Encoding.ASCII.GetBytes(_config.SerialNumber.PadRight(20)[..20]).CopyTo(st5, 0);
        _tables[5] = st5;
        // ST7: Procedure Initiation Table (placeholder for writes)
        _tables[PsemConstants.ProcedureInitiationTable] = new byte[64];
        // ST8: Procedure Response Table (16 bytes)
        _tables[PsemConstants.ProcedureResponseTable] = new byte[16];
        // ST52: Clock Table (7 bytes)
        var st52 = new byte[7];
        DateTime now = DateTime.UtcNow;
        st52[0] = (byte)(now.Year - 2000);
        st52[1] = (byte)now.Month;
        st52[2] = (byte)now.Day;
        st52[3] = (byte)now.Hour;
        st52[4] = (byte)now.Minute;
        st52[5] = (byte)now.Second;
        _tables[52] = st52;
        // MT0: Device Table (73 bytes)
        var mt0 = new byte[73];
        mt0[0] = _config.FirmwareVersion.Length > 0 ? _config.FirmwareVersion[0] : (byte)0;
        mt0[1] = _config.FirmwareVersion.Length > 1 ? _config.FirmwareVersion[1] : (byte)0;
        mt0[12] = (byte)_config.Model;
        mt0[13] = (byte)_config.Mode;
        // Write softswitch upgrades as 4-byte bitfield at offset 21
        var upgrades = (uint)_config.Upgrades;
        mt0[21] = (byte)(upgrades & 0xFF);
        mt0[22] = (byte)((upgrades >> 8) & 0xFF);
        mt0[23] = (byte)((upgrades >> 16) & 0xFF);
        mt0[24] = (byte)((upgrades >> 24) & 0xFF);
        _tables[PsemConstants.ManufacturingTableOffset] = mt0; // 2048
    }

#endregion
#region Fields
    private readonly ConcurrentDictionary<ushort, byte[]> _tables = new();
    private readonly EmulatorConfiguration _config;
#endregion
#region Public Methods
    /// <summary>Reads the full data for a table.</summary>
    /// <param name = "tableId">The table ID.</param>
    /// <returns>The table data, or null if the table doesn't exist.</returns>
    public byte[]? ReadTable(ushort tableId) => _tables.TryGetValue(tableId, out var data) ? data : null;
    /// <summary>Reads a portion of a table at the given offset.</summary>
    /// <param name = "tableId">The table ID.</param>
    /// <param name = "offset">The byte offset.</param>
    /// <param name = "count">The number of bytes to read.</param>
    /// <returns>The partial table data, or null if table doesn't exist or range is invalid.</returns>
    public byte[]? ReadTableOffset(ushort tableId, int offset, int count)
    {
        if (!_tables.TryGetValue(tableId, out var data))
            return null;
        if (offset < 0 || offset + count > data.Length)
            return null;
        return data.AsSpan(offset, count).ToArray();
    }

    /// <summary>Writes full data to a table.</summary>
    /// <param name = "tableId">The table ID.</param>
    /// <param name = "data">The data to write.</param>
    public void WriteTable(ushort tableId, byte[] data) => _tables[tableId] = data;
    /// <summary>Writes data at a specific offset within a table.</summary>
    /// <param name = "tableId">The table ID.</param>
    /// <param name = "offset">The byte offset.</param>
    /// <param name = "data">The data to write.</param>
    /// <returns>True if successful, false if the table doesn't exist or offset is invalid.</returns>
    public bool WriteTableOffset(ushort tableId, int offset, ReadOnlySpan<byte> data)
    {
        if (!_tables.TryGetValue(tableId, out var existing))
            return false;
        if (offset < 0 || offset + data.Length > existing.Length)
            return false;
        data.CopyTo(existing.AsSpan(offset));
        return true;
    }

    /// <summary>Checks whether a table exists in the store.</summary>
    public bool HasTable(ushort tableId) => _tables.ContainsKey(tableId);
    /// <summary>Gets all table IDs currently stored.</summary>
    public IEnumerable<ushort> TableIds => _tables.Keys;
#endregion
}
Was this page helpful?