RobotNet/RobotNet.Script/ICcLinkIeBasicClient.cs
2025-10-15 15:15:53 +07:00

416 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

namespace RobotNet.Script;
/// <summary>
/// Client CCLink IE Basic (SLMP over TCP/UDP) dùng để kết nối PC ↔ PLC Mitsubishi qua Ethernet thường,
/// hỗ trợ đọc/ghi device, polling chu kỳ và gửi lệnh SLMP thô.
/// </summary>
public interface ICcLinkIeBasicClient : IAsyncDisposable, IDisposable
{
/// <summary>
/// Trạng thái đã kết nối phiên transport (TCP/UDP) tới PLC hay chưa.
/// </summary>
bool IsConnected { get; }
/// <summary>
/// Số lần thử gửi lại khi lỗi tạm thời/timeout.
/// </summary>
int RetryCount { get; set; }
/// <summary>
/// Ngắt kết nối transport và giải phóng tài nguyên liên quan.
/// </summary>
/// <param name="ct">Token hủy bất đồng bộ.</param>
Task DisconnectAsync(CancellationToken ct = default);
/// <summary>
/// Đọc mảng bit (X/Y/M/L/S/B...) từ PLC.
/// </summary>
/// <param name="device">Loại device code.</param>
/// <param name="startAddress">Địa chỉ bắt đầu.</param>
/// <param name="count">Số bit cần đọc.</param>
/// <param name="ct">Token hủy.</param>
/// <returns>Mảng giá trị bit.</returns>
Task<bool[]> ReadBitsAsync(DeviceCode device, int startAddress, int count, CancellationToken ct = default);
/// <summary>
/// Ghi mảng bit (X/Y/M/L/S/B...) vào PLC.
/// </summary>
/// <param name="device">Loại device code.</param>
/// <param name="startAddress">Địa chỉ bắt đầu.</param>
/// <param name="values">Dãy giá trị bit cần ghi.</param>
/// <param name="ct">Token hủy.</param>
Task WriteBitsAsync(DeviceCode device, int startAddress, ReadOnlyMemory<bool> values, CancellationToken ct = default);
/// <summary>
/// Đọc mảng word (D/W/R/ZR/B...) từ PLC.
/// </summary>
/// <param name="device">Loại device code.</param>
/// <param name="startAddress">Địa chỉ bắt đầu (đơn vị word).</param>
/// <param name="wordCount">Số word cần đọc.</param>
/// <param name="ct">Token hủy.</param>
/// <returns>Mảng word (UInt16).</returns>
Task<ushort[]> ReadWordsAsync(DeviceCode device, int startAddress, int wordCount, CancellationToken ct = default);
/// <summary>
/// Ghi mảng word (D/W/R/ZR/B...) vào PLC.
/// </summary>
/// <param name="device">Loại device code.</param>
/// <param name="startAddress">Địa chỉ bắt đầu (đơn vị word).</param>
/// <param name="words">Dãy word cần ghi.</param>
/// <param name="ct">Token hủy.</param>
Task WriteWordsAsync(DeviceCode device, int startAddress, ReadOnlyMemory<ushort> words, CancellationToken ct = default);
/// <summary>
/// Đọc mảng doubleword (UInt32) từ PLC (áp dụng cho vùng hỗ trợ dword).
/// </summary>
/// <param name="device">Loại device code.</param>
/// <param name="startAddress">Địa chỉ bắt đầu (đơn vị dword).</param>
/// <param name="dwordCount">Số dword cần đọc.</param>
/// <param name="ct">Token hủy.</param>
/// <returns>Mảng dword (UInt32).</returns>
Task<uint[]> ReadDWordsAsync(DeviceCode device, int startAddress, int dwordCount, CancellationToken ct = default);
/// <summary>
/// Ghi mảng doubleword (UInt32) vào PLC (áp dụng cho vùng hỗ trợ dword).
/// </summary>
/// <param name="device">Loại device code.</param>
/// <param name="startAddress">Địa chỉ bắt đầu (đơn vị dword).</param>
/// <param name="dwords">Dãy dword cần ghi.</param>
/// <param name="ct">Token hủy.</param>
Task WriteDWordsAsync(DeviceCode device, int startAddress, ReadOnlyMemory<uint> dwords, CancellationToken ct = default);
/// <summary>
/// Đọc ngẫu nhiên nhiều điểm word rời rạc (mixed areas) trong một lần.
/// </summary>
/// <param name="points">Danh sách (device, address) cần đọc.</param>
/// <param name="ct">Token hủy.</param>
/// <returns>Mảng word theo thứ tự điểm yêu cầu.</returns>
Task<ushort[]> ReadRandomWordsAsync((DeviceCode Device, int Address)[] points, CancellationToken ct = default);
/// <summary>
/// Ghi ngẫu nhiên nhiều điểm word rời rạc (mixed areas) trong một lần.
/// </summary>
/// <param name="points">Danh sách (device, address) cần ghi.</param>
/// <param name="values">Giá trị word theo thứ tự điểm.</param>
/// <param name="ct">Token hủy.</param>
Task WriteRandomWordsAsync((DeviceCode Device, int Address)[] points, ReadOnlyMemory<ushort> values, CancellationToken ct = default);
/// <summary>
/// Sự kiện bắn ra sau mỗi chu kỳ polling hoàn tất.
/// </summary>
event EventHandler<PollUpdatedEventArgs>? Polled;
/// <summary>
/// Bắt đầu polling chu kỳ một hoặc nhiều vùng device.
/// </summary>
/// <param name="options">Tùy chọn polling (chu kỳ, vùng, giới hạn kích thước).</param>
/// <param name="ct">Token hủy.</param>
void StartPolling(PollOptions options, CancellationToken ct = default);
/// <summary>
/// Dừng polling chu kỳ.
/// </summary>
/// <param name="ct">Token hủy.</param>
Task StopPollingAsync(CancellationToken ct = default);
/// <summary>
/// Lấy trạng thái liên kết/SLMP gần nhất (end code, thống kê thời gian phản hồi...).
/// </summary>
/// <param name="ct">Token hủy.</param>
/// <returns>Thông tin trạng thái IE Basic.</returns>
Task<IeBasicStatus> GetStatusAsync(CancellationToken ct = default);
/// <summary>
/// Kiểm tra liên lạc PLC (ví dụ node monitor) ở mức nhanh/gọn.
/// </summary>
/// <param name="ct">Token hủy.</param>
/// <returns>true nếu phản hồi hợp lệ, ngược lại false.</returns>
Task<bool> PingAsync(CancellationToken ct = default);
/// <summary>
/// Truy vấn thông tin nhận dạng module/CPU (nếu PLC cho phép).
/// </summary>
/// <param name="ct">Token hủy.</param>
/// <returns>Thông tin định danh CPU/module.</returns>
Task<ModuleIdentity> IdentifyAsync(CancellationToken ct = default);
/// <summary>
/// Gửi lệnh SLMP thô (mở rộng/đặc thù) và nhận phản hồi.
/// </summary>
/// <param name="command">Mã lệnh SLMP.</param>
/// <param name="payload">Payload SLMP theo định dạng frame chọn (3E/4E).</param>
/// <param name="ct">Token hủy.</param>
/// <returns>Phản hồi SLMP gồm end code và payload.</returns>
Task<SlmpResponse> SendSlmpAsync(SlmpCommand command, ReadOnlyMemory<byte> payload, CancellationToken ct = default);
}
/// <summary>
/// Loại transport cho CCLink IE Basic (UDP hoặc TCP).
/// </summary>
public enum IeBasicTransport
{
/// <summary>Giao tiếp qua UDP (thường dùng cho IE Basic).</summary>
Udp,
/// <summary>Giao tiếp qua TCP.</summary>
Tcp
}
/// <summary>
/// Định dạng frame SLMP.
/// </summary>
public enum SlmpFrameFormat
{
/// <summary>Khung 3E (Format 3E).</summary>
Format3E,
/// <summary>Khung 4E (Format 4E).</summary>
Format4E
}
/// <summary>
/// Device code (vùng thiết bị) cấp ứng dụng.
/// </summary>
public enum DeviceCode
{
/// <summary>Input (bit).</summary>
X,
/// <summary>Output (bit).</summary>
Y,
/// <summary>Internal relay (bit).</summary>
M,
/// <summary>Latch relay (bit).</summary>
L,
/// <summary>Link relay (bit/word tùy CPU).</summary>
B,
/// <summary>Step relay (bit).</summary>
S,
/// <summary>Data register (word).</summary>
D,
/// <summary>Link register (word).</summary>
W,
/// <summary>File register (word).</summary>
R,
/// <summary>Extended file register (word).</summary>
ZR
}
/// <summary>
/// Tham số logic phía local (PC/sender) khi gửi SLMP.
/// </summary>
/// <param name="NetworkNo">Số mạng logic.</param>
/// <param name="ModuleIoNo">Địa chỉ I/O module logic (thường 0x03FF).</param>
/// <param name="MultidropNo">Số đa điểm (nếu dùng).</param>
/// <param name="StationNo">Số trạm logic.</param>
public readonly record struct IeBasicLocal(
byte NetworkNo,
ushort ModuleIoNo,
byte MultidropNo,
byte StationNo
);
/// <summary>
/// Cấu hình đầu xa: địa chỉ IP/Port và tham số logic đích cho SLMP.
/// </summary>
/// <param name="Host">Địa chỉ IP hoặc hostname PLC/module.</param>
/// <param name="Port">Cổng SLMP (ví dụ 5007).</param>
/// <param name="NetworkNo">Số mạng logic đích.</param>
/// <param name="ModuleIoNo">Địa chỉ I/O module logic đích.</param>
/// <param name="MultidropNo">Số đa điểm (nếu dùng).</param>
/// <param name="StationNo">Số trạm logic đích.</param>
public readonly record struct IeBasicRemote(
string Host,
int Port,
byte NetworkNo,
ushort ModuleIoNo,
byte MultidropNo,
byte StationNo
);
/// <summary>
/// Tùy chọn khởi tạo/kết nối IE Basic client.
/// </summary>
public sealed record IeBasicClientOptions(
string Host,
int Port,
byte NetworkNo,
ushort ModuleIoNo,
byte MultidropNo,
byte StationNo
)
{
/// <summary>Transport: UDP hoặc TCP.</summary>
public IeBasicTransport Transport { get; init; } = IeBasicTransport.Udp;
/// <summary>Định dạng frame SLMP (3E/4E).</summary>
public SlmpFrameFormat FrameFormat { get; init; } = SlmpFrameFormat.Format3E;
/// <summary>Tham số logic phía local.</summary>
public IeBasicLocal Local { get; init; } =
new IeBasicLocal(0, 0x03FF, 0, 0);
/// <summary>Timeout cho mỗi yêu cầu.</summary>
public TimeSpan Timeout { get; init; } = TimeSpan.FromMilliseconds(1000);
/// <summary>Số lần retry khi lỗi tạm thời.</summary>
public int RetryCount { get; init; } = 2;
/// <summary>Tự động reconnect nếu mất liên kết.</summary>
public bool AutoReconnect { get; init; } = true;
/// <summary>
/// Quy ước ghép cặp word trong DWord/Float (true: littleendian word order).
/// </summary>
public bool LittleEndianWordOrder { get; init; } = true;
}
/// <summary>
/// Tùy chọn cho polling chu kỳ các vùng device.
/// </summary>
public sealed record PollOptions
{
/// <summary>Khoảng thời gian giữa hai lần poll.</summary>
public TimeSpan Interval { get; init; } = TimeSpan.FromMilliseconds(20);
/// <summary>Danh sách vùng cần poll (device, địa chỉ bắt đầu, số lượng).</summary>
public (DeviceCode Device, int Start, int Count)[] Areas { get; init; } = [];
/// <summary>Giới hạn số word tối đa mỗi chu kỳ (để tránh gói quá lớn).</summary>
public int MaxWordsPerCycle { get; init; } = 256;
/// <summary>Gom vùng theo từng loại device để tối ưu số gói.</summary>
public bool AlignAreasByDevice { get; init; } = true;
}
/// <summary>
/// Dữ liệu trả về sau mỗi chu kỳ polling.
/// </summary>
public sealed class PollUpdatedEventArgs : EventArgs
{
/// <summary>Thời điểm khung dữ liệu được cập nhật.</summary>
public DateTimeOffset Timestamp { get; init; }
/// <summary>Mảng bit (nếu có vùng bit được cấu hình).</summary>
public ReadOnlyMemory<bool>? Bits { get; init; }
/// <summary>Mảng word (nếu có vùng word được cấu hình).</summary>
public ReadOnlyMemory<ushort>? Words { get; init; }
/// <summary>Mảng dword (nếu có vùng dword được cấu hình).</summary>
public ReadOnlyMemory<uint>? DWords { get; init; }
}
/// <summary>
/// Trạng thái liên kết/SLMP để chẩn đoán và theo dõi.
/// </summary>
public sealed record IeBasicStatus
{
/// <summary>Liên kết hiện “đang lên” (có phản hồi) hay không.</summary>
public bool LinkUp { get; init; }
/// <summary>Số lỗi liên tiếp gần nhất.</summary>
public int ConsecutiveErrors { get; init; }
/// <summary>End code SLMP của lỗi gần nhất (nếu có).</summary>
public SlmpEndCode? LastEndCode { get; init; }
/// <summary>Mô tả lỗi gần nhất (nếu có).</summary>
public string? LastErrorText { get; init; }
/// <summary>Thời gian phản hồi trung bình ước tính.</summary>
public TimeSpan? AvgRtt { get; init; }
/// <summary>Thời gian phản hồi lớn nhất quan sát.</summary>
public TimeSpan? MaxRtt { get; init; }
}
/// <summary>
/// Thông tin nhận dạng CPU/module (nếu PLC cho phép truy vấn).
/// </summary>
public sealed record ModuleIdentity
{
/// <summary>Dòng CPU (ví dụ iQR, iQF).</summary>
public string? CpuSeries { get; init; }
/// <summary>Model cụ thể (ví dụ R04ENCPU).</summary>
public string? CpuModel { get; init; }
/// <summary>Phiên bản firmware.</summary>
public string? Firmware { get; init; }
/// <summary>Nhà sản xuất (thường là Mitsubishi Electric).</summary>
public string? Vendor { get; init; }
/// <summary>Thông tin bổ sung khác (nếu có).</summary>
public string? AdditionalInfo { get; init; }
}
/// <summary>
/// Mã lệnh SLMP rút gọn (có thể mở rộng khi hiện thực adapter).
/// </summary>
public enum SlmpCommand : ushort
{
/// <summary>Đọc device theo dải liên tục (bit/word).</summary>
ReadDevice = 0x0401,
/// <summary>Ghi device theo dải liên tục (bit/word).</summary>
WriteDevice = 0x1401,
/// <summary>Đọc ngẫu nhiên nhiều điểm.</summary>
ReadRandom = 0x0403,
/// <summary>Ghi ngẫu nhiên nhiều điểm.</summary>
WriteRandom = 0x1402,
/// <summary>Node monitor / kiểm tra liên lạc.</summary>
NodeMonitor = 0x0619,
/// <summary>Truy vấn thông tin thiết bị/PLC.</summary>
DeviceInfo = 0x0601
}
/// <summary>
/// Mã end code SLMP (hoàn thành/thất bại), gồm cả mã nội bộ ánh xạ lỗi transport/timeout.
/// </summary>
public enum SlmpEndCode : ushort
{
/// <summary>Hoàn thành thành công.</summary>
Completed = 0x0000,
/// <summary>Lệnh không hợp lệ.</summary>
InvalidCommand = 0xC059,
/// <summary>Truy cập bị từ chối.</summary>
AccessDenied = 0xC056,
/// <summary>Lỗi phạm vi device.</summary>
DeviceRangeError = 0xC051,
/// <summary>Thiết bị bận.</summary>
Busy = 0xCEE0,
/// <summary>Quá thời gian chờ (gán nội bộ).</summary>
Timeout = 0xFFFF,
/// <summary>Lỗi transport/socket (gán nội bộ).</summary>
TransportError = 0xFFFE,
/// <summary>Lỗi không xác định (gán nội bộ).</summary>
Unknown = 0xFFFD
}
/// <summary>
/// Phản hồi SLMP thô trả về từ <see cref="ICcLinkIeBasicClient.SendSlmpAsync"/>.
/// </summary>
public sealed record SlmpResponse
{
/// <summary>End code của phản hồi.</summary>
public SlmpEndCode EndCode { get; init; }
/// <summary>Payload nhị phân trả về (theo frame 3E/4E).</summary>
public ReadOnlyMemory<byte> Payload { get; init; }
/// <summary>Thời điểm nhận gói.</summary>
public DateTimeOffset Timestamp { get; init; }
}