Initial commit
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user