State_SEER.cs 4.23 KB
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Rcs.Domain.Attributes;

namespace Rcs.Domain.Models.VDA5050.SEER;

/// <summary>
/// SEER(仙工)状态消息扩展。
/// 通过自定义转换器兼容 information 中的动态对象/数组字段。
/// </summary>
[ProtocolInfo("SEER", "2.0.0", nameof(State))]
[JsonConverter(typeof(StateSeerJsonConverter))]
public class State_SEER : State
{
    /// <summary>
    /// 货叉机构状态(SEER扩展字段)
    /// </summary>
    [JsonPropertyName("forkState")]
    public SeerForkState? ForkState { get; set; }

    /// <summary>
    /// 是否等待交互区释放(SEER扩展字段,可忽略)
    /// </summary>
    [JsonPropertyName("waitingForInteractionZoneRelease")]
    public bool? WaitingForInteractionZoneRelease { get; set; }
}

/// <summary>
/// SEER货叉状态
/// </summary>
public class SeerForkState
{
    /// <summary>
    /// 货叉高度
    /// </summary>
    [JsonPropertyName("forkHeight")]
    public double? ForkHeight { get; set; }
}

internal sealed class StateSeerJsonConverter : JsonConverter<State_SEER>
{
    public override State_SEER? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var rootNode = JsonNode.Parse(ref reader) as JsonObject
            ?? throw new JsonException("SEER state payload is not a JSON object.");

        // SEER 的 information.infoReferences.referenceValue 可能是对象/数组,
        // 先移除该字段后按基础 State 反序列化,避免 string 类型转换错误。
        var baseNode = rootNode.DeepClone() as JsonObject ?? new JsonObject();
        baseNode.Remove("information");

        var baseState = baseNode.Deserialize<State>(options) ?? new State();
        var seerState = new State_SEER();
        CopyState(baseState, seerState);

        if (rootNode.TryGetPropertyValue("forkState", out var forkNode) && forkNode is not null)
        {
            seerState.ForkState = forkNode.Deserialize<SeerForkState>(options);
        }

        if (rootNode.TryGetPropertyValue("waitingForInteractionZoneRelease", out var waitNode)
            && waitNode is JsonValue waitValue
            && waitValue.TryGetValue<bool>(out var waiting))
        {
            seerState.WaitingForInteractionZoneRelease = waiting;
        }

        return seerState;
    }

    public override void Write(Utf8JsonWriter writer, State_SEER value, JsonSerializerOptions options)
    {
        var baseNode = JsonSerializer.SerializeToNode((State)value, options)?.AsObject() ?? new JsonObject();

        if (value.ForkState is not null)
        {
            baseNode["forkState"] = JsonSerializer.SerializeToNode(value.ForkState, options);
        }

        if (value.WaitingForInteractionZoneRelease.HasValue)
        {
            baseNode["waitingForInteractionZoneRelease"] = value.WaitingForInteractionZoneRelease.Value;
        }

        baseNode.WriteTo(writer, options);
    }

    private static void CopyState(State source, State target)
    {
        target.HeaderId = source.HeaderId;
        target.Timestamp = source.Timestamp;
        target.Version = source.Version;
        target.Manufacturer = source.Manufacturer;
        target.SerialNumber = source.SerialNumber;

        target.Maps = source.Maps;
        target.OrderId = source.OrderId;
        target.OrderUpdateId = source.OrderUpdateId;
        target.ZoneSetId = source.ZoneSetId;
        target.LastNodeId = source.LastNodeId;
        target.LastNodeSequenceId = source.LastNodeSequenceId;
        target.NodeStates = source.NodeStates;
        target.EdgeStates = source.EdgeStates;
        target.AgvPosition = source.AgvPosition;
        target.Velocity = source.Velocity;
        target.Loads = source.Loads;
        target.Driving = source.Driving;
        target.Paused = source.Paused;
        target.NewBaseRequest = source.NewBaseRequest;
        target.DistanceSinceLastNode = source.DistanceSinceLastNode;
        target.ActionStates = source.ActionStates;
        target.BatteryState = source.BatteryState;
        target.OperatingMode = source.OperatingMode;
        target.Errors = source.Errors;
        target.Information = source.Information;
        target.SafetyState = source.SafetyState;
    }
}