feat: 06-17 신규 작업본 반영 (개발사양서/기능검토/승인원/Source 등 추가)

.claude/ 제외(.gitignore 추가). 기존 초기커밋(5a96a69) 위에 신규·수정·이동분 커밋.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-18 07:54:58 +09:00
parent 5a96a696b1
commit 096111e983
529 changed files with 12439 additions and 1166 deletions
@@ -15,6 +15,7 @@ namespace ErvDashboard.Api
state.FanMode = s.FanMode;
state.SubModeBitmap = s.SubMode;
state.Hood = s.HoodEnable; // byte5 bit0 = 연동 Enable
state.HoodRunning = s.HoodRunning; // byte5 bit1 = 연동운전중(메이크업 동작중)
state.HoodConnected = s.HoodConnected; // byte5 bit2 = 후드 통신연결
state.HystPreset = (HystPreset)s.HystPreset;
state.HystPm25 = s.HystPm25;
+6 -1
View File
@@ -81,7 +81,12 @@
</Grid>
<StackPanel Orientation="Horizontal" Margin="0,14,0,0">
<Button Content="변경" Width="90" Style="{StaticResource FlatButton}" Click="Apply_Click"/>
<Button Content="읽어오기" Width="90" Style="{StaticResource FlatButton}" Click="Read_Click"
ToolTip="ERV에서 통신으로 읽은 현재 값을 표에 불러옵니다(편집 내용은 버려짐)."/>
<Button Content="프리셋" Width="90" Style="{StaticResource FlatButton}" Click="PresetDefault_Click"
ToolTip="사양서 기본값을 표에 불러옵니다. ERV로 보내려면 '변경'을 누르세요."/>
<Button Content="변경" Width="90" Style="{StaticResource FlatButton}" Click="Apply_Click"
ToolTip="표의 값을 ERV로 전송합니다."/>
<Button Content="닫기" Width="90" Style="{StaticResource FlatButton}" Click="Close_Click"/>
</StackPanel>
</StackPanel>
@@ -65,6 +65,25 @@ namespace ErvDashboard
MPm10.Text = $"{_state.Pm10Thr[p][3] + 1}~"; MVoc.Text = $"{_state.VocThr[p][3] + 1}~";
}
// 사양서 기본값으로 표 채우기 (전송은 '변경'에서) — 개발사양서 p.10
void FillDefaults(int p)
{
TCo2_1.Text = DashboardState.DefCo2Thr[p][0].ToString(); TCo2_2.Text = DashboardState.DefCo2Thr[p][1].ToString(); TCo2_3.Text = DashboardState.DefCo2Thr[p][2].ToString(); TCo2_4.Text = DashboardState.DefCo2Thr[p][3].ToString();
TPm25_1.Text = DashboardState.DefPm25Thr[p][0].ToString(); TPm25_2.Text = DashboardState.DefPm25Thr[p][1].ToString(); TPm25_3.Text = DashboardState.DefPm25Thr[p][2].ToString(); TPm25_4.Text = DashboardState.DefPm25Thr[p][3].ToString();
TPm10_1.Text = DashboardState.DefPm10Thr[p][0].ToString(); TPm10_2.Text = DashboardState.DefPm10Thr[p][1].ToString(); TPm10_3.Text = DashboardState.DefPm10Thr[p][2].ToString(); TPm10_4.Text = DashboardState.DefPm10Thr[p][3].ToString();
TVoc_1.Text = DashboardState.DefVocThr[p][0].ToString(); TVoc_2.Text = DashboardState.DefVocThr[p][1].ToString(); TVoc_3.Text = DashboardState.DefVocThr[p][2].ToString(); TVoc_4.Text = DashboardState.DefVocThr[p][3].ToString();
var d = DashboardState.DefDeadband[p];
DCo2.Text = d.Co2.ToString(); DPm25.Text = d.Pm25.ToString(); DPm10.Text = d.Pm10.ToString(); DVoc.Text = d.Voc.ToString();
MCo2.Text = $"{DashboardState.DefCo2Thr[p][3] + 1}~"; MPm25.Text = $"{DashboardState.DefPm25Thr[p][3] + 1}~";
MPm10.Text = $"{DashboardState.DefPm10Thr[p][3] + 1}~"; MVoc.Text = $"{DashboardState.DefVocThr[p][3] + 1}~";
}
// '읽어오기' : ERV 통신값(=_state, STATUS로 갱신)으로 표 재채움 (편집 내용 버림)
void Read_Click(object sender, RoutedEventArgs e) => FillGrid((int)_state.HystPreset);
// '프리셋' : 사양서 기본값으로 표 채움 (전송은 '변경')
void PresetDefault_Click(object sender, RoutedEventArgs e) => FillDefaults((int)_state.HystPreset);
static int P(TextBox tb) { int.TryParse(tb.Text, out int v); return v < 0 ? 0 : v > 65535 ? 65535 : v; }
void Preset_Click(object sender, RoutedEventArgs e)
+4 -2
View File
@@ -565,7 +565,9 @@ namespace ErvDashboard
// - 바이패스 : 최대 1단(2~4단 비활성)
// - 환기/공청 : 0~4단
// 시나리오모드 활성 시: 운전모드·풍량·선택 안 된 시나리오모드 비활성화
bool subActive = _state.SmartSleep || _state.ComfortCook || _state.ReliefRecover;
// 쾌적조리는 '연동운전중(HoodRunning=후드 가동중)' 기준으로 시나리오 활성 판단.
// 후드 OFF(대기 상태)면 ERV는 본래 운전모드로 복귀하므로 운전모드를 다시 활성화해야 함(사양 3.1).
bool subActive = _state.SmartSleep || _state.HoodRunning || _state.ReliefRecover;
int fanMax = _state.RunMode == RunMode.Bypass ? 1 : 4;
foreach (var fb in _fanButtons)
{
@@ -576,7 +578,7 @@ namespace ErvDashboard
// 시나리오모드
SetActive(SmartSleepBtn, _state.SmartSleep);
SetActive(ComfortCookBtn, _state.ComfortCook);
SetActive(ComfortCookBtn, _state.HoodRunning); // 메이크업 실제 동작중(후드 가동)일 때만 강조 — 후드 OFF면 해제
SetActive(ReliefRecoverBtn, _state.ReliefRecover);
// (활성 모드 버튼은 OFF 토글 가능해야 하므로 자기 자신은 유지)
SmartSleepBtn.IsEnabled = !subActive || _state.SmartSleep;
@@ -12,7 +12,7 @@ namespace ErvDashboard.Model
bool _powerOn;
RunMode _runMode = RunMode.Off;
byte _fanMode;
bool _hood, _hoodConnected;
bool _hood, _hoodConnected, _hoodRunning;
bool _smartSleep, _comfortCook, _reliefRecover;
public bool PowerOn { get => _powerOn; set { if (_powerOn != value) { _powerOn = value; OnChanged(); } } }
@@ -24,6 +24,8 @@ namespace ErvDashboard.Model
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 byte5 bit1). 후드 OFF면 false → 쾌적조리 표시 해제·운전모드 활성.
public bool HoodRunning { get => _hoodRunning; set { if (_hoodRunning != value) { _hoodRunning = value; OnChanged(); } } }
// ---- (꺼짐)예약 : 잔여초(STATUS 수신) ----
int _reserveRemainSec;
@@ -115,6 +117,20 @@ namespace ErvDashboard.Model
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] };
// ===== 사양서 기본값 (히스테리시스/VSP 창의 '프리셋' 버튼용 — 불변) =====
// 오염단계 임계 [preset 0 ECO/1 NORMAL/2 TURBO][L0~L3 상한] — 개발사양서 p.10
public static readonly int[][] DefCo2Thr = { new[]{1000,1300,1600,2000}, new[]{800,1100,1400,1700}, new[]{700,1000,1300,1600} };
public static readonly int[][] DefPm25Thr = { new[]{20,38,60,86}, new[]{14,29,49,69}, new[]{12,23,38,52} };
public static readonly int[][] DefPm10Thr = { new[]{40,86,126,173}, new[]{28,66,102,138}, new[]{24,53,78,104} };
public static readonly int[][] DefVocThr = { new[]{171,195,308,438}, new[]{120,150,250,350}, new[]{103,120,192,263} };
// 데드밴드(하강) [preset] (Pm25,Pm10,Voc,Co2) — 개발사양서 p.10
public static readonly (int Pm25,int Pm10,int Voc,int Co2)[] DefDeadband =
{ (2,5,5,50), (2,5,5,50), (2,5,3,30) };
// 풍량 VSP 기본값 [9엔트리: 환기1~4/바이패스/공청1~4] (Sa,Ea)
// 개발사양서 p.12 첫 표(휴벤 ECO2/좌타입 HRD1-150EPI) = 펌웨어 MyControl.c 기본값. 환기4(터보)=자동250CMH 행.
public static readonly (int Sa,int Ea)[] DefVsp =
{ (56,57), (63,63), (70,70), (86,85), (67,75), (65,0), (72,0), (78,0), (80,0) };
// ---- 각실 ----
public ObservableCollection<RoomState> Rooms { get; }
+7 -2
View File
@@ -7,7 +7,7 @@
<Border Style="{StaticResource Card}" Margin="10">
<StackPanel>
<TextBlock Text="풍량 VSP 제어 · 상태 (SA 급기 / EA 배기)" Style="{StaticResource CardTitle}"/>
<ItemsControl ItemsSource="{Binding Vsp}" Width="990">
<ItemsControl x:Name="VspList" Width="990">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate><UniformGrid Columns="3"/></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
@@ -26,7 +26,12 @@
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel Orientation="Horizontal" Margin="0,12,0,0">
<Button Content="VSP 적용" Width="100" Style="{StaticResource FlatButton}" Click="Apply_Click"/>
<Button Content="읽어오기" Width="100" Style="{StaticResource FlatButton}" Click="Read_Click"
ToolTip="ERV에서 통신으로 읽은 현재 VSP 값을 불러옵니다(편집 내용은 버려짐)."/>
<Button Content="프리셋" Width="100" Style="{StaticResource FlatButton}" Click="PresetDefault_Click"
ToolTip="사양서 기본 VSP 값을 불러옵니다. ERV로 보내려면 'VSP 적용'을 누르세요."/>
<Button Content="VSP 적용" Width="100" Style="{StaticResource FlatButton}" Click="Apply_Click"
ToolTip="현재 VSP 값을 ERV로 전송합니다."/>
<Button Content="닫기" Width="90" Style="{StaticResource FlatButton}" Click="Close_Click"/>
</StackPanel>
</StackPanel>
+38 -3
View File
@@ -1,21 +1,56 @@
using System.Collections.ObjectModel;
using System.Windows;
using ErvDashboard.Model;
using ErvProtocol;
namespace ErvDashboard
{
// 풍량 VSP 팝업 (환기1~4 / 바이패스 / 공청1~4 SA·EA 편집)
// - 편집 필드는 로컬 작업본(_work). STATUS(1초)가 덮어쓰지 않아 편집이 유지됨.
// - 읽어오기 : ERV 통신값(_state) → 작업본 / 프리셋 : 사양 기본값 → 작업본 / VSP 적용 : 작업본 → ERV
public partial class VspWindow : Window
{
readonly MainWindow _owner;
readonly DashboardState _state;
readonly ObservableCollection<VspRow> _work = new();
public VspWindow(MainWindow owner, DashboardState state)
{
InitializeComponent();
_owner = owner;
DataContext = state;
_owner = owner; _state = state;
for (int i = 0; i < VspInfo.Count; i++)
_work.Add(new VspRow(VspInfo.Labels[i], VspInfo.Group[i], VspInfo.Index[i]));
VspList.ItemsSource = _work;
ReadFromErv(); // 열 때 ERV 현재값으로 시작
}
// ERV 통신값(_state, STATUS 갱신) → 작업본
void ReadFromErv()
{
for (int i = 0; i < _work.Count && i < _state.Vsp.Count; i++)
{ _work[i].Sa = _state.Vsp[i].Sa; _work[i].Ea = _state.Vsp[i].Ea; }
}
void Read_Click(object sender, RoutedEventArgs e) => ReadFromErv();
// 사양서 기본 VSP → 작업본 (전송은 'VSP 적용')
void PresetDefault_Click(object sender, RoutedEventArgs e)
{
var d = DashboardState.DefVsp;
for (int i = 0; i < _work.Count && i < d.Length; i++)
{ _work[i].Sa = d[i].Sa; _work[i].Ea = d[i].Ea; }
}
// 작업본 → _state → ERV 전송
void Apply_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < _work.Count && i < _state.Vsp.Count; i++)
{ _state.Vsp[i].Sa = _work[i].Sa; _state.Vsp[i].Ea = _work[i].Ea; }
_owner.ApplyVsp();
}
void Apply_Click(object sender, RoutedEventArgs e) => _owner.ApplyVsp();
void Close_Click(object sender, RoutedEventArgs e) => Close();
}
}