diff --git a/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor b/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor
index 497d67a..ace1b8b 100644
--- a/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor
+++ b/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor
@@ -25,9 +25,9 @@
X: @MonitorData.RobotPosition.X.ToString("F2")m | Y: @MonitorData.RobotPosition.Y.ToString("F2")m | θ: @((MonitorData.RobotPosition.Theta * 180 / Math.PI).ToString("F1"))°
}
-
+ @*
@(IsConnected ? "Connected" : "Disconnected")
-
+ *@
+
+ @*
-
+ *@
-
+ @*
-
+
+ *@
diff --git a/RobotApp.Client/Services/RobotStateClient.cs b/RobotApp.Client/Services/RobotStateClient.cs
index 5ca88e2..ece5fa0 100644
--- a/RobotApp.Client/Services/RobotStateClient.cs
+++ b/RobotApp.Client/Services/RobotStateClient.cs
@@ -7,7 +7,7 @@ using System.Text.Json;
namespace RobotApp.Client.Services;
-// ================= CONNECTION STATE =================
+// ================= SIGNALR CONNECTION STATE =================
public enum RobotClientState
{
Disconnected,
@@ -16,7 +16,7 @@ public enum RobotClientState
Reconnecting
}
-// ================= CLIENT =================
+// ================= ROBOT STATE CLIENT =================
public sealed class RobotStateClient : IAsyncDisposable
{
private readonly NavigationManager _nav;
@@ -25,11 +25,17 @@ public sealed class RobotStateClient : IAsyncDisposable
private readonly object _lock = new();
private bool _started;
+ // ================= STATE CACHE =================
public ConcurrentDictionary LatestStates { get; } = new();
+ // ================= ROBOT CONNECTION =================
+ private bool _isRobotConnected;
+ public bool IsRobotConnected => _isRobotConnected;
+
// ================= EVENTS =================
public event Action? OnStateReceived;
public event Action? OnStateReceivedAny;
+ public event Action? OnRobotConnectionChanged;
public event Action? OnConnectionStateChanged;
public RobotClientState ConnectionState { get; private set; } = RobotClientState.Disconnected;
@@ -43,7 +49,8 @@ public sealed class RobotStateClient : IAsyncDisposable
// ================= STATE HELPER =================
private void SetState(RobotClientState state)
{
- if (ConnectionState == state) return;
+ if (ConnectionState == state)
+ return;
ConnectionState = state;
OnConnectionStateChanged?.Invoke(state);
@@ -82,9 +89,10 @@ public sealed class RobotStateClient : IAsyncDisposable
return Task.CompletedTask;
};
- _connection.Closed += async error =>
+ _connection.Closed += async _ =>
{
_started = false;
+ SetState(RobotClientState.Disconnected);
if (_connection != null)
{
@@ -96,8 +104,14 @@ public sealed class RobotStateClient : IAsyncDisposable
}
};
+ // ================= SIGNALR HANDLERS =================
+
+ // VDA5050 State
_connection.On("ReceiveState", HandleState);
+ // Robot connection (bool only)
+ _connection.On("ReceiveRobotConnection", HandleRobotConnection);
+
try
{
await _connection.StartAsync();
@@ -136,6 +150,13 @@ public sealed class RobotStateClient : IAsyncDisposable
OnStateReceivedAny?.Invoke(state);
}
+ // ================= HANDLE ROBOT CONNECTION =================
+ private void HandleRobotConnection(bool isConnected)
+ {
+ _isRobotConnected = isConnected;
+ OnRobotConnectionChanged?.Invoke(isConnected);
+ }
+
// ================= SUBSCRIBE =================
public async Task SubscribeRobotAsync(string serialNumber)
{
@@ -164,6 +185,7 @@ public sealed class RobotStateClient : IAsyncDisposable
}
LatestStates.TryRemove(serialNumber, out _);
+ _isRobotConnected = false;
}
// ================= GET CACHE =================
@@ -177,6 +199,7 @@ public sealed class RobotStateClient : IAsyncDisposable
public async ValueTask DisposeAsync()
{
_started = false;
+ _isRobotConnected = false;
SetState(RobotClientState.Disconnected);
if (_connection != null)
diff --git a/RobotApp.Client/Services/UiEdge.cs b/RobotApp.Client/Services/UiEdge.cs
index 061fe7a..987a580 100644
--- a/RobotApp.Client/Services/UiEdge.cs
+++ b/RobotApp.Client/Services/UiEdge.cs
@@ -270,31 +270,17 @@ public class OrderMessage
}
public object ToSchemaObject()
{
- int seq = 0;
+ // ================= SORT NODES BY UI SEQUENCE =================
+ var orderedNodes = Nodes
+ .OrderBy(n => n.SequenceId)
+ .ToList();
- return new
- {
- headerId = HeaderId++,
-
- timestamp = string.IsNullOrWhiteSpace(Timestamp)
- ? DateTime.UtcNow.ToString("O")
- : Timestamp,
-
- version = Version,
- manufacturer = Manufacturer,
- serialNumber = SerialNumber,
- orderId = OrderId,
- orderUpdateId = OrderUpdateId,
-
- zoneSetId = string.IsNullOrWhiteSpace(ZoneSetId)
- ? null
- : ZoneSetId,
-
- // ================= NODES =================
- nodes = Nodes.Select(n => new
+ // ================= BUILD NODE OBJECTS =================
+ var nodeObjects = orderedNodes
+ .Select((n, index) => new
{
nodeId = n.NodeId,
- sequenceId = seq++,
+ sequenceId = index * 2, // ✅ NODE = EVEN
released = n.Released,
nodePosition = new
@@ -311,36 +297,55 @@ public class OrderMessage
: n.NodePosition.MapId
},
- actions = n.Actions.Select(a => new
- {
- actionId = a.ActionId,
- actionType = a.ActionType,
- blockingType = a.BlockingType,
+ actions = n.Actions?
+ .Select(a => new
+ {
+ actionId = a.ActionId,
+ actionType = a.ActionType,
+ blockingType = a.BlockingType,
- actionParameters = a.ActionParameters != null
- ? a.ActionParameters
- .Select(p => new { key = p.Key, value = p.Value })
+ actionParameters = a.ActionParameters?
+ .Select(p => new
+ {
+ key = p.Key,
+ value = p.Value
+ })
.ToArray()
- : Array.Empty