using System; using System.Windows.Threading; namespace ERVSimulator.Model { // 펌웨어 [MyMotor.c] Damper_Mode() + Diffuser_Damper_process() 시퀀스를 흉내 // - Cmd 변경 시 18초 시퀀스 트리거 (slot 180 / 120 / 60, 100ms tick) // - 본체 댐퍼 6개: Run_Mode 에 따라 즉시 목표각 세팅 // - 디퓨저 댐퍼: Memory → Current 슬롯별 복사 // - 팬 PWM: 매 tick ±1 ramp public class DamperSequencer { public ErvState State { get; } private readonly DispatcherTimer _timer; private int _diffuserSlot; // 180..0 카운트다운 private int _seqType; // 1=on, 2=off, 3=decrease, 4=increase private int _prevAirVolume; private bool _pendingSequence; public DamperSequencer(ErvState state) { State = state; _timer = new DispatcherTimer(DispatcherPriority.Normal) { Interval = TimeSpan.FromMilliseconds(100) }; _timer.Tick += OnTick; _timer.Start(); } // RoomCon/HomeNet 핸들러가 Run_Mode/Fan_Mode 갱신 후 호출 public void NotifyCommandChanged() { _pendingSequence = true; } void OnTick(object? sender, EventArgs e) { // ---- Fan_Speed_process() 시작부: VENT && Fan=0 → 정지 진입 ---- int newAirVolume = (State.RunMode != RunMode.Off && State.FanMode != 0) ? State.FanMode : 0; if (_pendingSequence) { _pendingSequence = false; _diffuserSlot = 180; _seqType = DetermineSeqType(_prevAirVolume, newAirVolume); _prevAirVolume = newAirVolume; } // ---- Damper_Mode(Run_Mode) — 본체 댐퍼 즉시 목표각 ---- ApplyBodyDamperMode(EffectiveBodyMode()); // ---- Diffuser_Damper_process() — 슬롯 기반 적용 ---- if (_diffuserSlot == 180) { if (_seqType == 1 || _seqType == 4) CopyMemoryToCurrent(1, 2); else if (_seqType == 2 || _seqType == 3) SetFanTargets(); // 즉시 ramp 시작 } else if (_diffuserSlot == 120) { if (_seqType == 1 || _seqType == 4 || _seqType == 2 || _seqType == 3) CopyMemoryToCurrent(3, 4); } else if (_diffuserSlot == 60) { if (_seqType == 1 || _seqType == 4) SetFanTargets(); else if (_seqType == 2 || _seqType == 3) CopyMemoryToCurrent(1, 2); } if (_diffuserSlot > 0) _diffuserSlot--; // ---- 팬 ramp ±1 (펌웨어와 동일) ---- if (State.Fan1Current < State.Fan1Target) State.Fan1Current++; else if (State.Fan1Current > State.Fan1Target) State.Fan1Current--; if (State.Fan2Current < State.Fan2Target) State.Fan2Current++; else if (State.Fan2Current > State.Fan2Target) State.Fan2Current--; } int DetermineSeqType(int prev, int now) { if (prev == 0 && now != 0) return 4; // increase (power on) if (prev != 0 && now == 0) return 3; // decrease (power off) if (prev > now) return 3; return 4; } RunMode EffectiveBodyMode() { // VENT && Fan=0 → 본체 댐퍼는 MODE_OFF 로 진입 (펌웨어 Fan_Speed_process 분기) if (State.RunMode == RunMode.Off) return RunMode.Off; if (State.RunMode == RunMode.Ventilation && State.FanMode == 0 && State.Fan1Current == 0 && State.Fan2Current == 0) return RunMode.Off; return State.RunMode; } // 펌웨어 Damper_Mode() — MyMotor.c:472 void ApplyBodyDamperMode(RunMode mode) { switch (mode) { case RunMode.Ventilation: SetBody(DamperId.OA, 0); SetBody(DamperId.EA, 0); SetBody(DamperId.BYPASS, 100); SetBody(DamperId.SA, 0); SetBody(DamperId.RA, 70); SetBody(DamperId.AIR, 105); break; case RunMode.AirClean: SetBody(DamperId.OA, 100); SetBody(DamperId.EA, 100); SetBody(DamperId.BYPASS, 100); SetBody(DamperId.SA, 0); SetBody(DamperId.RA, 140); SetBody(DamperId.AIR, 0); break; case RunMode.Bypass: SetBody(DamperId.OA, 0); SetBody(DamperId.EA, 0); SetBody(DamperId.BYPASS, 0); SetBody(DamperId.SA, 0); SetBody(DamperId.RA, 140); SetBody(DamperId.AIR, 105); break; case RunMode.Auto: // 펌웨어는 자동 시 공기질에 따라 VENT/AIR 선택. 단순화: VENT 와 동일. SetBody(DamperId.OA, 0); SetBody(DamperId.EA, 0); SetBody(DamperId.BYPASS, 100); SetBody(DamperId.SA, 0); SetBody(DamperId.RA, 70); SetBody(DamperId.AIR, 105); break; case RunMode.Off: default: SetBody(DamperId.OA, 100); SetBody(DamperId.EA, 100); SetBody(DamperId.BYPASS, 100); SetBody(DamperId.SA, 100); SetBody(DamperId.RA, 0); SetBody(DamperId.AIR, 105); break; } } void SetBody(DamperId id, int angle) => State.GetDamper(id).TargetAngle = angle; void CopyMemoryToCurrent(int fromRoom, int toRoom) { for (int r = fromRoom; r <= toRoom; r++) { var room = State.GetRoom(r); room.CurrentSA = room.MemorySA; room.CurrentRA = room.MemoryRA; } } // 펌웨어 Fan_Speed_Setting(Run_Mode, Fan_Mode) — MyMotor.c:1233 void SetFanTargets() { int idx = System.Math.Clamp(State.FanMode, (byte)0, (byte)4); switch (State.RunMode) { case RunMode.Ventilation: case RunMode.Auto: State.Fan1Target = State.FanSAPreset_Vent[idx]; State.Fan2Target = State.FanEAPreset_Vent[idx]; break; case RunMode.Bypass: State.Fan1Target = State.FanSAPreset_Bypass[idx]; State.Fan2Target = State.FanEAPreset_Bypass[idx]; break; case RunMode.AirClean: State.Fan1Target = State.FanSAPreset_Air[idx]; State.Fan2Target = State.FanEAPreset_Air[idx]; break; default: State.Fan1Target = 0; State.Fan2Target = 0; break; } } } }