chore: HERV 통합 저장소 재초기화 커밋
손상된 .git 히스토리(missing tree)로 재초기화 후 작업트리 전체 커밋. .claude/ 만 제외(로컬 에이전트 설정). 구 저장소 백업(.git_corrupt_backup/) 포함. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ERVSimulator.Model
|
||||
{
|
||||
// 6 본체 댐퍼 (Damper_Mode()가 직접 각도 명령)
|
||||
public class BodyDamper : INotifyPropertyChanged
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Connector { get; } // CN2, CN10 ...
|
||||
public string ColorTag { get; } // GREEN, YELLOW ...
|
||||
public DamperId Id { get; }
|
||||
|
||||
private int _targetAngle;
|
||||
public int TargetAngle
|
||||
{
|
||||
get => _targetAngle;
|
||||
set { if (_targetAngle != value) { _targetAngle = value; OnChanged(); OnChanged(nameof(IsOpen)); } }
|
||||
}
|
||||
|
||||
// 펌웨어 주석: 90 = close, 0 = open, 100/105 = close 변형
|
||||
// RA(환기)만 '3step--reverse' → 0=닫힘, 70/140=열림 으로 규칙 반대 (MyMotor.c:482)
|
||||
// → 전원 OFF(RA=0) 시 6개 댐퍼 모두 닫힘으로 표시
|
||||
public bool IsOpen => Id == DamperId.RA ? TargetAngle >= 50 : TargetAngle < 50;
|
||||
|
||||
public BodyDamper(DamperId id, string name, string cn, string color)
|
||||
{
|
||||
Id = id; Name = name; Connector = cn; ColorTag = color;
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
void OnChanged([CallerMemberName] string? n = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n));
|
||||
}
|
||||
|
||||
// 디퓨저 각실 (1~4)
|
||||
public class DiffuserRoom : INotifyPropertyChanged
|
||||
{
|
||||
public int RoomId { get; }
|
||||
public string Name { get; }
|
||||
|
||||
int _memorySA, _memoryRA, _currentSA, _currentRA, _light;
|
||||
// Memory_* (RoomCon/HomeNet 핸들러가 즉시 갱신, 목표값)
|
||||
public int MemorySA { get => _memorySA; set { if (_memorySA != value) { _memorySA = value; OnChanged(); } } }
|
||||
public int MemoryRA { get => _memoryRA; set { if (_memoryRA != value) { _memoryRA = value; OnChanged(); } } }
|
||||
// Diffuser_Dmp_Ang_* (시퀀서가 슬롯 시간에 Memory→Current 복사)
|
||||
public int CurrentSA { get => _currentSA; set { if (_currentSA != value) { _currentSA = value; OnChanged(); OnChanged(nameof(IsOpenSA)); } } }
|
||||
public int CurrentRA { get => _currentRA; set { if (_currentRA != value) { _currentRA = value; OnChanged(); OnChanged(nameof(IsOpenRA)); } } }
|
||||
public int LightBright { get => _light; set { if (_light != value) { _light = value; OnChanged(); } } }
|
||||
// 디퓨저 응답이 echo 한 실제 LED 단수 (디퓨저 수동 LED 제어 시 ERV 명령과 다를 수 있음) → STATUS 로 송신
|
||||
int _ledReported;
|
||||
public int LedReported { get => _ledReported; set { if (_ledReported != value) { _ledReported = value; OnChanged(); } } }
|
||||
// 수동 LED 조작(CTRL_LED) 시 true → 자동로직이 LED 를 덮어쓰지 않음(예외). 비-수동모드 진입 시 해제.
|
||||
public bool LedManual { get; set; }
|
||||
// 수동 댐퍼 조작(CTRL_DAMPER) 시 true → 비자동(환기/공청/바이패스)에서 자동개방 덮어쓰기 안 함. 자동/부가모드/전원OFF/모드전환 시 해제.
|
||||
public bool DamperManual { get; set; }
|
||||
public bool IsOpenSA => CurrentSA > 0;
|
||||
public bool IsOpenRA => CurrentRA > 0;
|
||||
|
||||
// ---- 공기질 센서값 (DiffuserSimulator 응답에서 수신) ----
|
||||
int _co2, _pm25, _pm10, _voc, _level, _airQuality = 4, _temp, _humi;
|
||||
public int Co2 { get => _co2; set { if (_co2 != value) { _co2 = value; OnChanged(); } } }
|
||||
public int Pm25 { get => _pm25; set { if (_pm25 != value) { _pm25 = value; OnChanged(); } } }
|
||||
public int Pm10 { get => _pm10; set { if (_pm10 != value) { _pm10 = value; OnChanged(); } } }
|
||||
public int Voc { get => _voc; set { if (_voc != value) { _voc = value; OnChanged(); } } }
|
||||
public int Temp { get => _temp; set { if (_temp != value) { _temp = value; OnChanged(); } } }
|
||||
public int Humi { get => _humi; set { if (_humi != value) { _humi = value; OnChanged(); } } }
|
||||
// 오염 단계 0~4 (자동로직 산출)
|
||||
public int Level { get => _level; set { if (_level != value) { _level = value; OnChanged(); OnChanged(nameof(SensorText)); } } }
|
||||
// 공기질 코드 1매우나쁨~4좋음 (STATUS 송신용)
|
||||
public int AirQuality { get => _airQuality; set { if (_airQuality != value) { _airQuality = value; OnChanged(); } } }
|
||||
|
||||
public string SensorText => $"CO2 {Co2} PM2.5 {Pm25} PM10 {Pm10} VOC {Voc} → Lv{Level}";
|
||||
|
||||
public DiffuserRoom(int id, string name) { RoomId = id; Name = name; }
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
void OnChanged([CallerMemberName] string? n = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n));
|
||||
}
|
||||
|
||||
public class ErvState : INotifyPropertyChanged
|
||||
{
|
||||
// ---- 상위 상태 ----
|
||||
bool _powerOn;
|
||||
RunMode _runMode = RunMode.Off;
|
||||
RunMode _setRunMode = RunMode.Off;
|
||||
byte _fanMode; // 0~4 단
|
||||
byte _setFanMode;
|
||||
|
||||
public bool PowerOn { get => _powerOn; set { if (_powerOn != value) { _powerOn = value; OnChanged(); } } }
|
||||
public RunMode RunMode { get => _runMode; set { if (_runMode != value) { _runMode = value; OnChanged(); } } }
|
||||
public RunMode SetRunMode { get => _setRunMode; set { if (_setRunMode != value) { _setRunMode = value; OnChanged(); } } }
|
||||
public byte FanMode { get => _fanMode; set { if (_fanMode != value) { _fanMode = value; OnChanged(); } } }
|
||||
public byte SetFanMode { get => _setFanMode; set { if (_setFanMode != value) { _setFanMode = value; OnChanged(); } } }
|
||||
|
||||
// ---- 260520 자동 동작로직 상태 ----
|
||||
byte _hystPreset = 1; // 0 ECO / 1 NORMAL / 2 TURBO
|
||||
bool _autoConcentrate; // false 분산 / true 집중
|
||||
int _loadScore, _pMax, _dP;
|
||||
public byte HystPreset { get => _hystPreset; set { if (_hystPreset != value) { _hystPreset = value; OnChanged(); } } }
|
||||
public bool AutoConcentrate { get => _autoConcentrate; set { if (_autoConcentrate != value) { _autoConcentrate = value; OnChanged(); OnChanged(nameof(AutoStateText)); } } }
|
||||
public int LoadScore { get => _loadScore; set { if (_loadScore != value) { _loadScore = value; OnChanged(); OnChanged(nameof(AutoStateText)); } } }
|
||||
public int PMax { get => _pMax; set { if (_pMax != value) { _pMax = value; OnChanged(); } } }
|
||||
public int DP { get => _dP; set { if (_dP != value) { _dP = value; OnChanged(); } } }
|
||||
public string AutoStateText => RunMode == RunMode.Auto
|
||||
? $"{(PMax == 0 ? "대기" : AutoConcentrate ? "집중" : "분산")} · Score {LoadScore} · {FanMode}단"
|
||||
: "(자동모드 아님)";
|
||||
|
||||
// 부가모드 (월패드 토글/버튼)
|
||||
byte _extRunMode;
|
||||
bool _hoodEnable;
|
||||
public byte ExtRunMode { get => _extRunMode; set { if (_extRunMode != value) { _extRunMode = value; OnChanged(); OnChanged(nameof(SubModeText)); } } } // 1 안심회복 / 4 스마트수면
|
||||
public bool HoodEnable { get => _hoodEnable; set { if (_hoodEnable != value) { _hoodEnable = value; OnChanged(); OnChanged(nameof(SubModeText)); } } } // 후드연동(쾌적조리)
|
||||
public bool HoodStatus { get; set; }
|
||||
public byte ResetState { get; set; } // ERV 리셋 토글 echo
|
||||
|
||||
// ---- 후드(HOOD 프로토콜 Rev1.3) 슬레이브 보고값 ----
|
||||
bool _hoodConnected;
|
||||
public bool HoodConnected { get => _hoodConnected; set { if (_hoodConnected != value) { _hoodConnected = value; OnChanged(); } } } // 후드 폴 응답 생존(통신 연결)
|
||||
int _hoodFan; bool _hoodLight; bool _hoodCmd; int _hoodError;
|
||||
public int HoodFan { get => _hoodFan; set { if (_hoodFan != value) { _hoodFan = value; OnChanged(); } } } // 후드 FAN STATUS 0~5
|
||||
public bool HoodLight { get => _hoodLight; set { if (_hoodLight != value) { _hoodLight = value; OnChanged(); } } } // 후드 LIGHT STATUS
|
||||
public bool HoodCmd { get => _hoodCmd; set { if (_hoodCmd != value) { _hoodCmd = value; OnChanged(); } } } // 연동 CMD(후드 동작중)
|
||||
public int HoodError { get => _hoodError; set { if (_hoodError != value) { _hoodError = value; OnChanged(); } } } // ERROR : 0 정상 / 1 FAN / 2 기타
|
||||
|
||||
public bool SmartSleep { get => ExtRunMode == 4; } // 스마트수면
|
||||
public bool CookingMode { get => HoodEnable; } // 쾌적조리(후드연동)
|
||||
public bool RecoveryMode { get => ExtRunMode == 1; } // 안심회복
|
||||
public string SubModeText
|
||||
{
|
||||
get
|
||||
{
|
||||
var s = "";
|
||||
if (SmartSleep) s += "스마트수면 ";
|
||||
if (CookingMode) s += "쾌적조리 ";
|
||||
if (RecoveryMode) s += "안심회복 ";
|
||||
return s.Length == 0 ? "없음" : s.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
// ---- (꺼짐)예약 0~8시간 ----
|
||||
int _reserveHours; // 0 = 해제
|
||||
int _reserveRemainSec;
|
||||
public int ReserveHours { get => _reserveHours; set { if (_reserveHours != value) { _reserveHours = value; OnChanged(); } } }
|
||||
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}"
|
||||
: "예약 없음";
|
||||
|
||||
// 히스테리시스 데드밴드(하강) [preset] : CO2,PM2.5,PM10,VOC (사양서 10p)
|
||||
public ushort[] Co2Db { get; } = { 50, 50, 30 };
|
||||
public ushort[] Pm25Db { get; } = { 2, 2, 2 };
|
||||
public ushort[] Pm10Db { get; } = { 5, 5, 5 };
|
||||
public ushort[] VocDb { get; } = { 5, 5, 3 };
|
||||
// 모드별(ECO/NORMAL/TURBO) 오염단계 상한 임계 [preset][레벨1~4]
|
||||
public ushort[][] Co2Thr { get; } = { new ushort[]{1000,1300,1600,2000}, new ushort[]{800,1100,1400,1700}, new ushort[]{700,1000,1300,1600} };
|
||||
public ushort[][] Pm25Thr { get; } = { new ushort[]{20,38,60,86}, new ushort[]{14,29,49,69}, new ushort[]{12,23,38,52} };
|
||||
public ushort[][] Pm10Thr { get; } = { new ushort[]{40,86,126,173}, new ushort[]{28,66,102,138}, new ushort[]{24,53,78,104} };
|
||||
public ushort[][] VocThr { get; } = { new ushort[]{171,195,308,438}, new ushort[]{120,150,250,350}, new ushort[]{103,120,192,263} };
|
||||
|
||||
// ---- 본체 6 댐퍼 ----
|
||||
public ObservableCollection<BodyDamper> BodyDampers { get; }
|
||||
|
||||
// ---- 각실 디퓨저 4 룸 ----
|
||||
public ObservableCollection<DiffuserRoom> Rooms { get; }
|
||||
|
||||
// ---- 팬 (BLDC SA/EA) ----
|
||||
// 펌웨어 PWM duty 0~10000 매핑. UI는 0~10000 슬라이드로 표시 + 환산 RPM 추정.
|
||||
int _fan1Target, _fan1Current; // SA
|
||||
int _fan2Target, _fan2Current; // EA
|
||||
public int Fan1Target { get => _fan1Target; set { if (_fan1Target != value) { _fan1Target = value; OnChanged(); } } }
|
||||
public int Fan1Current { get => _fan1Current; set { if (_fan1Current != value) { _fan1Current = value; OnChanged(); } } }
|
||||
public int Fan2Target { get => _fan2Target; set { if (_fan2Target != value) { _fan2Target = value; OnChanged(); } } }
|
||||
public int Fan2Current { get => _fan2Current; set { if (_fan2Current != value) { _fan2Current = value; OnChanged(); } } }
|
||||
|
||||
// ---- 에러 코드 (PPT 매핑 + HERV 펌웨어 My_define.h:206 비트맵) ----
|
||||
public const byte ERR_FILTER_CLEAN = 0x01;
|
||||
public const byte ERR_FILTER_CHANGE = 0x02;
|
||||
public const byte ERR_SOJA_CHANGE = 0x04;
|
||||
public const byte ERR_TEMP_SENSOR = 0x08; // E02 온도센서 에러
|
||||
public const byte ERR_PROTECT = 0x10; // COLD 장비보호모드
|
||||
public const byte ERR_EA_FAN = 0x20; // E10 배기(EA)팬 에러
|
||||
public const byte ERR_SOMETIME = 0x40; // E07 내부통신 에러
|
||||
public const byte ERR_SA_FAN = 0x80; // E09 급기(SA)팬 에러
|
||||
|
||||
byte _errorCode;
|
||||
public byte ErrorCode
|
||||
{
|
||||
get => _errorCode;
|
||||
set
|
||||
{
|
||||
if (_errorCode != value)
|
||||
{
|
||||
_errorCode = value;
|
||||
OnChanged();
|
||||
OnChanged(nameof(E02_TempSensor));
|
||||
OnChanged(nameof(E09_SaFan));
|
||||
OnChanged(nameof(E10_EaFan));
|
||||
OnChanged(nameof(COLD_Protect));
|
||||
OnChanged(nameof(E07_InternalComm));
|
||||
OnChanged(nameof(FilterClean));
|
||||
OnChanged(nameof(FilterChange));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 알람(유지보수) — 필터 청소/교환. 룸콘·대시보드로 ErrorCode 비트로 전달.
|
||||
public bool FilterClean { get => (ErrorCode & ERR_FILTER_CLEAN) != 0; set => SetErr(ERR_FILTER_CLEAN, value); }
|
||||
public bool FilterChange { get => (ErrorCode & ERR_FILTER_CHANGE) != 0; set => SetErr(ERR_FILTER_CHANGE, value); }
|
||||
|
||||
public bool E02_TempSensor { get => (ErrorCode & ERR_TEMP_SENSOR) != 0; set => SetErr(ERR_TEMP_SENSOR, value); }
|
||||
public bool E09_SaFan { get => (ErrorCode & ERR_SA_FAN) != 0; set => SetErr(ERR_SA_FAN, value); }
|
||||
public bool E10_EaFan { get => (ErrorCode & ERR_EA_FAN) != 0; set => SetErr(ERR_EA_FAN, value); }
|
||||
public bool COLD_Protect { get => (ErrorCode & ERR_PROTECT) != 0; set => SetErr(ERR_PROTECT, value); }
|
||||
public bool E07_InternalComm { get => (ErrorCode & ERR_SOMETIME) != 0; set => SetErr(ERR_SOMETIME, value); }
|
||||
|
||||
void SetErr(byte bit, bool on)
|
||||
{
|
||||
byte newVal = on ? (byte)(_errorCode | bit) : (byte)(_errorCode & ~bit);
|
||||
ErrorCode = newVal;
|
||||
}
|
||||
|
||||
// 1~4단 VSP preset (1바이트 0~255). 기본값 = 사양서 DL H-ERV VSP 실측표 (index 1~4)
|
||||
public ushort[] FanSAPreset_Vent { get; } = { 0, 56, 63, 70, 86 }; // 환기 SA
|
||||
public ushort[] FanEAPreset_Vent { get; } = { 0, 57, 63, 70, 85 }; // 환기 EA
|
||||
public ushort[] FanSAPreset_Bypass { get; } = { 0, 67, 0, 0, 0 }; // 바이패스 SA (기본단)
|
||||
public ushort[] FanEAPreset_Bypass { get; } = { 0, 75, 0, 0, 0 }; // 바이패스 EA
|
||||
public ushort[] FanSAPreset_Air { get; } = { 0, 65, 72, 78, 80 }; // 공청 SA
|
||||
public ushort[] FanEAPreset_Air { get; } = { 0, 0, 0, 0, 0 }; // 공청 EA (미사용 '-')
|
||||
|
||||
public ErvState()
|
||||
{
|
||||
BodyDampers = new ObservableCollection<BodyDamper>
|
||||
{
|
||||
// PPT 순서/색상 매핑
|
||||
new(DamperId.OA, "외기(OA)", "CN2", "GREEN"),
|
||||
new(DamperId.AIR, "공청(AIR)", "CN10", "YELLOW"),
|
||||
new(DamperId.BYPASS, "바이패스", "CN5", "RED"),
|
||||
new(DamperId.EA, "배기(EA)", "CN3", "BLACK"),
|
||||
new(DamperId.SA, "급기(SA)", "CN7", "BLUE"),
|
||||
new(DamperId.RA, "환기(RA) 3단", "CN9", "WHITE"),
|
||||
};
|
||||
|
||||
Rooms = new ObservableCollection<DiffuserRoom>
|
||||
{
|
||||
new(1, "거실"),
|
||||
new(2, "침실1"),
|
||||
new(3, "침실2"),
|
||||
new(4, "침실3"),
|
||||
};
|
||||
}
|
||||
|
||||
public BodyDamper GetDamper(DamperId id)
|
||||
{
|
||||
foreach (var d in BodyDampers) if (d.Id == id) return d;
|
||||
throw new System.InvalidOperationException();
|
||||
}
|
||||
|
||||
public DiffuserRoom GetRoom(int roomId)
|
||||
{
|
||||
foreach (var r in Rooms) if (r.RoomId == roomId) return r;
|
||||
throw new System.InvalidOperationException();
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
void OnChanged([CallerMemberName] string? n = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user