@@ -12,6 +12,9 @@ public class SdoClient : IDisposable
|
||||
private readonly ICanBus _canBus;
|
||||
private readonly byte _nodeId;
|
||||
private readonly ConcurrentDictionary<string, TaskCompletionSource<SdoResponse>> _pendingRequests;
|
||||
// CANopen chỉ cho phép 1 giao dịch SDO tại một thời điểm trên mỗi node.
|
||||
// Hai facade trái/phải của ZLAC8015D dùng chung SdoClient nên phải xếp hàng ở đây.
|
||||
private readonly SemaphoreSlim _transactionLock = new(1, 1);
|
||||
private readonly TimeSpan _timeout;
|
||||
private readonly ILogger<SdoClient>? _logger;
|
||||
private bool _disposed;
|
||||
@@ -31,26 +34,37 @@ public class SdoClient : IDisposable
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(SdoClient));
|
||||
|
||||
var request = SdoRequest.CreateUpload(index, subIndex);
|
||||
var response = await SendRequestAsync(request, cancellationToken);
|
||||
|
||||
if (response.IsAbort)
|
||||
|
||||
await _transactionLock.WaitAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
var abortCode = (SdoAbortCode)(uint)response.AbortCode;
|
||||
var message = $"SDO Upload failed: {abortCode.GetDescription()}";
|
||||
throw new SdoException(_nodeId, index, subIndex, (uint)response.AbortCode, message);
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(SdoClient));
|
||||
|
||||
var request = SdoRequest.CreateUpload(index, subIndex);
|
||||
var response = await SendRequestAsync(request, cancellationToken);
|
||||
|
||||
if (response.IsAbort)
|
||||
{
|
||||
var abortCode = (SdoAbortCode)(uint)response.AbortCode;
|
||||
var message = $"SDO Upload failed: {abortCode.GetDescription()}";
|
||||
throw new SdoException(_nodeId, index, subIndex, (uint)response.AbortCode, message);
|
||||
}
|
||||
|
||||
// Check if expedited or segmented
|
||||
if (response.IsExpedited)
|
||||
{
|
||||
return response.GetDataBytes();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Segmented transfer
|
||||
return await UploadSegmentedAsync(index, subIndex, response, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if expedited or segmented
|
||||
if (response.IsExpedited)
|
||||
finally
|
||||
{
|
||||
return response.GetDataBytes();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Segmented transfer
|
||||
return await UploadSegmentedAsync(index, subIndex, response, cancellationToken);
|
||||
_transactionLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,27 +140,81 @@ public class SdoClient : IDisposable
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(SdoClient));
|
||||
|
||||
if (data.Length <= 4)
|
||||
|
||||
await _transactionLock.WaitAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
// Expedited transfer (≤4 bytes)
|
||||
var request = SdoRequest.CreateDownload(index, subIndex, data);
|
||||
var response = await SendRequestAsync(request, cancellationToken);
|
||||
|
||||
if (response.IsAbort)
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(SdoClient));
|
||||
|
||||
if (data.Length <= 4)
|
||||
{
|
||||
var abortCode = (SdoAbortCode)(uint)response.AbortCode;
|
||||
var message = $"SDO Download failed: {abortCode.GetDescription()}";
|
||||
throw new SdoException(_nodeId, index, subIndex, (uint)response.AbortCode, message);
|
||||
// Expedited transfer (≤4 bytes)
|
||||
var request = SdoRequest.CreateDownload(index, subIndex, data);
|
||||
var response = await SendRequestAsync(request, cancellationToken);
|
||||
|
||||
if (response.IsAbort)
|
||||
{
|
||||
var abortCode = (SdoAbortCode)(uint)response.AbortCode;
|
||||
var message = $"SDO Download failed: {abortCode.GetDescription()}";
|
||||
throw new SdoException(_nodeId, index, subIndex, (uint)response.AbortCode, message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Segmented transfer (>4 bytes)
|
||||
await DownloadSegmentedAsync(index, subIndex, data, cancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
finally
|
||||
{
|
||||
// Segmented transfer (>4 bytes)
|
||||
await DownloadSegmentedAsync(index, subIndex, data, cancellationToken);
|
||||
_transactionLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ghi nhiều object liên tiếp trong MỘT lần giữ transaction lock.
|
||||
/// Dùng khi các lệnh phải đi liền nhau không bị giao dịch SDO khác chen giữa,
|
||||
/// ví dụ lệnh vận tốc hai bánh của ZLAC8015D khi không có RPDO: nếu ghi bằng hai
|
||||
/// DownloadAsync riêng, poll feedback có thể chiếm kênh giữa hai lệnh làm bánh sau
|
||||
/// nhận lệnh muộn hơn hẳn. Chỉ hỗ trợ expedited transfer (data ≤ 4 byte mỗi mục).
|
||||
/// </summary>
|
||||
public async Task DownloadBatchAsync(IReadOnlyList<(ushort Index, byte SubIndex, byte[] Data)> items, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(SdoClient));
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.Data.Length > 4)
|
||||
throw new NotSupportedException("DownloadBatchAsync chỉ hỗ trợ expedited transfer (≤4 byte mỗi mục)");
|
||||
}
|
||||
|
||||
await _transactionLock.WaitAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(SdoClient));
|
||||
|
||||
foreach (var (index, subIndex, data) in items)
|
||||
{
|
||||
var request = SdoRequest.CreateDownload(index, subIndex, data);
|
||||
var response = await SendRequestAsync(request, cancellationToken);
|
||||
|
||||
if (response.IsAbort)
|
||||
{
|
||||
var abortCode = (SdoAbortCode)(uint)response.AbortCode;
|
||||
var message = $"SDO Download failed: {abortCode.GetDescription()}";
|
||||
throw new SdoException(_nodeId, index, subIndex, (uint)response.AbortCode, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_transactionLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download data using segmented transfer (for data > 4 bytes)
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user