ParsedTable using a TableDefinition." /> ParsedTable using a TableDefinition." /> ParsedTable using a TableDefinition." />
Class Static
public static class TableDeserializer

Namespace: SharpMeter.Core.Tables

Deserializes raw PSEM table bytes into ParsedTable using a TableDefinition.

Methods

NameDescription
Deserialize(TableDefinition definition, ReadOnlyMemory<byte> data) static Parses raw table bytes using the provided table definition.

Deserialize(TableDefinition definition, ReadOnlyMemory data)

Result<ParsedTable> TableDeserializer.Deserialize(TableDefinition definition, ReadOnlyMemory<byte> data)

Parses raw table bytes using the provided table definition.

Parameters

NameTypeDescription
definitionSharpMeter.Core.Tables.TableDefinitionThe table structure definition.
dataReadOnlyMemory<byte>The raw table bytes.

Returns: A fully parsed table with named fields, or an error.

View Source
/// <summary>
///     Deserializes raw PSEM table bytes into <see cref = "ParsedTable"/> using a <see cref = "TableDefinition"/>.
/// </summary>
public static class TableDeserializer
{
#region Public Methods
    /// <summary>
    ///     Parses raw table bytes using the provided table definition.
    /// </summary>
    /// <param name = "definition">The table structure definition.</param>
    /// <param name = "data">The raw table bytes.</param>
    /// <returns>A fully parsed table with named fields, or an error.</returns>
    public static Result<ParsedTable> Deserialize(TableDefinition definition, ReadOnlyMemory<byte> data)
    {
        ArgumentNullException.ThrowIfNull(definition);
        if (definition.ExpectedSize > 0 && data.Length < definition.ExpectedSize)
            return PsemError.Framing($"Table {definition.DisplayName} expected {definition.ExpectedSize} bytes, got {data.Length}");
        ImmutableArray<ParsedTableField>.Builder fields = ImmutableArray.CreateBuilder<ParsedTableField>(definition.Fields.Length);
        foreach (TableFieldDefinition fieldDef in definition.Fields)
        {
            if (fieldDef.Offset + fieldDef.Size > data.Length)
                break; // Partial table — stop parsing
            ReadOnlyMemory<byte> rawBytes = data.Slice(fieldDef.Offset, fieldDef.Size);
            ParsedTableField parsed = ParseField(fieldDef, rawBytes);
            fields.Add(parsed);
        }

        return new ParsedTable
        {
            Definition = definition,
            RawData = data,
            Fields = fields.ToImmutable()
        };
    }

#endregion
#region Field Parsing
    private static ParsedTableField ParseField(TableFieldDefinition def, ReadOnlyMemory<byte> rawBytes)
    {
        ReadOnlySpan<byte> span = rawBytes.Span;
        var(displayValue, numericValue) = def.Type switch
        {
            FieldType.Ascii => (Encoding.ASCII.GetString(span).TrimEnd('\0', ' '), -1),
            FieldType.String => (Encoding.UTF8.GetString(span).TrimEnd('\0'), -1),
            FieldType.Hex => (Convert.ToHexString(span), -1),
            FieldType.UInt8 when span.Length >= 1 => (span[0].ToString(CultureInfo.InvariantCulture), span[0]),
            FieldType.UInt16 when span.Length >= 2 => ParseUInt16(span),
            FieldType.UInt32 when span.Length >= 4 => ParseUInt32(span),
            FieldType.Int8 when span.Length >= 1 => (((sbyte)span[0]).ToString(CultureInfo.InvariantCulture), (sbyte)span[0]),
            FieldType.Int16 when span.Length >= 2 => ParseInt16(span),
            FieldType.Int32 when span.Length >= 4 => ParseInt32(span),
            FieldType.Boolean when span.Length >= 1 => (span[0] != 0 ? "True" : "False", span[0] != 0 ? 1L : 0L),
            FieldType.Enum when span.Length >= 1 => ParseEnum(def, span[0]),
            FieldType.BitArray => ParseBitArray(span),
            FieldType.BitField => ("(bit field)", -1),
            FieldType.Bcd => ParseBcd(span),
            FieldType.DateTime => ParseDateTime(span),
            _ => (Convert.ToHexString(span), -1)};
        ImmutableArray<ParsedBitField> bitFields = def.Type == FieldType.BitField ? ParseBitFields(def, span) : ImmutableArray<ParsedBitField>.Empty;
        return new ParsedTableField
        {
            Definition = def,
            RawBytes = rawBytes,
            DisplayValue = displayValue,
            NumericValue = numericValue,
            BitFields = bitFields
        };
    }

    private static (string Display, long Numeric) ParseUInt16(ReadOnlySpan<byte> span)
    {
        var value = (ushort)((span[0] << 8) | span[1]);
        return (value.ToString(CultureInfo.InvariantCulture), value);
    }

    private static (string Display, long Numeric) ParseUInt32(ReadOnlySpan<byte> span)
    {
        var value = (uint)((span[0] << 24) | (span[1] << 16) | (span[2] << 8) | span[3]);
        return (value.ToString(CultureInfo.InvariantCulture), value);
    }

    private static (string Display, long Numeric) ParseInt16(ReadOnlySpan<byte> span)
    {
        var value = (short)((span[0] << 8) | span[1]);
        return (value.ToString(CultureInfo.InvariantCulture), value);
    }

    private static (string Display, long Numeric) ParseInt32(ReadOnlySpan<byte> span)
    {
        var value = (span[0] << 24) | (span[1] << 16) | (span[2] << 8) | span[3];
        return (value.ToString(CultureInfo.InvariantCulture), value);
    }

    private static (string Display, long Numeric) ParseEnum(TableFieldDefinition def, byte value)
    {
        if (def.EnumValues.TryGetValue(value, out var name))
            return ($"{name} ({value})", value);
        return (value.ToString(CultureInfo.InvariantCulture), value);
    }

    private static (string Display, long Numeric) ParseBitArray(ReadOnlySpan<byte> span)
    {
        var sb = new StringBuilder();
        for (var byteIdx = 0; byteIdx < span.Length; byteIdx++)
            for (var bit = 0; bit < 8; bit++)
                if ((span[byteIdx] & (1 << bit)) != 0)
                {
                    if (sb.Length > 0)
                        sb.Append(", ");
                    sb.Append(byteIdx * 8 + bit);
                }

        return (sb.Length > 0 ? sb.ToString() : "(none)", -1);
    }

    private static (string Display, long Numeric) ParseBcd(ReadOnlySpan<byte> span)
    {
        var sb = new StringBuilder(span.Length * 2);
        foreach (var b in span)
        {
            sb.Append((char)('0' + (b >> 4)));
            sb.Append((char)('0' + (b & 0x0F)));
        }

        return (sb.ToString(), -1);
    }

    private static (string Display, long Numeric) ParseDateTime(ReadOnlySpan<byte> span)
    {
        if (span.Length < 6)
            return (Convert.ToHexString(span), -1);
        var year = 2000 + span[0];
        return ($"{year:D4}-{span[1]:D2}-{span[2]:D2} {span[3]:D2}:{span[4]:D2}:{span[5]:D2}", -1);
    }

    private static ImmutableArray<ParsedBitField> ParseBitFields(TableFieldDefinition def, ReadOnlySpan<byte> span)
    {
        if (def.BitFields.IsDefaultOrEmpty)
            return[];
        // Convert bytes to a single integer value for bit extraction
        long rawValue = 0;
        for (var i = 0; i < Math.Min(span.Length, 8); i++)
            rawValue |= (long)span[i] << (i * 8);
        ImmutableArray<ParsedBitField>.Builder builder = ImmutableArray.CreateBuilder<ParsedBitField>(def.BitFields.Length);
        foreach (BitFieldDefinition bitDef in def.BitFields)
        {
            var mask = (1L << bitDef.BitCount) - 1;
            var value = (int)((rawValue >> bitDef.StartBit) & mask);
            string display;
            if (bitDef.EnumValues is not null && bitDef.EnumValues.TryGetValue(value, out var enumName))
                display = $"{enumName} ({value})";
            else if (bitDef.BitCount == 1)
                display = value != 0 ? "True" : "False";
            else
                display = value.ToString(CultureInfo.InvariantCulture);
            builder.Add(new ParsedBitField(bitDef.Name, value, display));
        }

        return builder.ToImmutable();
    }
#endregion
}
Was this page helpful?