Initial commit

This commit is contained in:
2026-07-13 09:25:40 +07:00
parent c08ff54676
commit bccfb156d7
1938 changed files with 641646 additions and 0 deletions

View File

@@ -0,0 +1,412 @@
@using MudBlazor
@using Microsoft.AspNetCore.SignalR.Client
@using RobotNet10.RobotApp.Client.Clients
@using RobotNet10.RobotApp.Client.Shared.Devices
@implements IAsyncDisposable
@inject ModbusTcpHubClient ModbusTcpHubClient
@inject ISnackbar Snackbar
<MudCard>
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h5">@DeviceName</MudText>
<MudText Typo="Typo.body2" Color="Color.Secondary">@DeviceId</MudText>
</CardHeaderContent>
<CardHeaderActions>
<MudTooltip Text="Reload Modbus data">
<MudIconButton Icon="@Icons.Material.Filled.Refresh"
Color="Color.Primary"
Size="Size.Small"
OnClick="ReloadModbusDataAsync"
Disabled="@(!ModbusTcpHubClient.IsConnected || IsReloading)">
</MudIconButton>
</MudTooltip>
</CardHeaderActions>
</MudCardHeader>
<MudCardContent Style="position: relative;">
<MudGrid>
<!-- Connection Info -->
<MudItem xs="12">
<MudCard Elevation="0" Class="pa-4">
<MudGrid>
<MudItem xs="12" sm="3">
<MudText Typo="Typo.body1">
<strong>IP:</strong> @ModbusData.IpAddress
</MudText>
</MudItem>
<MudItem xs="12" sm="3">
<MudText Typo="Typo.body1">
<strong>Port:</strong> @ModbusData.Port
</MudText>
</MudItem>
<MudItem xs="12" sm="3">
<MudText Typo="Typo.body1">
<strong>Slave ID:</strong> @ModbusData.SlaveId
</MudText>
</MudItem>
<MudItem xs="12" sm="3">
<MudChip T="string" Color="@(ModbusData.IsConnected ? Color.Success : Color.Error)"
Size="Size.Small"
Variant="Variant.Filled">
@(ModbusData.IsConnected ? "Connected" : "Disconnected")
</MudChip>
</MudItem>
</MudGrid>
</MudCard>
</MudItem>
<!-- Holding Registers -->
@if (ModbusData.HoldingRegisters != null && ModbusData.HoldingRegisters.Length > 0)
{
@foreach (var range in ModbusData.HoldingRegisters)
{
<MudItem xs="12">
<MudCard Elevation="0" Class="pa-4">
<MudText Typo="Typo.h6" Color="Color.Primary">
Holding Registers: @range.Name
</MudText>
<MudText Typo="Typo.body2" Color="Color.Secondary" Class="mb-2">
Address: @range.StartAddress - @(range.StartAddress + range.Quantity - 1) (@range.Quantity registers)
</MudText>
<MudTable Items="@range.Values" Hover="true" Dense="true" Striped="true">
<HeaderContent>
<MudTh>Address</MudTh>
<MudTh>Index</MudTh>
<MudTh>Name</MudTh>
<MudTh>Value (Decimal)</MudTh>
<MudTh>Value (Hex)</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Address">@context.Address</MudTd>
<MudTd DataLabel="Index">@context.Index</MudTd>
<MudTd DataLabel="Name">
@if (!string.IsNullOrEmpty(context.Name))
{
<MudChip T="string" Size="Size.Small" Variant="Variant.Text" Color="Color.Info">
@context.Name
</MudChip>
}
else
{
<MudText Typo="Typo.body2" Color="Color.Secondary">-</MudText>
}
</MudTd>
<MudTd DataLabel="Value (Decimal)">
<strong>@context.Value</strong>
</MudTd>
<MudTd DataLabel="Value (Hex)">
<MudText Typo="Typo.body2" Color="Color.Secondary">0x@(context.Value.ToString("X4"))</MudText>
</MudTd>
</RowTemplate>
</MudTable>
</MudCard>
</MudItem>
}
}
<!-- Input Registers -->
@if (ModbusData.InputRegisters != null && ModbusData.InputRegisters.Length > 0)
{
@foreach (var range in ModbusData.InputRegisters)
{
<MudItem xs="12">
<MudCard Elevation="0" Class="pa-4">
<MudText Typo="Typo.h6" Color="Color.Info">
Input Registers: @range.Name
</MudText>
<MudText Typo="Typo.body2" Color="Color.Secondary" Class="mb-2">
Address: @range.StartAddress - @(range.StartAddress + range.Quantity - 1) (@range.Quantity registers)
</MudText>
<MudTable Items="@range.Values" Hover="true" Dense="true" Striped="true">
<HeaderContent>
<MudTh>Address</MudTh>
<MudTh>Index</MudTh>
<MudTh>Name</MudTh>
<MudTh>Value (Decimal)</MudTh>
<MudTh>Value (Hex)</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Address">@context.Address</MudTd>
<MudTd DataLabel="Index">@context.Index</MudTd>
<MudTd DataLabel="Name">
@if (!string.IsNullOrEmpty(context.Name))
{
<MudChip T="string" Size="Size.Small" Variant="Variant.Text" Color="Color.Info">
@context.Name
</MudChip>
}
else
{
<MudText Typo="Typo.body2" Color="Color.Secondary">-</MudText>
}
</MudTd>
<MudTd DataLabel="Value (Decimal)">
<strong>@context.Value</strong>
</MudTd>
<MudTd DataLabel="Value (Hex)">
<MudText Typo="Typo.body2" Color="Color.Secondary">0x@(context.Value.ToString("X4"))</MudText>
</MudTd>
</RowTemplate>
</MudTable>
</MudCard>
</MudItem>
}
}
<!-- Coils -->
@if (ModbusData.Coils != null && ModbusData.Coils.Length > 0)
{
@foreach (var range in ModbusData.Coils)
{
<MudItem xs="12">
<MudCard Elevation="0" Class="pa-4">
<MudText Typo="Typo.h6" Color="Color.Success">
Coils: @range.Name
</MudText>
<MudText Typo="Typo.body2" Color="Color.Secondary" Class="mb-2">
Address: @range.StartAddress - @(range.StartAddress + range.Quantity - 1) (@range.Quantity coils)
</MudText>
<MudTable Items="@range.BoolValues" Hover="true" Dense="true" Striped="true">
<HeaderContent>
<MudTh>Address</MudTh>
<MudTh>Index</MudTh>
<MudTh>Name</MudTh>
<MudTh>Value</MudTh>
<MudTh>Action</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Address">@context.Address</MudTd>
<MudTd DataLabel="Index">@context.Index</MudTd>
<MudTd DataLabel="Name">
@if (!string.IsNullOrEmpty(context.Name))
{
<MudChip T="string" Size="Size.Small" Variant="Variant.Text" Color="Color.Info">
@context.Name
</MudChip>
}
else
{
<MudText Typo="Typo.body2" Color="Color.Secondary">-</MudText>
}
</MudTd>
<MudTd DataLabel="Value">
<MudChip T="string" Size="Size.Small"
Variant="Variant.Filled"
Color="@(context.Value ? Color.Success : Color.Default)">
@(context.Value ? "ON" : "OFF")
</MudChip>
</MudTd>
<MudTd DataLabel="Action">
@{
var coilKey = $"{range.StartAddress}_{context.Index}";
var isWriting = WritingCoils.ContainsKey(coilKey) && WritingCoils[coilKey];
}
<MudSwitch Value="@context.Value"
Disabled="@(!ModbusTcpHubClient.IsConnected || isWriting)"
Color="Color.Success"
Size="Size.Small"
ValueChanged="@((bool value) => HandleCoilToggle(context.Address, value, coilKey))">
</MudSwitch>
@if (isWriting)
{
<MudProgressCircular Indeterminate="true" Size="Size.Small" Class="ml-2" />
}
</MudTd>
</RowTemplate>
</MudTable>
</MudCard>
</MudItem>
}
}
<!-- Discrete Inputs -->
@if (ModbusData.DiscreteInputs != null && ModbusData.DiscreteInputs.Length > 0)
{
@foreach (var range in ModbusData.DiscreteInputs)
{
<MudItem xs="12">
<MudCard Elevation="0" Class="pa-4">
<MudText Typo="Typo.h6" Color="Color.Warning">
Discrete Inputs: @range.Name
</MudText>
<MudText Typo="Typo.body2" Color="Color.Secondary" Class="mb-2">
Address: @range.StartAddress - @(range.StartAddress + range.Quantity - 1) (@range.Quantity inputs)
</MudText>
<MudTable Items="@range.BoolValues" Hover="true" Dense="true" Striped="true">
<HeaderContent>
<MudTh>Address</MudTh>
<MudTh>Index</MudTh>
<MudTh>Name</MudTh>
<MudTh>Value</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Address">@context.Address</MudTd>
<MudTd DataLabel="Index">@context.Index</MudTd>
<MudTd DataLabel="Name">
@if (!string.IsNullOrEmpty(context.Name))
{
<MudChip T="string" Size="Size.Small" Variant="Variant.Text" Color="Color.Info">
@context.Name
</MudChip>
}
else
{
<MudText Typo="Typo.body2" Color="Color.Secondary">-</MudText>
}
</MudTd>
<MudTd DataLabel="Value">
<MudChip T="string" Size="Size.Small"
Variant="Variant.Filled"
Color="@(context.Value ? Color.Success : Color.Default)">
@(context.Value ? "ON" : "OFF")
</MudChip>
</MudTd>
</RowTemplate>
</MudTable>
</MudCard>
</MudItem>
}
}
</MudGrid>
</MudCardContent>
</MudCard>
<MudOverlay Visible="@IsLoading" Absolute="true">
<MudProgressCircular Indeterminate="true" Size="Size.Large" />
</MudOverlay>
@code {
[Parameter, EditorRequired]
public string DeviceId { get; set; } = string.Empty;
private string DeviceName { get; set; } = string.Empty;
private ModbusTcpData ModbusData = new();
private bool IsLoading => !ModbusTcpHubClient.IsConnected;
private bool IsReloading = false;
private Dictionary<string, bool> WritingCoils = new();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && !string.IsNullOrEmpty(DeviceId))
{
await ConnectAsync();
}
await base.OnAfterRenderAsync(firstRender);
}
private async Task ConnectAsync()
{
try
{
await ModbusTcpHubClient.StartAsync();
// Lấy DeviceName từ ModbusTcpHub
var deviceInfo = await ModbusTcpHubClient.GetDeviceInfoAsync(DeviceId);
if (deviceInfo != null)
{
DeviceName = deviceInfo.DeviceName;
}
else
{
DeviceName = DeviceId; // Fallback to DeviceId if not found
}
ModbusData = await ModbusTcpHubClient.GetModbusDataAsync(DeviceId);
StateHasChanged();
}
catch (Exception ex)
{
Snackbar.Add($"Failed to connect: {ex.Message}", Severity.Error);
}
}
private async Task DisconnectAsync()
{
try
{
await ModbusTcpHubClient.StopAsync();
ModbusData = new ModbusTcpData(); // Reset về giá trị mặc định
StateHasChanged();
}
catch (Exception ex)
{
Snackbar.Add($"Failed to disconnect: {ex.Message}", Severity.Error);
}
}
private async Task ReloadModbusDataAsync()
{
if (!ModbusTcpHubClient.IsConnected || IsReloading)
return;
try
{
IsReloading = true;
StateHasChanged();
ModbusData = await ModbusTcpHubClient.GetModbusDataAsync(DeviceId);
}
catch (Exception ex)
{
Snackbar.Add($"Failed to reload Modbus data: {ex.Message}", Severity.Error);
}
finally
{
IsReloading = false;
StateHasChanged();
}
}
private async Task HandleCoilToggle(ushort address, bool value, string coilKey)
{
if (!ModbusTcpHubClient.IsConnected)
{
Snackbar.Add("Not connected to Modbus device", Severity.Warning);
return;
}
// Set writing state
WritingCoils[coilKey] = true;
StateHasChanged();
try
{
var success = await ModbusTcpHubClient.WriteCoilAsync(DeviceId, address, value);
if (success)
{
Snackbar.Add($"Coil {address} set to {(value ? "ON" : "OFF")}", Severity.Success);
// Reload data để cập nhật giá trị mới nhất
await ReloadModbusDataAsync();
}
else
{
Snackbar.Add($"Failed to write coil {address}", Severity.Error);
// Reload để khôi phục giá trị cũ
await ReloadModbusDataAsync();
}
}
catch (Exception ex)
{
Snackbar.Add($"Error writing coil {address}: {ex.Message}", Severity.Error);
// Reload để khôi phục giá trị cũ
await ReloadModbusDataAsync();
}
finally
{
WritingCoils[coilKey] = false;
StateHasChanged();
}
}
public async ValueTask DisposeAsync()
{
await DisconnectAsync();
}
}