Initial commit

This commit is contained in:
2026-07-03 16:31:37 +07:00
commit 899c7c637d
1939 changed files with 641750 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
@using RobotNet.VDA5050.State
@using RobotNet.VDA5050.Type
<MudPaper Class="pa-4" Elevation="2">
<MudText Typo="Typo.h6" Class="mb-3">Action States</MudText>
@if (State?.ActionStates != null && State.ActionStates.Length > 0)
{
<MudTable Items="@State.ActionStates" T="ActionState" Hover="true" Dense="true" Height="400px" FixedHeader="true" Elevation="0">
<HeaderContent>
<MudTh>Action ID</MudTh>
<MudTh>Action Type</MudTh>
<MudTh>Status</MudTh>
<MudTh>Description</MudTh>
<MudTh>Result</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Action ID">
<MudText Typo="Typo.body2" Style="max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
@context.ActionId
</MudText>
</MudTd>
<MudTd DataLabel="Action Type">@(context.ActionType ?? "N/A")</MudTd>
<MudTd DataLabel="Status">
<MudChip T="string"
Size="Size.Small"
Color="@GetActionStatusColor(context.ActionStatus)">
@context.ActionStatus
</MudChip>
</MudTd>
<MudTd DataLabel="Description">
<MudText Typo="Typo.body2" Style="max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
@(context.ActionDescription ?? "N/A")
</MudText>
</MudTd>
<MudTd DataLabel="Result">
<MudText Typo="Typo.body2" Style="max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
@(context.ResultDescription ?? "N/A")
</MudText>
</MudTd>
</RowTemplate>
</MudTable>
}
else
{
<MudText Typo="Typo.body2" Align="Align.Center">No action states available</MudText>
}
</MudPaper>
@code {
public StateMsg? State { get; set; }
private Color GetActionStatusColor(ActionStatus status)
{
return status switch
{
ActionStatus.WAITING => Color.Default,
ActionStatus.RUNNING => Color.Info,
ActionStatus.FINISHED => Color.Success,
ActionStatus.FAILED => Color.Error,
_ => Color.Default
};
}
public void Update(StateMsg? state)
{
State = state;
StateHasChanged();
}
}

View File

@@ -0,0 +1,75 @@
@using RobotNet.VDA5050.State
<MudPaper Class="pa-4" Elevation="2">
@if (ShowNameCard)
{
<MudText Typo="Typo.h6" Class="mb-3">Battery State</MudText>
}
@if (BatteryState != null)
{
<MudSimpleTable Elevation="0">
<tbody>
<tr>
<td><strong>Charge:</strong></td>
<td>
<MudProgressLinear Value="@BatteryState.BatteryCharge"
Color="@GetBatteryColor(BatteryState.BatteryCharge)" />
@BatteryState.BatteryCharge.ToString("F1")%
</td>
</tr>
<tr>
<td><strong>Voltage:</strong></td>
<td>@BatteryState.BatteryVoltage?.ToString("F2") V</td>
</tr>
<tr>
<td><strong>Health:</strong></td>
<td>@BatteryState.BatteryHealth.ToString("F1")%</td>
</tr>
<tr>
<td><strong>Charging:</strong></td>
<td>
@if (BatteryState.Charging)
{
<MudChip T="string" Size="Size.Small" Color="Color.Info">Yes</MudChip>
}
else
{
<MudChip T="string" Size="Size.Small" Color="Color.Default">No</MudChip>
}
</td>
</tr>
@if (BatteryState.Reach > 0)
{
<tr>
<td><strong>Reach:</strong></td>
<td>@BatteryState.Reach?.ToString("F0") m</td>
</tr>
}
</tbody>
</MudSimpleTable>
}
else
{
<MudText Typo="Typo.body2" Align="Align.Center">No battery data available</MudText>
}
</MudPaper>
@code {
[Parameter]
public bool ShowNameCard { get; set; } = true;
public BatteryState? BatteryState { get; set; }
private Color GetBatteryColor(double charge)
{
if (charge > 50) return Color.Success;
if (charge > 20) return Color.Warning;
return Color.Error;
}
public void Update(BatteryState? batteryState)
{
BatteryState = batteryState;
StateHasChanged();
}
}

View File

@@ -0,0 +1,60 @@
@using RobotNet.VDA5050.State
<MudPaper Class="pa-4" Elevation="2">
<MudText Typo="Typo.h6" Class="mb-3">Edge States</MudText>
@if (State?.EdgeStates != null && State.EdgeStates.Length > 0)
{
<MudTable Items="@State.EdgeStates" T="EdgeState" Hover="true" Dense="true" Height="400px" FixedHeader="true" Elevation="0">
<HeaderContent>
<MudTh>Edge ID</MudTh>
<MudTh>Sequence ID</MudTh>
<MudTh>Released</MudTh>
<MudTh>Description</MudTh>
<MudTh>Trajectory</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Edge ID">@context.EdgeId</MudTd>
<MudTd DataLabel="Sequence ID">@context.SequenceId</MudTd>
<MudTd DataLabel="Released">
@if (context.Released)
{
<MudChip T="string" Size="Size.Small" Color="Color.Success">Yes</MudChip>
}
else
{
<MudChip T="string" Size="Size.Small" Color="Color.Warning">No</MudChip>
}
</MudTd>
<MudTd DataLabel="Description">
<MudText Typo="Typo.body2" Style="max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
@(context.EdgeDescription ?? "N/A")
</MudText>
</MudTd>
<MudTd DataLabel="Trajectory">
@if (context.Trajectory != null)
{
<MudChip T="string" Size="Size.Small" Color="Color.Info">Available</MudChip>
}
else
{
<MudText Typo="Typo.body2">N/A</MudText>
}
</MudTd>
</RowTemplate>
</MudTable>
}
else
{
<MudText Typo="Typo.body2" Align="Align.Center">No edge states available</MudText>
}
</MudPaper>
@code {
public StateMsg? State { get; set; }
public void Update(StateMsg? state)
{
State = state;
StateHasChanged();
}
}

View File

@@ -0,0 +1,75 @@
@using RobotNet.VDA5050.State
@using RobotNet.VDA5050.Type
<MudPaper Class="pa-4" Elevation="2">
<MudText Typo="Typo.h6" Class="mb-3">Errors</MudText>
@if (Errors != null && Errors.Length > 0)
{
<MudTable Items="@Errors" T="Error" Hover="true" Dense="true" Height="400px" FixedHeader="true" Elevation="0">
<HeaderContent>
<MudTh>Error Type</MudTh>
<MudTh>Level</MudTh>
<MudTh>Description</MudTh>
<MudTh>Hint</MudTh>
<MudTh>References</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Error Type">@context.ErrorType</MudTd>
<MudTd DataLabel="Level">
<MudChip T="string"
Size="Size.Small"
Color="@GetErrorLevelColor(context.ErrorLevel)">
@context.ErrorLevel
</MudChip>
</MudTd>
<MudTd DataLabel="Description">
<MudText Typo="Typo.body2" Style="max-width: 250px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
@(context.ErrorDescription ?? "N/A")
</MudText>
</MudTd>
<MudTd DataLabel="Hint">
<MudText Typo="Typo.body2" Style="max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
@(context.ErrorHint ?? "N/A")
</MudText>
</MudTd>
<MudTd DataLabel="References">
@if (context.ErrorReferences != null && context.ErrorReferences.Length > 0)
{
<MudChip T="string" Size="Size.Small" Color="Color.Info">
@context.ErrorReferences.Length reference(s)
</MudChip>
}
else
{
<MudText Typo="Typo.body2">N/A</MudText>
}
</MudTd>
</RowTemplate>
</MudTable>
}
else
{
<MudText Typo="Typo.body2" Align="Align.Center">No errors</MudText>
}
</MudPaper>
@code {
private Error[]? Errors { get; set; }
private Color GetErrorLevelColor(ErrorLevel level)
{
return level switch
{
ErrorLevel.NONE => Color.Success,
ErrorLevel.WARNING => Color.Warning,
ErrorLevel.FATAL => Color.Error,
_ => Color.Default
};
}
public void Update(Error[]? errors)
{
Errors = errors;
StateHasChanged();
}
}

View File

@@ -0,0 +1,46 @@
@using RobotNet.VDA5050.State
<MudPaper Class="pa-4" Elevation="2">
<MudText Typo="Typo.h6" Class="mb-3">Header Information</MudText>
@if (State != null)
{
<MudSimpleTable Elevation="0">
<tbody>
<tr>
<td><strong>Header ID:</strong></td>
<td>@State.HeaderId</td>
</tr>
<tr>
<td><strong>Timestamp:</strong></td>
<td>@State.Timestamp.ToString("g")</td>
</tr>
<tr>
<td><strong>Version:</strong></td>
<td>@State.Version</td>
</tr>
<tr>
<td><strong>Manufacturer:</strong></td>
<td>@State.Manufacturer</td>
</tr>
<tr>
<td><strong>Serial Number:</strong></td>
<td>@State.SerialNumber</td>
</tr>
</tbody>
</MudSimpleTable>
}
else
{
<MudText Typo="Typo.body2" Align="Align.Center">No state data available</MudText>
}
</MudPaper>
@code {
public StateMsg? State { get; set; }
public void Update(StateMsg? state)
{
State = state;
StateHasChanged();
}
}

View File

@@ -0,0 +1,68 @@
@using RobotNet.VDA5050.State
@using RobotNet.VDA5050.Type
<MudPaper Class="pa-4" Elevation="2">
<MudText Typo="Typo.h6" Class="mb-3">Information</MudText>
@if (Info != null && Info.Length > 0)
{
<MudTable Items="@Info" T="Information" Hover="true" Dense="true" Height="400px" FixedHeader="true" Elevation="0">
<HeaderContent>
<MudTh>Info Type</MudTh>
<MudTh>Level</MudTh>
<MudTh>Description</MudTh>
<MudTh>References</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Info Type">@context.InfoType</MudTd>
<MudTd DataLabel="Level">
<MudChip T="string"
Size="Size.Small"
Color="@GetInfoLevelColor(context.InfoLevel)">
@context.InfoLevel
</MudChip>
</MudTd>
<MudTd DataLabel="Description">
<MudText Typo="Typo.body2" Style="max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
@(context.InfoDescription ?? "N/A")
</MudText>
</MudTd>
<MudTd DataLabel="References">
@if (context.InfoReferences != null && context.InfoReferences.Length > 0)
{
<MudChip T="string" Size="Size.Small" Color="Color.Info">
@context.InfoReferences.Length reference(s)
</MudChip>
}
else
{
<MudText Typo="Typo.body2">N/A</MudText>
}
</MudTd>
</RowTemplate>
</MudTable>
}
else
{
<MudText Typo="Typo.body2" Align="Align.Center">No information available</MudText>
}
</MudPaper>
@code {
private Information[]? Info { get; set; }
private Color GetInfoLevelColor(InfoLevel level)
{
return level switch
{
InfoLevel.INFO => Color.Info,
InfoLevel.DEBUG => Color.Default,
_ => Color.Default
};
}
public void Update(Information[]? info)
{
Info = info;
StateHasChanged();
}
}

View File

@@ -0,0 +1,58 @@
@using RobotNet.VDA5050.State
<MudPaper Class="pa-4" Elevation="2">
<MudText Typo="Typo.h6" Class="mb-3">Loads</MudText>
@if (State?.Loads != null && State.Loads.Length > 0)
{
<MudTable Items="@State.Loads" T="Load" Hover="true" Dense="true" Height="400px" FixedHeader="true" Elevation="0">
<HeaderContent>
<MudTh>Load ID</MudTh>
<MudTh>Load Type</MudTh>
<MudTh>Position</MudTh>
<MudTh>Weight</MudTh>
<MudTh>Dimensions</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Load ID">
<MudText Typo="Typo.body2" Style="max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
@(context.LoadId ?? "N/A")
</MudText>
</MudTd>
<MudTd DataLabel="Load Type">@(context.LoadType ?? "N/A")</MudTd>
<MudTd DataLabel="Position">@(context.LoadPosition ?? "N/A")</MudTd>
<MudTd DataLabel="Weight">@(context.Weight > 0 ? context.Weight?.ToString("F2") + " kg" : "N/A")</MudTd>
<MudTd DataLabel="Dimensions">
@if (context.LoadDimensions != null)
{
<MudText Typo="Typo.body2">
@($"{context.LoadDimensions.Length:F2} × {context.LoadDimensions.Width:F2}")
@if (context.LoadDimensions.Height > 0)
{
<text> × @context.LoadDimensions.Height.ToString("F2")</text>
}
<text> m</text>
</MudText>
}
else
{
<MudText Typo="Typo.body2">N/A</MudText>
}
</MudTd>
</RowTemplate>
</MudTable>
}
else
{
<MudText Typo="Typo.body2" Align="Align.Center">No loads available</MudText>
}
</MudPaper>
@code {
public StateMsg? State { get; set; }
public void Update(StateMsg? state)
{
State = state;
StateHasChanged();
}
}

View File

@@ -0,0 +1,216 @@
@using RobotNet.VDA5050
@using RobotNet.VDA5050.Type
@using System.Net.Http.Json
@using RobotNet10.FleetManager.Shared.Models
@using RobotNet10.Shared
@inject HttpClient HttpClient
@inject ISnackbar Snackbar
<MudPaper Class="pa-4" Elevation="0">
<MudText Typo="Typo.h6" Class="mb-4">Manual Actions</MudText>
<MudStack Spacing="3">
<!-- Action Type Selector -->
<MudSelect @bind-Value="selectedActionType"
Label="Action Type"
Variant="Variant.Outlined"
T="ActionType">
@foreach (ActionType actionType in Enum.GetValues<ActionType>())
{
<MudSelectItem Value="@actionType">@actionType.ToString()</MudSelectItem>
}
</MudSelect>
<!-- Blocking Type Selector -->
<MudSelect @bind-Value="selectedBlockingType"
Label="Blocking Type"
Variant="Variant.Outlined"
T="BlockingType">
@foreach (BlockingType blockingType in Enum.GetValues<BlockingType>())
{
<MudSelectItem Value="@blockingType">@blockingType.ToString()</MudSelectItem>
}
</MudSelect>
<!-- Action Parameters -->
<MudText Typo="Typo.subtitle2">Action Parameters</MudText>
@foreach (var param in actionParameters)
{
<MudGrid>
<MudItem xs="5">
<MudTextField @bind-Value="param.Key"
Label="Key"
Variant="Variant.Outlined"
Margin="Margin.Dense" />
</MudItem>
<MudItem xs="5">
<MudTextField @bind-Value="param.Value"
Label="Value"
Variant="Variant.Outlined"
Margin="Margin.Dense" />
</MudItem>
<MudItem xs="2">
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Color="Color.Error"
Size="Size.Small"
OnClick="@(() => RemoveParameter(param))" />
</MudItem>
</MudGrid>
}
<MudButton StartIcon="@Icons.Material.Filled.Add"
Variant="Variant.Outlined"
Color="Color.Primary"
OnClick="AddParameter">
Add Parameter
</MudButton>
<!-- Send Action Button -->
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Send"
OnClick="SendAction"
Disabled="@isSending"
FullWidth="true">
@if (isSending)
{
<MudProgressCircular Class="mr-2" Size="Size.Small" Indeterminate="true" />
<span>Sending...</span>
}
else
{
<span>Send Action</span>
}
</MudButton>
<!-- Action History -->
<MudDivider />
<MudText Typo="Typo.subtitle2">Recent Actions</MudText>
@if (actionHistory.Count > 0)
{
<MudStack Spacing="1" Style="height: 200px; overflow-y: auto">
@foreach (var action in actionHistory.Take(10))
{
<MudPaper Class="pa-2" Elevation="0">
<MudText Typo="Typo.body2">
<strong>@action.ActionType</strong> - @action.Timestamp.ToString("g")
</MudText>
</MudPaper>
}
</MudStack>
}
else
{
<MudText Typo="Typo.body2" Align="Align.Center">No actions sent yet</MudText>
}
</MudStack>
</MudPaper>
@code {
[Parameter]
public string RobotId { get; set; } = string.Empty;
private ActionType selectedActionType = ActionType.STATE_REQUEST;
private BlockingType selectedBlockingType = BlockingType.HARD;
private List<RobotNet.VDA5050.InstantAction.ActionParameter> actionParameters = new();
private List<SentAction> actionHistory = new();
private bool isSending = false;
private class SentAction
{
public ActionType ActionType { get; set; }
public DateTime Timestamp { get; set; }
}
private void AddParameter()
{
actionParameters.Add(new RobotNet.VDA5050.InstantAction.ActionParameter());
}
private void RemoveParameter(RobotNet.VDA5050.InstantAction.ActionParameter param)
{
actionParameters.Remove(param);
}
private async Task SendAction()
{
if (string.IsNullOrWhiteSpace(RobotId))
{
Snackbar.Add("Robot ID is required", Severity.Warning);
return;
}
isSending = true;
StateHasChanged();
try
{
// Build action request
var actionRequest = new RobotInstantActionModel
{
RobotId = RobotId,
Action = new RobotNet.VDA5050.InstantAction.Action
{
ActionId = Guid.NewGuid().ToString(),
BlockingType = selectedBlockingType,
ActionType = selectedActionType.ToJsonString(),
ActionParameters = [.. actionParameters.Where(p => !string.IsNullOrWhiteSpace(p.Key))],
}
};
// Call API endpoint (TODO: Create this endpoint in Phase 10)
var response = await HttpClient.PostAsJsonAsync($"/api/robotManager/InstantActions", actionRequest);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadFromJsonAsync<MessageResult>();
if(result is null)
{
Snackbar.Add($"Sending action is failed", Severity.Warning);
}
else if (!result.IsSuccess)
{
Snackbar.Add($"{result.Message}", Severity.Warning);
}
else
{
Snackbar.Add($"Action '{selectedActionType}' sent successfully", Severity.Success);
// Add to history
actionHistory.Insert(0, new SentAction
{
ActionType = selectedActionType,
Timestamp = DateTime.UtcNow
});
// Clear parameters
actionParameters.Clear();
}
}
else if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
Snackbar.Add($"Robot with ID '{RobotId}' not found", Severity.Error);
}
else if (response.StatusCode == System.Net.HttpStatusCode.BadRequest)
{
var error = await response.Content.ReadAsStringAsync();
Snackbar.Add($"Invalid action request: {error}", Severity.Warning);
}
else
{
var error = await response.Content.ReadAsStringAsync();
Snackbar.Add($"Error sending action: {error}", Severity.Error);
}
}
catch (Exception ex)
{
Snackbar.Add($"Error sending action: {ex.Message}", Severity.Error);
}
finally
{
isSending = false;
StateHasChanged();
}
}
}

View File

@@ -0,0 +1,401 @@
@using RobotNet10.FleetManager.Shared.DTOs.Robot
@using RobotNet10.FleetManager.Client.Services
@using RobotNet10.MapEditor.Services.API
@using RobotNet10.MapEditor.Shared.DTOs.LayoutData
@using RobotNet10.MapEditor.Shared.DTOs.Node
@using RobotNet10.FleetManager.Shared.Models
@using RobotNet10.Shared
@using System.Net.Http.Json
@inject HttpClient HttpClient
@inject ISnackbar Snackbar
@inject RobotApiService RobotApiService
@inject MapManagerApiService MapApiService
<MudPaper Class="pa-4" Elevation="0">
<MudText Typo="Typo.h6" Class="mb-4">Manual Order</MudText>
<MudStack Spacing="3">
<!-- Node Selector -->
<MudSelect @bind-Value="selectedNodeName"
Label="Select Node"
Variant="Variant.Outlined"
T="string"
Disabled="@isLoadingNodes"
ErrorText="@GetValidationError("NodeName")">
@if (isLoadingNodes)
{
<MudSelectItem Value="@(string.Empty)" Disabled="true">Loading nodes...</MudSelectItem>
}
else if (nodes != null && nodes.Any())
{
<MudSelectItem Value="@(string.Empty)">-- Select Node --</MudSelectItem>
@foreach (var node in nodes.Where(n => !string.IsNullOrEmpty(n.NodeName)).OrderBy(n => n.NodeName))
{
var displayName = !string.IsNullOrWhiteSpace(node.NodeName)
? $"{node.NodeName} ({node.NodeId})"
: node.NodeId;
<MudSelectItem Value="@node.NodeName">@displayName</MudSelectItem>
}
}
else
{
<MudSelectItem Value="@(string.Empty)" Disabled="true">No nodes available</MudSelectItem>
}
</MudSelect>
@if (!string.IsNullOrWhiteSpace(selectedNodeName))
{
var selectedNode = nodes?.FirstOrDefault(n =>
(!string.IsNullOrWhiteSpace(n.NodeName) && n.NodeName == selectedNodeName) ||
(string.IsNullOrWhiteSpace(n.NodeName) && n.NodeId == selectedNodeName));
@if (selectedNode != null)
{
<MudPaper Class="pa-2" Elevation="0" Style="background-color: var(--mud-palette-background-grey);">
<MudText Typo="Typo.body2">
<strong>Node ID:</strong> @selectedNode.NodeId<br />
@if (!string.IsNullOrWhiteSpace(selectedNode.NodeDescription))
{
<strong>Description:</strong> @selectedNode.NodeDescription<br />
}
<strong>Position:</strong> X: @selectedNode.X.ToString("F2")m, Y: @selectedNode.Y.ToString("F2")m
</MudText>
</MudPaper>
}
}
<!-- Action Buttons -->
<MudStack Row="true" Spacing="2">
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Send"
OnClick="SendOrder"
Disabled="@(isSending || string.IsNullOrWhiteSpace(selectedNodeName))"
FullWidth="true">
@if (isSending)
{
<MudProgressCircular Class="mr-2" Size="Size.Small" Indeterminate="true" />
<span>Sending...</span>
}
else
{
<span>Send Order</span>
}
</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Error"
StartIcon="@Icons.Material.Filled.Cancel"
OnClick="CancelOrder"
Disabled="@isCanceling"
FullWidth="true">
@if (isCanceling)
{
<MudProgressCircular Class="mr-2" Size="Size.Small" Indeterminate="true" />
<span>Canceling...</span>
}
else
{
<span>Cancel Order</span>
}
</MudButton>
</MudStack>
<!-- Order History -->
<MudDivider />
<MudText Typo="Typo.subtitle2">Recent Orders</MudText>
@if (orderHistory.Count > 0)
{
<MudStack Spacing="1" Style="height: 200px; overflow: auto;">
@foreach (var order in orderHistory.Take(10))
{
<MudPaper Class="pa-2" Elevation="0">
<MudText Typo="Typo.body2">
<strong>@order.NodeName</strong> - @order.Timestamp.ToString("g")
@if (!order.IsSuccess)
{
<MudChip T="string" Size="Size.Small" Color="Color.Error" Class="ml-2">Failed</MudChip>
}
else
{
<MudChip T="string" Size="Size.Small" Color="Color.Success" Class="ml-2">Success</MudChip>
}
</MudText>
</MudPaper>
}
</MudStack>
}
else
{
<MudText Typo="Typo.body2" Align="Align.Center">No orders sent yet</MudText>
}
</MudStack>
</MudPaper>
@code {
[Parameter]
public string RobotId { get; set; } = string.Empty;
[Parameter]
public Guid? MapId { get; set; }
private string selectedNodeName = string.Empty;
private List<NodeDto> nodes = new();
private List<SentOrder> orderHistory = new();
private bool isSending = false;
private bool isCanceling = false;
private bool isLoadingNodes = false;
private Dictionary<string, string> validationErrors = new();
protected override async Task OnInitializedAsync()
{
await LoadNodesAsync();
}
private async Task LoadNodesAsync()
{
if (!MapId.HasValue || MapId.Value == Guid.Empty)
{
// Try to get MapId from robot
try
{
var robot = await RobotApiService.GetByRobotIdAsync(RobotId);
if (robot?.MapId.HasValue == true)
{
MapId = robot.MapId;
}
else
{
nodes = new List<NodeDto>();
return;
}
}
catch (Exception ex)
{
Snackbar.Add($"Error loading robot: {ex.Message}", Severity.Warning);
nodes = new List<NodeDto>();
return;
}
}
isLoadingNodes = true;
StateHasChanged();
try
{
var layoutData = await MapApiService.GetLayoutDataAsync(MapId.Value);
nodes = layoutData.Nodes?.ToList() ?? new List<NodeDto>();
}
catch (Exception ex)
{
Snackbar.Add($"Error loading nodes: {ex.Message}", Severity.Warning);
nodes = new List<NodeDto>();
}
finally
{
isLoadingNodes = false;
StateHasChanged();
}
}
private string? GetValidationError(string fieldName)
{
return validationErrors.TryGetValue(fieldName, out var error) ? error : null;
}
private async Task SendOrder()
{
if (string.IsNullOrWhiteSpace(RobotId))
{
Snackbar.Add("Robot ID is required", Severity.Warning);
return;
}
if (string.IsNullOrWhiteSpace(selectedNodeName))
{
validationErrors["NodeName"] = "Please select a node";
StateHasChanged();
return;
}
isSending = true;
validationErrors.Clear();
StateHasChanged();
try
{
// Find the selected node to get NodeName (preferred) or NodeId
var selectedNode = nodes?.FirstOrDefault(n =>
(!string.IsNullOrWhiteSpace(n.NodeName) && n.NodeName == selectedNodeName) ||
(string.IsNullOrWhiteSpace(n.NodeName) && n.NodeId == selectedNodeName));
if (selectedNode == null)
{
Snackbar.Add("Selected node not found", Severity.Warning);
return;
}
// Use NodeName if available, otherwise use NodeId
var nodeNameToSend = !string.IsNullOrWhiteSpace(selectedNode.NodeName)
? selectedNode.NodeName
: selectedNode.NodeId;
// Build order request
var orderRequest = new RobotMoveToNodeModel
{
RobotId = RobotId,
NodeName = nodeNameToSend,
LastAngle = null, // Optional, can be enhanced later
};
// Call API endpoint
var response = await HttpClient.PostAsJsonAsync($"/api/RobotManager/MoveToNode", orderRequest);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadFromJsonAsync<MessageResult>();
if (result is null)
{
Snackbar.Add($"Sending order failed", Severity.Warning);
orderHistory.Insert(0, new SentOrder
{
NodeName = selectedNodeName,
Timestamp = DateTime.UtcNow,
IsSuccess = false
});
}
else if (!result.IsSuccess)
{
Snackbar.Add($"{result.Message}", Severity.Warning);
orderHistory.Insert(0, new SentOrder
{
NodeName = selectedNodeName,
Timestamp = DateTime.UtcNow,
IsSuccess = false
});
}
else
{
Snackbar.Add($"Order to node '{selectedNodeName}' sent successfully", Severity.Success);
// Add to history
orderHistory.Insert(0, new SentOrder
{
NodeName = selectedNodeName,
Timestamp = DateTime.UtcNow,
IsSuccess = true
});
// Clear selection
selectedNodeName = string.Empty;
}
}
else if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
Snackbar.Add($"Robot with ID '{RobotId}' not found", Severity.Error);
orderHistory.Insert(0, new SentOrder
{
NodeName = selectedNodeName,
Timestamp = DateTime.UtcNow,
IsSuccess = false
});
}
else if (response.StatusCode == System.Net.HttpStatusCode.BadRequest)
{
var error = await response.Content.ReadAsStringAsync();
Snackbar.Add($"Invalid order request: {error}", Severity.Warning);
orderHistory.Insert(0, new SentOrder
{
NodeName = selectedNodeName,
Timestamp = DateTime.UtcNow,
IsSuccess = false
});
}
else
{
var error = await response.Content.ReadAsStringAsync();
Snackbar.Add($"Error sending order: {error}", Severity.Error);
orderHistory.Insert(0, new SentOrder
{
NodeName = selectedNodeName,
Timestamp = DateTime.UtcNow,
IsSuccess = false
});
}
}
catch (Exception ex)
{
Snackbar.Add($"Error sending order: {ex.Message}", Severity.Error);
orderHistory.Insert(0, new SentOrder
{
NodeName = selectedNodeName ?? "Unknown",
Timestamp = DateTime.UtcNow,
IsSuccess = false
});
}
finally
{
isSending = false;
StateHasChanged();
}
}
private async Task CancelOrder()
{
if (string.IsNullOrWhiteSpace(RobotId))
{
Snackbar.Add("Robot ID is required", Severity.Warning);
return;
}
isCanceling = true;
StateHasChanged();
try
{
// Call API endpoint
var response = await HttpClient.DeleteAsync($"/api/RobotManager/MoveToNode/{RobotId}");
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadFromJsonAsync<MessageResult>();
if (result is null)
{
Snackbar.Add($"Canceling order failed", Severity.Warning);
}
else if (!result.IsSuccess)
{
Snackbar.Add($"{result.Message}", Severity.Warning);
}
else
{
Snackbar.Add($"Order canceled successfully", Severity.Success);
}
}
else if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
Snackbar.Add($"Robot with ID '{RobotId}' not found", Severity.Error);
}
else
{
var error = await response.Content.ReadAsStringAsync();
Snackbar.Add($"Error canceling order: {error}", Severity.Error);
}
}
catch (Exception ex)
{
Snackbar.Add($"Error canceling order: {ex.Message}", Severity.Error);
}
finally
{
isCanceling = false;
StateHasChanged();
}
}
// Helper class for order history
private class SentOrder
{
public string NodeName { get; set; } = string.Empty;
public DateTime Timestamp { get; set; }
public bool IsSuccess { get; set; }
}
}

View File

@@ -0,0 +1,32 @@
@using RobotNet.VDA5050.State
<MudPaper Class="pa-4" Elevation="2">
<MudText Typo="Typo.h6" Class="mb-3">Maps</MudText>
@if (State?.Maps != null && State.Maps.Length > 0)
{
<MudTable Items="@State.Maps" T="Map" Hover="true" Dense="true" Elevation="0">
<HeaderContent>
<MudTh>Map ID</MudTh>
<MudTh>Map Description</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Map ID">@context.MapId</MudTd>
<MudTd DataLabel="Map Description">@(context.MapDescription ?? "N/A")</MudTd>
</RowTemplate>
</MudTable>
}
else
{
<MudText Typo="Typo.body2" Align="Align.Center">No maps available</MudText>
}
</MudPaper>
@code {
public StateMsg? State { get; set; }
public void Update(StateMsg? state)
{
State = state;
StateHasChanged();
}
}

View File

@@ -0,0 +1,63 @@
@using RobotNet.VDA5050.State
<MudPaper Class="pa-4" Elevation="2">
<MudText Typo="Typo.h6" Class="mb-3">Node States</MudText>
@if (State?.NodeStates != null && State.NodeStates.Length > 0)
{
<MudTable Items="@State.NodeStates" T="NodeState" Hover="true" Dense="true" Height="400px" FixedHeader="true" Elevation="0">
<HeaderContent>
<MudTh>Node ID</MudTh>
<MudTh>Sequence ID</MudTh>
<MudTh>Released</MudTh>
<MudTh>Description</MudTh>
<MudTh>Position</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Node ID">@context.NodeId</MudTd>
<MudTd DataLabel="Sequence ID">@context.SequenceId</MudTd>
<MudTd DataLabel="Released">
@if (context.Released)
{
<MudChip T="string" Size="Size.Small" Color="Color.Success">Yes</MudChip>
}
else
{
<MudChip T="string" Size="Size.Small" Color="Color.Warning">No</MudChip>
}
</MudTd>
<MudTd DataLabel="Description">
<MudText Typo="Typo.body2" Style="max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
@(context.NodeDescription ?? "N/A")
</MudText>
</MudTd>
<MudTd DataLabel="Position">
@if (context.NodePosition != null)
{
<MudText Typo="Typo.body2">
@($"({context.NodePosition.X:F2}, {context.NodePosition.Y:F2})")
</MudText>
}
else
{
<MudText Typo="Typo.body2">N/A</MudText>
}
</MudTd>
</RowTemplate>
</MudTable>
}
else
{
<MudText Typo="Typo.body2" Align="Align.Center">No node states available</MudText>
}
</MudPaper>
@code {
public StateMsg? State { get; set; }
public void Update(StateMsg? state)
{
State = state;
Console.WriteLine($"Update node state: {State?.NodeStates.Length}");
StateHasChanged();
}
}

View File

@@ -0,0 +1,93 @@
@using RobotNet.VDA5050.State
<MudPaper Class="pa-4" Elevation="2">
<MudText Typo="Typo.h6" Class="mb-3">Order Information</MudText>
@if (State != null)
{
<MudSimpleTable Elevation="0">
<tbody>
<tr>
<td><strong>Order ID:</strong></td>
<td>@State.OrderId</td>
</tr>
<tr>
<td><strong>Order Update ID:</strong></td>
<td>@State.OrderUpdateId</td>
</tr>
<tr>
<td><strong>Zone Set ID:</strong></td>
<td>@(State.ZoneSetId ?? "N/A")</td>
</tr>
<tr>
<td><strong>Last Node ID:</strong></td>
<td>@State.LastNodeId</td>
</tr>
<tr>
<td><strong>Last Node Sequence ID:</strong></td>
<td>@State.LastNodeSequenceId</td>
</tr>
<tr>
<td><strong>Driving:</strong></td>
<td>
@if (State.Driving)
{
<MudChip T="string" Size="Size.Small" Color="Color.Success">Yes</MudChip>
}
else
{
<MudChip T="string" Size="Size.Small" Color="Color.Default">No</MudChip>
}
</td>
</tr>
<tr>
<td><strong>Paused:</strong></td>
<td>
@if (State.Paused)
{
<MudChip T="string" Size="Size.Small" Color="Color.Warning">Yes</MudChip>
}
else
{
<MudChip T="string" Size="Size.Small" Color="Color.Default">No</MudChip>
}
</td>
</tr>
<tr>
<td><strong>Operating Mode:</strong></td>
<td>@State.OperatingMode</td>
</tr>
<tr>
<td><strong>Distance Since Last Node:</strong></td>
<td>@State.DistanceSinceLastNode?.ToString("F3") m</td>
</tr>
<tr>
<td><strong>New Base Request:</strong></td>
<td>
@if (State.NewBaseRequest)
{
<MudChip T="string" Size="Size.Small" Color="Color.Warning">Yes</MudChip>
}
else
{
<MudChip T="string" Size="Size.Small" Color="Color.Default">No</MudChip>
}
</td>
</tr>
</tbody>
</MudSimpleTable>
}
else
{
<MudText Typo="Typo.body2" Align="Align.Center">No order data available</MudText>
}
</MudPaper>
@code {
public StateMsg? State { get; set; }
public void Update(StateMsg? state)
{
State = state;
StateHasChanged();
}
}

View File

@@ -0,0 +1,76 @@
@using RobotNet.VDA5050.State
<MudPaper Class="pa-4" Elevation="2">
<MudText Typo="Typo.h6" Class="mb-3">Position</MudText>
@if (State?.AgvPosition != null)
{
<MudSimpleTable Elevation="0">
<tbody>
<tr>
<td><strong>X:</strong></td>
<td>@State.AgvPosition.X.ToString("F3") m</td>
</tr>
<tr>
<td><strong>Y:</strong></td>
<td>@State.AgvPosition.Y.ToString("F3") m</td>
</tr>
<tr>
<td><strong>Theta:</strong></td>
<td>@State.AgvPosition.Theta.ToString("F3") rad</td>
</tr>
<tr>
<td><strong>Map ID:</strong></td>
<td>@(State.AgvPosition.MapId ?? "N/A")</td>
</tr>
<tr>
<td><strong>Position Initialized:</strong></td>
<td>
@if (State.AgvPosition.PositionInitialized)
{
<MudChip T="string" Size="Size.Small" Color="Color.Success">Yes</MudChip>
}
else
{
<MudChip T="string" Size="Size.Small" Color="Color.Warning">No</MudChip>
}
</td>
</tr>
@if (State.AgvPosition.LocalizationScore >= 0)
{
<tr>
<td><strong>Localization Score:</strong></td>
<td>@State.AgvPosition.LocalizationScore.ToString("F3")</td>
</tr>
}
@if (State.AgvPosition.DeviationRange >= 0)
{
<tr>
<td><strong>Deviation Range:</strong></td>
<td>@State.AgvPosition.DeviationRange.ToString("F3") m</td>
</tr>
}
@if (!string.IsNullOrWhiteSpace(State.AgvPosition.MapDescription))
{
<tr>
<td><strong>Map Description:</strong></td>
<td>@State.AgvPosition.MapDescription</td>
</tr>
}
</tbody>
</MudSimpleTable>
}
else
{
<MudText Typo="Typo.body2" Align="Align.Center">No position data available</MudText>
}
</MudPaper>
@code {
public StateMsg? State { get; set; }
public void Update(StateMsg? state)
{
State = state;
StateHasChanged();
}
}

View File

@@ -0,0 +1,62 @@
@using RobotNet.VDA5050.State
@using RobotNet.VDA5050.Type
<MudPaper Class="pa-4" Elevation="2">
<MudText Typo="Typo.h6" Class="mb-3">Safety State</MudText>
@if (State?.SafetyState != null)
{
<MudSimpleTable Elevation="0">
<tbody>
<tr>
<td><strong>E-Stop:</strong></td>
<td>
<MudChip T="string"
Size="Size.Small"
Color="@GetEStopColor(State.SafetyState.EStop)">
@State.SafetyState.EStop
</MudChip>
</td>
</tr>
<tr>
<td><strong>Field Violation:</strong></td>
<td>
@if (State.SafetyState.FieldViolation)
{
<MudChip T="string" Size="Size.Small" Color="Color.Error">Yes</MudChip>
}
else
{
<MudChip T="string" Size="Size.Small" Color="Color.Success">No</MudChip>
}
</td>
</tr>
</tbody>
</MudSimpleTable>
}
else
{
<MudText Typo="Typo.body2" Align="Align.Center">No safety state data available</MudText>
}
</MudPaper>
@code {
public StateMsg? State { get; set; }
private Color GetEStopColor(EStop eStop)
{
return eStop switch
{
EStop.NONE => Color.Success,
EStop.AUTOACK => Color.Warning,
EStop.MANUAL => Color.Error,
EStop.REMOTE => Color.Warning,
_ => Color.Default
};
}
public void Update(StateMsg? state)
{
State = state;
StateHasChanged();
}
}

View File

@@ -0,0 +1,38 @@
@using RobotNet.VDA5050.State
<MudPaper Class="pa-4" Elevation="2">
<MudText Typo="Typo.h6" Class="mb-3">Velocity</MudText>
@if (State?.Velocity != null)
{
<MudSimpleTable Elevation="0">
<tbody>
<tr>
<td><strong>Vx:</strong></td>
<td>@State.Velocity.Vx.ToString("F3") m/s</td>
</tr>
<tr>
<td><strong>Vy:</strong></td>
<td>@State.Velocity.Vy.ToString("F3") m/s</td>
</tr>
<tr>
<td><strong>Omega:</strong></td>
<td>@State.Velocity.Omega.ToString("F3") rad/s</td>
</tr>
</tbody>
</MudSimpleTable>
}
else
{
<MudText Typo="Typo.body2" Align="Align.Center">No velocity data available</MudText>
}
</MudPaper>
@code {
public StateMsg? State { get; set; }
public void Update(StateMsg? state)
{
State = state;
StateHasChanged();
}
}