5a96a696b1
- 펌웨어(program), C# 대시보드(TestProgram), 시뮬레이터(Simulator), 프로토콜/문서(Protocol, doc) 전체를 단일 저장소로 통합 - program 폴더의 별도 git 저장소를 제거하고 통합 저장소에 흡수 - 빌드 산출물(program/build, bin/obj, *.o/.elf/.bin/.hex 등) .gitignore 처리 - 사내 Synology NAS Git 원격 연결 예정 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
117 lines
3.5 KiB
C#
117 lines
3.5 KiB
C#
using System;
|
|
using System.IO.Ports;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace ERVSimulator.Protocol
|
|
{
|
|
// 공용 시리얼 채널 - byte 단위 수신 콜백 + 송신 helper
|
|
public class SerialChannel : IDisposable
|
|
{
|
|
private SerialPort? _port;
|
|
private CancellationTokenSource? _cts;
|
|
private bool _disposed;
|
|
|
|
public string ChannelName { get; }
|
|
public event Action<byte>? ByteReceived;
|
|
public event Action<string>? Log;
|
|
public event Action<bool>? ConnectionChanged;
|
|
public bool IsConnected => _port?.IsOpen == true;
|
|
|
|
public SerialChannel(string channelName) { ChannelName = channelName; }
|
|
|
|
public static string[] GetAvailablePorts() => SerialPort.GetPortNames();
|
|
|
|
public bool Connect(string portName, int baudRate)
|
|
{
|
|
try
|
|
{
|
|
Disconnect();
|
|
_port = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One)
|
|
{
|
|
ReadTimeout = 100,
|
|
WriteTimeout = 200,
|
|
Handshake = Handshake.None,
|
|
DtrEnable = false,
|
|
RtsEnable = false,
|
|
};
|
|
_port.Open();
|
|
_cts = new CancellationTokenSource();
|
|
_ = Task.Run(() => ReadLoop(_cts.Token));
|
|
Log?.Invoke($"[{ChannelName}] Connected {portName} @ {baudRate}");
|
|
ConnectionChanged?.Invoke(true);
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log?.Invoke($"[{ChannelName}] Connect FAIL: {ex.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public void Disconnect()
|
|
{
|
|
try { _cts?.Cancel(); } catch { }
|
|
try { _port?.Close(); } catch { }
|
|
_port?.Dispose();
|
|
_port = null;
|
|
ConnectionChanged?.Invoke(false);
|
|
}
|
|
|
|
void ReadLoop(CancellationToken ct)
|
|
{
|
|
var buf = new byte[64];
|
|
while (!ct.IsCancellationRequested && _port != null && _port.IsOpen)
|
|
{
|
|
try
|
|
{
|
|
int n = _port.Read(buf, 0, buf.Length);
|
|
for (int i = 0; i < n; i++) ByteReceived?.Invoke(buf[i]);
|
|
}
|
|
catch (TimeoutException) { /* expected */ }
|
|
catch (Exception ex)
|
|
{
|
|
Log?.Invoke($"[{ChannelName}] ReadLoop error: {ex.Message}");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool Send(byte[] data, int length)
|
|
{
|
|
if (_port == null || !_port.IsOpen) return false;
|
|
try
|
|
{
|
|
_port.Write(data, 0, length);
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log?.Invoke($"[{ChannelName}] Send FAIL: {ex.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed) return;
|
|
_disposed = true;
|
|
Disconnect();
|
|
}
|
|
}
|
|
|
|
public static class HexFormat
|
|
{
|
|
public static string Bytes(byte[] data, int length)
|
|
{
|
|
var sb = new System.Text.StringBuilder(length * 3);
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
if (i > 0) sb.Append(' ');
|
|
sb.Append(data[i].ToString("X2"));
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
}
|