using System;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ICT_WPF.Services.UDP
{
public sealed class IctFirmwareUpdateSession : IDisposable
{
// MFC 상수
private const int CH_BOARD_RESPONSE_TIMEOUT_MS = 500;
private const int F0_RETRY_PERIOD_MS = 200;
private const int F0_TIMEOUT_MS = 10_000; // 50*200ms
private const int F1_ACK_TIMEOUT_MS = 2500; // CH_BOARD_FW_UPDATE_ACK_TIMEOUT
private const int F2_TIMEOUT_MS = 10_000;
private readonly IctUdpService _udp;
private readonly int _ch0Base; // 0 => CH01
private readonly string _boardIp; // "192.168.0.x"
private byte _waitCmd;
private TaskCompletionSource<byte[]?>? _waitRx; // cmd별 단순 대기
public IctFirmwareUpdateSession(IctUdpService udp, int ch0Base)
{
_udp = udp;
_ch0Base = ch0Base;
_boardIp = $"192.168.0.{ch0Base + 1}";
_udp.DatagramReceived += OnDatagram;
}
public void Dispose()
{
_udp.DatagramReceived -= OnDatagram;
}
// RX 프레임 파싱 후 "payload만" 돌려줌 (DATA 부분)
private static bool TryParseFrame(byte[] datagram, out byte cmd, out byte[] payload)
{
cmd = 0;
payload = Array.Empty<byte>();
if (datagram == null || datagram.Length < 12) return false;
if (datagram[0] != IctUdpProtocolLdeict.STX) return false;
if (datagram[^1] != IctUdpProtocolLdeict.ETX) return false;
try
{
cmd = Convert.ToByte(Encoding.ASCII.GetString(datagram, 3, 2), 16);
int len = Convert.ToInt32(Encoding.ASCII.GetString(datagram, 5, 4), 16);
if (len < 0) return false;
if (datagram.Length < 9 + len + 3) return false;
payload = new byte[len];
Buffer.BlockCopy(datagram, 9, payload, 0, len);
return true;
}
catch
{
return false;
}
}
private void OnDatagram(byte[] datagram, IPEndPoint remote)
{
if (remote.Address.ToString() != _boardIp) return;
if (!TryParseFrame(datagram, out var cmd, out var payload)) return;
var tcs = _waitRx;
if (tcs != null && cmd == _waitCmd)
{
if (ReferenceEquals(_waitRx, tcs))
_waitRx = null;
tcs.TrySetResult(payload);
}
}
private async Task<byte[]?> SendAndWaitAsync(byte cmd, byte[] payload, int timeoutMs, CancellationToken ct)
{
_waitCmd = cmd;
var tcs = new TaskCompletionSource<byte[]?>(TaskCreationOptions.RunContinuationsAsynchronously);
_waitRx = tcs;
var frame = IctUdpProtocolLdeict.BuildFrame(_ch0Base, cmd, payload);
await _udp.SendAsync(frame, _boardIp);
var done = await Task.WhenAny(tcs.Task, Task.Delay(timeoutMs, ct));
if (done != tcs.Task)
{
// timeout이면 대기 해제
if (ReferenceEquals(_waitRx, tcs))
_waitRx = null;
return null;
}
return tcs.Task.Result;
}
private static bool IsOkAck(byte[] payload)
{
// ResultCode가 ASCII '0' 이면 OK
return payload != null && payload.Length >= 1 && payload[0] == (byte)'0';
}
// === 외부에서 호출 ===
public async Task<string?> ReadBoardVersionAsync(CancellationToken ct)
{
var payload = await SendAndWaitAsync(IctCmd01Mapper.Cmd, Array.Empty<byte>(), CH_BOARD_RESPONSE_TIMEOUT_MS, ct);
if (payload == null) return null;
if (!IctCmd01Mapper.TryParseRx(payload, out var maj, out var min)) return null;
return IctCmd01Mapper.ToVersionString(maj, min);
}
public async Task<bool> EnterBootloaderByF0Async(CancellationToken ct)
{
var start = Environment.TickCount64;
while (!ct.IsCancellationRequested)
{
var payload = await SendAndWaitAsync(0xF0, Array.Empty<byte>(), F0_RETRY_PERIOD_MS, ct);
if (payload != null && IsOkAck(payload)) return true;
if (Environment.TickCount64 - start > F0_TIMEOUT_MS)
return false;
}
return false;
}
public async Task<bool> SendFirmwareAsync(
byte[] fileBytes,
Action<long, long>? onProgress,
CancellationToken ct)
{
if (fileBytes == null || fileBytes.Length == 0) return false;
long offset = 0;
long total = fileBytes.Length;
while (offset < total)
{
ct.ThrowIfCancellationRequested();
int size = (int)Math.Min(1024, total - offset);
// MFC: dwStartAddress = offset (베이스 없음)
uint addr = (uint)offset;
var payload = IctCmdF1Mapper.BuildTxPayload(addr, fileBytes, (int)offset, size);
// ✅ 이제 rx는 payload만 옴 (cmd 붙어있지 않음)
var ack = await SendAndWaitAsync(0xF1, payload, F1_ACK_TIMEOUT_MS, ct);
if (ack == null || !IsOkAck(ack)) return false;
offset += size;
onProgress?.Invoke(offset, total);
}
// F2 Complete (payload only)
var ack2 = await SendAndWaitAsync(0xF2, Array.Empty<byte>(), F2_TIMEOUT_MS, ct);
if (ack2 == null || !IsOkAck(ack2)) return false;
return true;
}
}
}
해당 클래스는
UDP위에서 요청 -> 해당 응답 하나만 골라서 기다리는 구조
핵심변수 2개
var ack = await SendAndWaitAsync(0xF1, payload, 2500, ct);
if (tcs != null && cmd == _waitCmd)
{
tcs.TrySetResult(payload);
}
SendAndWaitAsync
1. send
2. 기다림
낯선 변수/멤버/함수/클래스 정리
1. _udp