Files
jeon 5a96a696b1 chore: HERV 통합 저장소 초기 커밋
- 펌웨어(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>
2026-06-15 21:44:23 +09:00

56 lines
2.1 KiB
C#

using System.Collections.Concurrent;
using System.Net.Sockets;
using ErvProtocol;
namespace ErvCollector.Storage
{
// 현장별 활성 소켓 + 최신 상태 보관. 제어 프레임을 해당 현장 EW11 소켓으로 송신.
public sealed class SiteHub
{
readonly ConcurrentDictionary<string, NetworkStream> _sockets = new();
readonly ConcurrentDictionary<string, StatusRecord> _last = new();
readonly ConcurrentDictionary<string, DateTime> _lastSeenUtc = new();
public event Action<string>? Log;
public void SetSocket(string site, NetworkStream s) { _sockets[site] = s; }
public void RemoveSocket(string site, NetworkStream s)
{
// 현재 등록된 것이 이 스트림일 때만 제거(재연결 레이스 방지)
if (_sockets.TryGetValue(site, out var cur) && ReferenceEquals(cur, s))
_sockets.TryRemove(site, out _);
}
public void SetStatus(string site, StatusRecord rec)
{
_last[site] = rec;
_lastSeenUtc[site] = DateTime.UtcNow;
}
public StatusRecord? GetStatus(string site) => _last.TryGetValue(site, out var r) ? r : null;
public DateTime LastSeen(string site) => _lastSeenUtc.TryGetValue(site, out var t) ? t : DateTime.MinValue;
public bool IsOnline(string site) =>
_sockets.ContainsKey(site) && (DateTime.UtcNow - LastSeen(site)) < TimeSpan.FromSeconds(30);
// 제어 프레임 송신. 성공 true.
public bool TrySend(string site, byte[] frame)
{
if (!_sockets.TryGetValue(site, out var stream))
{
Log?.Invoke($"[{site}] 제어 실패: 연결 없음");
return false;
}
try
{
lock (stream) { stream.Write(frame, 0, frame.Length); stream.Flush(); }
return true;
}
catch (Exception ex)
{
Log?.Invoke($"[{site}] 제어 송신 오류: {ex.Message}");
return false;
}
}
}
}