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>
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using ErvProtocol;
|
||||
|
||||
namespace ErvDashboard.Model
|
||||
{
|
||||
// 대시보드 전체 상태 (STATUS 0x81 수신값 + 사용자 제어 의도)
|
||||
public class DashboardState : INotifyPropertyChanged
|
||||
{
|
||||
// ---- ERV 제어/상태 ----
|
||||
bool _powerOn;
|
||||
RunMode _runMode = RunMode.Off;
|
||||
byte _fanMode;
|
||||
bool _hood, _hoodConnected;
|
||||
bool _smartSleep, _comfortCook, _reliefRecover;
|
||||
|
||||
public bool PowerOn { get => _powerOn; set { if (_powerOn != value) { _powerOn = value; OnChanged(); } } }
|
||||
public RunMode RunMode { get => _runMode; set { if (_runMode != value) { _runMode = value; OnChanged(); OnChanged(nameof(IsAuto)); OnChanged(nameof(CanRoomControl)); } } }
|
||||
public bool IsAuto => RunMode == RunMode.Auto;
|
||||
// 각실 댐퍼/LED 수동 제어 가능 여부 (환기/공청/바이패스에서만, 자동 제외)
|
||||
public bool CanRoomControl => RunMode != RunMode.Auto;
|
||||
public byte FanMode { get => _fanMode; set { if (_fanMode != value) { _fanMode = value; OnChanged(); } } }
|
||||
public bool Hood { get => _hood; set { if (_hood != value) { _hood = value; OnChanged(); } } }
|
||||
// 후드 485 통신연결 여부 (STATUS byte5 bit2). 후드연동 ON일 때 연결/미연결 텍스트 표시용
|
||||
public bool HoodConnected { get => _hoodConnected; set { if (_hoodConnected != value) { _hoodConnected = value; OnChanged(); } } }
|
||||
|
||||
// ---- (꺼짐)예약 : 잔여초(STATUS 수신) ----
|
||||
int _reserveRemainSec;
|
||||
public int ReserveRemainSec { get => _reserveRemainSec; set { if (_reserveRemainSec != value) { _reserveRemainSec = value; OnChanged(); OnChanged(nameof(ReserveText)); } } }
|
||||
public string ReserveText => ReserveRemainSec > 0
|
||||
? $"꺼짐까지 {ReserveRemainSec / 3600}:{(ReserveRemainSec % 3600) / 60:00}:{ReserveRemainSec % 60:00}"
|
||||
: "예약 없음";
|
||||
|
||||
public bool SmartSleep { get => _smartSleep; set { if (_smartSleep != value) { _smartSleep = value; OnChanged(); } } }
|
||||
|
||||
// 스마트수면 시간설정(대시보드 전용 — 종료 시각에 자동 해제). 자정 기준 분(0~1439).
|
||||
int _sleepStartMin = 0; // 오전 12:00
|
||||
int _sleepEndMin = 7 * 60 + 30; // 오전 7:30
|
||||
public int SleepStartMin { get => _sleepStartMin; set { if (_sleepStartMin != value) { _sleepStartMin = value; OnChanged(); OnChanged(nameof(SleepSummary)); } } }
|
||||
public int SleepEndMin { get => _sleepEndMin; set { if (_sleepEndMin != value) { _sleepEndMin = value; OnChanged(); OnChanged(nameof(SleepSummary)); } } }
|
||||
public string SleepSummary => $"{DashboardState.FmtTime(SleepStartMin)} ~ {DashboardState.FmtTime(SleepEndMin)}";
|
||||
public static string FmtTime(int min)
|
||||
{
|
||||
min = ((min % 1440) + 1440) % 1440;
|
||||
int h = min / 60, m = min % 60, h12 = h % 12; if (h12 == 0) h12 = 12;
|
||||
return $"{(h < 12 ? "오전" : "오후")} {h12}:{m:00}";
|
||||
}
|
||||
public bool ComfortCook { get => _comfortCook; set { if (_comfortCook != value) { _comfortCook = value; OnChanged(); } } }
|
||||
public bool ReliefRecover { get => _reliefRecover; set { if (_reliefRecover != value) { _reliefRecover = value; OnChanged(); } } }
|
||||
|
||||
public byte SubModeBitmap
|
||||
{
|
||||
get
|
||||
{
|
||||
byte b = 0;
|
||||
if (SmartSleep) b |= SubModeBits.SmartSleep;
|
||||
if (ComfortCook) b |= SubModeBits.ComfortCook;
|
||||
if (ReliefRecover) b |= SubModeBits.ReliefRecover;
|
||||
return b;
|
||||
}
|
||||
set
|
||||
{
|
||||
SmartSleep = (value & SubModeBits.SmartSleep) != 0;
|
||||
ComfortCook = (value & SubModeBits.ComfortCook) != 0;
|
||||
ReliefRecover = (value & SubModeBits.ReliefRecover) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 자동운전 상태 (읽기전용) ----
|
||||
AutoState _autoState = AutoState.Distribute;
|
||||
public AutoState AutoState
|
||||
{
|
||||
get => _autoState;
|
||||
set { if (_autoState != value) { _autoState = value; OnChanged(); OnChanged(nameof(AutoStateText)); } }
|
||||
}
|
||||
public string AutoStateText => AutoState == AutoState.Focus ? "집중" : "분산";
|
||||
|
||||
// 합산 부하점수 (4실 Level 합, 0~16) — STATUS 수신 시 StatusMapper 가 갱신
|
||||
int _totalLoadScore;
|
||||
public int TotalLoadScore
|
||||
{
|
||||
get => _totalLoadScore;
|
||||
set { if (_totalLoadScore != value) { _totalLoadScore = value; OnChanged(); OnChanged(nameof(TotalLoadScoreText)); } }
|
||||
}
|
||||
public string TotalLoadScoreText => $"{TotalLoadScore} / 16";
|
||||
|
||||
// ---- 히스테리시스 ----
|
||||
HystPreset _hystPreset = HystPreset.Normal;
|
||||
int _hystPm25, _hystPm10, _hystVoc, _hystCo2;
|
||||
public HystPreset HystPreset { get => _hystPreset; set { if (_hystPreset != value) { _hystPreset = value; OnChanged(); } } }
|
||||
public int HystPm25 { get => _hystPm25; set { if (_hystPm25 != value) { _hystPm25 = value; OnChanged(); } } }
|
||||
public int HystPm10 { get => _hystPm10; set { if (_hystPm10 != value) { _hystPm10 = value; OnChanged(); } } }
|
||||
public int HystVoc { get => _hystVoc; set { if (_hystVoc != value) { _hystVoc = value; OnChanged(); } } }
|
||||
public int HystCo2 { get => _hystCo2; set { if (_hystCo2 != value) { _hystCo2 = value; OnChanged(); } } }
|
||||
|
||||
// ---- 에러코드 ----
|
||||
int _errorCode;
|
||||
public int ErrorCode { get => _errorCode; set { if (_errorCode != value) { _errorCode = value; OnChanged(); OnChanged(nameof(ErrorCodeHex)); } } }
|
||||
public string ErrorCodeHex => $"0x{ErrorCode:X4}";
|
||||
|
||||
// ---- ERV 리셋 (토글) ----
|
||||
bool _reset;
|
||||
public bool Reset { get => _reset; set { if (_reset != value) { _reset = value; OnChanged(); } } }
|
||||
|
||||
// ---- 풍량 VSP (9엔트리) ----
|
||||
public ObservableCollection<VspRow> Vsp { get; }
|
||||
|
||||
// ---- 히스테리시스 데드밴드 테이블 (ECO/NORMAL/TURBO 별 PM2.5/PM10/VOC/CO2) ----
|
||||
public ObservableCollection<HystRow> HystTable { get; }
|
||||
|
||||
// ---- 모드별 오염단계 임계표 [preset 0 ECO/1 NORMAL/2 TURBO][L1~L4 상한] ----
|
||||
public int[][] Co2Thr { get; } = { new int[4], new int[4], new int[4] };
|
||||
public int[][] Pm25Thr { get; } = { new int[4], new int[4], new int[4] };
|
||||
public int[][] Pm10Thr { get; } = { new int[4], new int[4], new int[4] };
|
||||
public int[][] VocThr { get; } = { new int[4], new int[4], new int[4] };
|
||||
|
||||
// ---- 각실 ----
|
||||
public ObservableCollection<RoomState> Rooms { get; }
|
||||
|
||||
public DashboardState()
|
||||
{
|
||||
Rooms = new ObservableCollection<RoomState>
|
||||
{
|
||||
new(1, "거실"),
|
||||
new(2, "침실1"),
|
||||
new(3, "침실2"),
|
||||
new(4, "침실3"),
|
||||
};
|
||||
// 히스테리시스 기본값 (NORMAL 가정, 펌웨어 m_*_Level 기준)
|
||||
HystPm25 = 30; HystPm10 = 50; HystVoc = 300; HystCo2 = 700;
|
||||
|
||||
// 풍량 VSP 9엔트리 (환기1~4, 바이패스, 공청1~4)
|
||||
Vsp = new ObservableCollection<VspRow>();
|
||||
for (int i = 0; i < VspInfo.Count; i++)
|
||||
Vsp.Add(new VspRow(VspInfo.Labels[i], VspInfo.Group[i], VspInfo.Index[i]));
|
||||
|
||||
// 히스테리시스 데드밴드(하강) 기본값 - 사양서
|
||||
HystTable = new ObservableCollection<HystRow>
|
||||
{
|
||||
new("ECO", 0) { Pm25 = 2, Pm10 = 5, Voc = 5, Co2 = 50 },
|
||||
new("NORMAL", 1) { Pm25 = 2, Pm10 = 5, Voc = 5, Co2 = 50 },
|
||||
new("TURBO", 2) { Pm25 = 2, Pm10 = 5, Voc = 3, Co2 = 30 },
|
||||
};
|
||||
|
||||
// 모드별 오염단계 임계 기본값 - 사양서 (CO2/PM2.5/PM10/VOC 의 L1~L4 상한)
|
||||
int[][] co2 = { new[]{1000,1300,1600,2000}, new[]{800,1100,1400,1700}, new[]{700,1000,1300,1600} };
|
||||
int[][] pm25 = { new[]{20,38,60,86}, new[]{14,29,49,69}, new[]{12,23,38,52} };
|
||||
int[][] pm10 = { new[]{40,86,126,173}, new[]{28,66,102,138}, new[]{24,53,78,104} };
|
||||
int[][] voc = { new[]{171,195,308,438}, new[]{120,150,250,350}, new[]{103,120,192,263} };
|
||||
for (int i = 0; i < 3; i++) for (int k = 0; k < 4; k++)
|
||||
{ Co2Thr[i][k] = co2[i][k]; Pm25Thr[i][k] = pm25[i][k]; Pm10Thr[i][k] = pm10[i][k]; VocThr[i][k] = voc[i][k]; }
|
||||
}
|
||||
|
||||
public RoomState Room(int id) => Rooms[id - 1];
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
void OnChanged([CallerMemberName] string? n = null) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user