5a96a696b1
- 펌웨어(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>
486 lines
22 KiB
C#
486 lines
22 KiB
C#
using System;
|
|
using System.Globalization;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Data;
|
|
using System.Windows.Media;
|
|
using System.Windows.Threading;
|
|
using ERVSimulator.Model;
|
|
using ERVSimulator.Protocol;
|
|
|
|
namespace ERVSimulator
|
|
{
|
|
public partial class MainWindow : Window
|
|
{
|
|
readonly ErvState _state = new();
|
|
readonly DamperSequencer _seq;
|
|
readonly SerialChannel _roomConCh = new("RoomCon");
|
|
readonly SerialChannel _homeNetCh = new("HomeNet");
|
|
readonly SerialChannel _diffuserCh = new("Diffuser");
|
|
readonly SerialChannel _hoodCh = new("Hood");
|
|
readonly HomeNetProtocol _homeNet;
|
|
readonly DiffuserMasterProtocol _diffuser;
|
|
readonly HoodMasterProtocol _hood;
|
|
readonly RoomConProtocol _roomCon;
|
|
readonly AutoLogic _autoLogic;
|
|
readonly DispatcherTimer _uiTick;
|
|
readonly DispatcherTimer _reserveTick;
|
|
|
|
// Tokyo Night palette refs
|
|
static readonly Brush ConnectedLed = (Brush)new BrushConverter().ConvertFromString("#9ECE6A")!;
|
|
static readonly Brush DisconnectedLed = (Brush)new BrushConverter().ConvertFromString("#F7768E")!;
|
|
static readonly Brush AccentCyan = (Brush)new BrushConverter().ConvertFromString("#7DCFFF")!;
|
|
static readonly Brush AccentGreen = (Brush)new BrushConverter().ConvertFromString("#9ECE6A")!;
|
|
static readonly Brush AccentRed = (Brush)new BrushConverter().ConvertFromString("#F7768E")!;
|
|
static readonly Brush AccentYellow = (Brush)new BrushConverter().ConvertFromString("#E0AF68")!;
|
|
static readonly Brush AccentOrange = (Brush)new BrushConverter().ConvertFromString("#FF9E64")!;
|
|
static readonly Brush AccentBlue = (Brush)new BrushConverter().ConvertFromString("#7AA2F7")!;
|
|
static readonly Brush CardBg = (Brush)new BrushConverter().ConvertFromString("#313147")!;
|
|
static readonly Brush TextSecondary = (Brush)new BrushConverter().ConvertFromString("#565F89")!;
|
|
static readonly Brush TextPrimary = (Brush)new BrushConverter().ConvertFromString("#C0CAF5")!;
|
|
static readonly Brush BorderColor = (Brush)new BrushConverter().ConvertFromString("#3B3B55")!;
|
|
static readonly Brush LedOff = (Brush)new BrushConverter().ConvertFromString("#3B3B55")!;
|
|
|
|
public MainWindow()
|
|
{
|
|
InitializeComponent();
|
|
_seq = new DamperSequencer(_state);
|
|
_homeNet = new HomeNetProtocol(_homeNetCh, _state, _seq, Dispatcher);
|
|
_diffuser = new DiffuserMasterProtocol(_diffuserCh, _state, Dispatcher);
|
|
_hood = new HoodMasterProtocol(_hoodCh, _state, Dispatcher);
|
|
_roomCon = new RoomConProtocol(_roomConCh, _state, _seq, Dispatcher);
|
|
_autoLogic = new AutoLogic(_state, _seq);
|
|
|
|
DamperItems.ItemsSource = _state.BodyDampers;
|
|
RoomLoadItems.ItemsSource = _state.Rooms; // 자동운전 상태 - 각실 부하점수
|
|
|
|
_roomConCh.Log += Log;
|
|
_homeNetCh.Log += Log;
|
|
_diffuserCh.Log += Log;
|
|
_hoodCh.Log += Log;
|
|
_roomConCh.ConnectionChanged += b => Dispatcher.BeginInvoke(() => UpdateChannelLed(RoomConStatus, RoomConStatusText, b));
|
|
_homeNetCh.ConnectionChanged += b => Dispatcher.BeginInvoke(() => UpdateChannelLed(HomeNetStatus, HomeNetStatusText, b));
|
|
_diffuserCh.ConnectionChanged += b => Dispatcher.BeginInvoke(() => UpdateChannelLed(DiffuserStatus, DiffuserStatusText, b));
|
|
_hoodCh.ConnectionChanged += b => Dispatcher.BeginInvoke(() => UpdateChannelLed(HoodStatus, HoodStatusText, b));
|
|
_homeNet.PacketReceived += Log;
|
|
_homeNet.PacketSent += Log;
|
|
_diffuser.PacketReceived += Log;
|
|
_diffuser.PacketSent += Log;
|
|
_hood.PacketReceived += Log;
|
|
_hood.PacketSent += Log;
|
|
_roomCon.PacketReceived += Log;
|
|
_roomCon.PacketSent += Log;
|
|
_autoLogic.Log += Log;
|
|
|
|
_state.PropertyChanged += (_, e) => Dispatcher.BeginInvoke(() =>
|
|
{
|
|
UpdateTopState();
|
|
if (e.PropertyName == nameof(ErvState.ErrorCode) ||
|
|
e.PropertyName == nameof(ErvState.E02_TempSensor) ||
|
|
e.PropertyName == nameof(ErvState.E09_SaFan) ||
|
|
e.PropertyName == nameof(ErvState.E10_EaFan) ||
|
|
e.PropertyName == nameof(ErvState.COLD_Protect) ||
|
|
e.PropertyName == nameof(ErvState.E07_InternalComm))
|
|
{
|
|
UpdateErrorIndicators();
|
|
}
|
|
});
|
|
|
|
_uiTick = new DispatcherTimer(DispatcherPriority.Normal) { Interval = TimeSpan.FromMilliseconds(100) };
|
|
_uiTick.Tick += (_, _) => UpdateRealtime();
|
|
_uiTick.Start();
|
|
|
|
// (꺼짐)예약 1초 카운트다운
|
|
_reserveTick = new DispatcherTimer(DispatcherPriority.Normal) { Interval = TimeSpan.FromSeconds(1) };
|
|
_reserveTick.Tick += (_, _) => ReserveTick();
|
|
_reserveTick.Start();
|
|
|
|
RefreshPortsList();
|
|
UpdateTopState();
|
|
UpdateRealtime();
|
|
UpdateErrorIndicators();
|
|
}
|
|
|
|
// ---- 시리얼 연결 ----
|
|
void RefreshPorts_Click(object sender, RoutedEventArgs e) => RefreshPortsList();
|
|
|
|
void RefreshPortsList()
|
|
{
|
|
var ports = SerialChannel.GetAvailablePorts();
|
|
RoomConPortCombo.ItemsSource = ports;
|
|
HomeNetPortCombo.ItemsSource = ports;
|
|
DiffuserPortCombo.ItemsSource = ports;
|
|
HoodPortCombo.ItemsSource = ports;
|
|
if (ports.Length > 0)
|
|
{
|
|
if (RoomConPortCombo.SelectedIndex < 0) RoomConPortCombo.SelectedIndex = 0;
|
|
if (HomeNetPortCombo.SelectedIndex < 0) HomeNetPortCombo.SelectedIndex = ports.Length > 1 ? 1 : 0;
|
|
if (DiffuserPortCombo.SelectedIndex < 0) DiffuserPortCombo.SelectedIndex = ports.Length > 2 ? 2 : 0;
|
|
if (HoodPortCombo.SelectedIndex < 0) HoodPortCombo.SelectedIndex = ports.Length > 3 ? 3 : 0;
|
|
}
|
|
}
|
|
|
|
void DiffuserConnect_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (DiffuserPortCombo.SelectedItem is string p) _diffuserCh.Connect(p, 115200);
|
|
}
|
|
void DiffuserDisconnect_Click(object sender, RoutedEventArgs e) => _diffuserCh.Disconnect();
|
|
|
|
void HoodConnect_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (HoodPortCombo.SelectedItem is string p) _hoodCh.Connect(p, 115200);
|
|
}
|
|
void HoodDisconnect_Click(object sender, RoutedEventArgs e) => _hoodCh.Disconnect();
|
|
|
|
void RoomConConnect_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (RoomConPortCombo.SelectedItem is string p) _roomConCh.Connect(p, 9600);
|
|
}
|
|
void RoomConDisconnect_Click(object sender, RoutedEventArgs e) => _roomConCh.Disconnect();
|
|
|
|
void HomeNetConnect_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (HomeNetPortCombo.SelectedItem is string p)
|
|
{
|
|
int baud = 9600;
|
|
if (HomeNetBaudCombo.SelectedItem is ComboBoxItem item &&
|
|
int.TryParse(item.Content?.ToString(), out var b)) baud = b;
|
|
_homeNetCh.Connect(p, baud);
|
|
}
|
|
}
|
|
void HomeNetDisconnect_Click(object sender, RoutedEventArgs e) => _homeNetCh.Disconnect();
|
|
|
|
void UpdateChannelLed(System.Windows.Shapes.Ellipse led, TextBlock text, bool connected)
|
|
{
|
|
led.Fill = connected ? ConnectedLed : DisconnectedLed;
|
|
text.Text = connected ? "연결됨" : "미연결";
|
|
text.Foreground = connected ? AccentGreen : TextSecondary;
|
|
}
|
|
|
|
// ---- 운전 모드 버튼 ----
|
|
void ModeButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (sender is Button b && b.Tag is string tag && Enum.TryParse<RunMode>(tag, out var m))
|
|
{
|
|
_state.RunMode = m;
|
|
_state.SetRunMode = m;
|
|
// 운전모드 전환 시 풍량 1단 (자동 제외 — 자동은 부하점수로 결정)
|
|
if (m != RunMode.Auto) _state.FanMode = _state.SetFanMode = 1;
|
|
_state.PowerOn = true;
|
|
_seq.NotifyCommandChanged();
|
|
Log($"[Manual] Mode → {m}");
|
|
}
|
|
}
|
|
|
|
// ---- 풍량 0~4 ----
|
|
void FanButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (sender is Button b && b.Tag is string tag && byte.TryParse(tag, out var f))
|
|
{
|
|
_state.FanMode = _state.SetFanMode = f;
|
|
if (f > 0) _state.PowerOn = true;
|
|
_seq.NotifyCommandChanged();
|
|
Log($"[Manual] 풍량 → {f}단{(_state.RunMode == RunMode.Auto ? " (자동모드는 곧 부하점수로 재설정됨)" : "")}");
|
|
}
|
|
}
|
|
|
|
// ---- 자동모드 프리셋 (절전/표준/쾌속) : 자동에서만, 공기질 판정 임계 = 선택 프리셋 ----
|
|
void PresetButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (_state.RunMode != RunMode.Auto) { Log("프리셋은 자동모드에서만 선택할 수 있습니다."); return; }
|
|
if (sender is Button b && b.Tag is string tag && byte.TryParse(tag, out var p))
|
|
{
|
|
_state.HystPreset = p;
|
|
_seq.NotifyCommandChanged();
|
|
Log($"[Manual] 프리셋 → {(p == 0 ? "ECO" : p == 1 ? "NORMAL" : "TURBO")}");
|
|
}
|
|
}
|
|
|
|
// ---- 시나리오모드 (스마트수면/쾌적조리/안심회복) ----
|
|
void SubModeButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (sender is not Button b || b.Tag is not string tag) return;
|
|
switch (tag)
|
|
{
|
|
case "Sleep": _state.ExtRunMode = _state.SmartSleep ? (byte)0 : (byte)4; break;
|
|
case "Recovery": _state.ExtRunMode = _state.RecoveryMode ? (byte)0 : (byte)1; break;
|
|
case "Cook": _state.HoodEnable = !_state.HoodEnable; break;
|
|
}
|
|
if (_state.ExtRunMode != 0 || _state.HoodEnable) _state.PowerOn = true;
|
|
_seq.NotifyCommandChanged();
|
|
Log($"[Manual] 시나리오모드 → {_state.SubModeText}");
|
|
UpdateTopState();
|
|
}
|
|
|
|
// ---- 풍량 VSP 설정 팝업 ----
|
|
VspWindow? _vspWin;
|
|
void OpenVsp_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (_vspWin == null)
|
|
{
|
|
_vspWin = new VspWindow(_state) { Owner = this };
|
|
_vspWin.Applied += Log;
|
|
_vspWin.Closed += (_, _) => _vspWin = null;
|
|
_vspWin.Show();
|
|
}
|
|
else _vspWin.Activate();
|
|
}
|
|
|
|
// ---- 공기질 센서 히스테리시스 팝업 ----
|
|
HystWindow? _hystWin;
|
|
void OpenHyst_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (_hystWin == null)
|
|
{
|
|
_hystWin = new HystWindow(_state) { Owner = this };
|
|
_hystWin.Applied += Log;
|
|
_hystWin.Closed += (_, _) => _hystWin = null;
|
|
_hystWin.Show();
|
|
}
|
|
else _hystWin.Activate();
|
|
}
|
|
|
|
// ---- (꺼짐)예약 0~8시간 ----
|
|
bool _suppressReserveCombo; // 상태→콤보 동기화 중 ReserveCombo_Changed 재진입 차단
|
|
void ReserveCombo_Changed(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (_suppressReserveCombo) return;
|
|
if (ReserveCombo.SelectedIndex < 0) return;
|
|
int hours = ReserveCombo.SelectedIndex; // 0=해제, 1~8시간
|
|
_state.ReserveHours = hours;
|
|
_state.ReserveRemainSec = hours * 3600;
|
|
Log(hours == 0 ? "[Manual] 예약 해제" : $"[Manual] {hours}시간 후 꺼짐 예약");
|
|
}
|
|
|
|
// ---- 에러 카드 토글 ----
|
|
void ErrorCard_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
|
{
|
|
if (sender is not Border b || b.Tag is not string tag) return;
|
|
switch (tag)
|
|
{
|
|
case "E02": _state.E02_TempSensor = !_state.E02_TempSensor; break;
|
|
case "E09": _state.E09_SaFan = !_state.E09_SaFan; break;
|
|
case "E10": _state.E10_EaFan = !_state.E10_EaFan; break;
|
|
case "COLD": _state.COLD_Protect = !_state.COLD_Protect; break;
|
|
case "E07": _state.E07_InternalComm = !_state.E07_InternalComm; break;
|
|
case "FCLEAN": _state.FilterClean = !_state.FilterClean; break;
|
|
case "FCHANGE": _state.FilterChange = !_state.FilterChange; break;
|
|
}
|
|
Log($"[Manual] ErrorCode → 0x{_state.ErrorCode:X2}");
|
|
}
|
|
|
|
// ---- UI 갱신 ----
|
|
void UpdateTopState()
|
|
{
|
|
// 전원 카드 강조
|
|
SetPowerCard(PowerOnCard, _state.PowerOn, AccentGreen);
|
|
SetPowerCard(PowerOffCard, !_state.PowerOn, AccentRed);
|
|
|
|
// 운전 모드 버튼 강조
|
|
foreach (var child in ModePanel.Children)
|
|
{
|
|
if (child is Button btn && btn.Tag is string tag)
|
|
{
|
|
bool active = tag == _state.RunMode.ToString();
|
|
btn.Background = active ? AccentCyan : CardBg;
|
|
btn.Foreground = active ? Brushes.Black : TextPrimary;
|
|
}
|
|
}
|
|
|
|
// 풍량 버튼 강조
|
|
foreach (var child in FanPanel.Children)
|
|
{
|
|
if (child is Button btn && btn.Tag is string tag)
|
|
{
|
|
bool active = tag == _state.FanMode.ToString();
|
|
btn.Background = active ? AccentBlue : CardBg;
|
|
btn.Foreground = active ? Brushes.Black : TextPrimary;
|
|
}
|
|
}
|
|
|
|
// 자동모드 프리셋 버튼 : 자동에서만 활성, 활성 프리셋 강조 (기본 표준=NORMAL)
|
|
bool presetEnabled = _state.RunMode == RunMode.Auto;
|
|
foreach (var child in PresetPanel.Children)
|
|
{
|
|
if (child is Button btn && btn.Tag is string tag)
|
|
{
|
|
bool active = presetEnabled && tag == _state.HystPreset.ToString();
|
|
btn.IsEnabled = presetEnabled;
|
|
btn.Background = active ? AccentBlue : CardBg;
|
|
btn.Foreground = active ? Brushes.Black : TextPrimary;
|
|
}
|
|
}
|
|
|
|
// 시나리오모드 버튼 강조
|
|
foreach (var child in SubModePanel.Children)
|
|
{
|
|
if (child is Button btn && btn.Tag is string tag)
|
|
{
|
|
bool active = tag switch { "Sleep" => _state.SmartSleep, "Cook" => _state.CookingMode, "Recovery" => _state.RecoveryMode, _ => false };
|
|
btn.Background = active ? AccentOrange : CardBg;
|
|
btn.Foreground = active ? Brushes.Black : TextPrimary;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static void SetPowerCard(Border card, bool active, Brush accent)
|
|
{
|
|
if (card.Child is TextBlock tb)
|
|
{
|
|
tb.Foreground = active ? Brushes.White : TextSecondary;
|
|
}
|
|
card.Background = active ? accent : CardBg;
|
|
card.BorderBrush = active ? accent : BorderColor;
|
|
}
|
|
|
|
void UpdateErrorIndicators()
|
|
{
|
|
UpdateErrLed(ErrCard_E02, ErrLed_E02, _state.E02_TempSensor, AccentRed);
|
|
UpdateErrLed(ErrCard_E09, ErrLed_E09, _state.E09_SaFan, AccentRed);
|
|
UpdateErrLed(ErrCard_E10, ErrLed_E10, _state.E10_EaFan, AccentRed);
|
|
UpdateErrLed(ErrCard_COLD, ErrLed_COLD, _state.COLD_Protect, AccentBlue);
|
|
UpdateErrLed(ErrCard_E07, ErrLed_E07, _state.E07_InternalComm, AccentOrange);
|
|
UpdateErrLed(ErrCard_FCLEAN, ErrLed_FCLEAN, _state.FilterClean, AccentYellow);
|
|
UpdateErrLed(ErrCard_FCHANGE, ErrLed_FCHANGE, _state.FilterChange, AccentYellow);
|
|
ErrorCodeHex.Text = $"ErrorCode = 0x{_state.ErrorCode:X2}";
|
|
}
|
|
|
|
static void UpdateErrLed(Border card, System.Windows.Shapes.Ellipse led, bool on, Brush onColor)
|
|
{
|
|
led.Fill = on ? onColor : LedOff;
|
|
card.BorderBrush = on ? onColor : BorderColor;
|
|
card.BorderThickness = new Thickness(on ? 2 : 1);
|
|
}
|
|
|
|
void UpdateRealtime()
|
|
{
|
|
ReserveText.Text = _state.ReserveText;
|
|
// 대시보드 등에서 CTRL_RESERVE 로 설정된 예약을 콤보에 반영(수신 명령도 ERVSim 에서 확인 가능)
|
|
if (ReserveCombo.SelectedIndex != _state.ReserveHours && _state.ReserveHours >= 0 && _state.ReserveHours <= 8)
|
|
{
|
|
_suppressReserveCombo = true;
|
|
ReserveCombo.SelectedIndex = _state.ReserveHours;
|
|
_suppressReserveCombo = false;
|
|
}
|
|
AutoStateRun.Text = _state.AutoStateText;
|
|
|
|
// 후드연동 버튼 — 쾌적조리(HoodEnable) ON 시 강조. 단, 통신중 후드 에러는 빨강+에러명 우선.
|
|
bool hoodOnline = (DateTime.UtcNow - _hood.LastRxUtc) < TimeSpan.FromSeconds(2);
|
|
if (hoodOnline && _state.HoodError != 0)
|
|
{
|
|
HoodLinkBtn.Background = AccentRed;
|
|
HoodLinkBtn.Foreground = Brushes.Black;
|
|
HoodLinkBtn.Content = _state.HoodError == 1 ? "후드연동 FAN에러" : "후드연동 기타에러";
|
|
}
|
|
else
|
|
{
|
|
HoodLinkBtn.Background = _state.HoodEnable ? AccentOrange : CardBg;
|
|
HoodLinkBtn.Foreground = _state.HoodEnable ? Brushes.Black : TextPrimary;
|
|
HoodLinkBtn.Content = "후드연동";
|
|
}
|
|
|
|
// 후드 통신 상태 (HoodSimulator 폴 응답 생존) — 후드연동 버튼 옆 표시
|
|
HoodCommLed.Fill = hoodOnline ? AccentGreen : AccentRed;
|
|
HoodCommText.Text = hoodOnline ? "후드 통신 중" : "후드 통신 안됨";
|
|
HoodCommText.Foreground = hoodOnline ? AccentGreen : TextSecondary;
|
|
}
|
|
|
|
// (꺼짐)예약 1초 카운트다운 — 0 도달 시 전원 OFF
|
|
void ReserveTick()
|
|
{
|
|
if (_state.ReserveRemainSec <= 0) return;
|
|
_state.ReserveRemainSec--;
|
|
if (_state.ReserveRemainSec == 0)
|
|
{
|
|
_state.ReserveHours = 0;
|
|
_state.PowerOn = false;
|
|
_state.FanMode = _state.SetFanMode = 0;
|
|
_seq.NotifyCommandChanged();
|
|
if (ReserveCombo.SelectedIndex != 0) ReserveCombo.SelectedIndex = 0;
|
|
Log("[예약] 예약시간 종료 → 전원 OFF");
|
|
}
|
|
}
|
|
|
|
// ---- 로그 (DiffuserSimulator 와 동일 : 읽기전용 TextBox, 텍스트 드래그 선택/복사 가능) ----
|
|
void Log(string msg)
|
|
{
|
|
var line = $"[{DateTime.Now:HH:mm:ss.fff}] {msg}";
|
|
Dispatcher.BeginInvoke(() =>
|
|
{
|
|
LogList.AppendText(line + Environment.NewLine);
|
|
if (LogList.LineCount > 500)
|
|
{
|
|
var lines = LogList.Text.Split(Environment.NewLine);
|
|
LogList.Text = string.Join(Environment.NewLine, lines[^300..]);
|
|
}
|
|
LogList.ScrollToEnd();
|
|
});
|
|
}
|
|
|
|
void ClearLog_Click(object sender, RoutedEventArgs e) => LogList.Clear();
|
|
|
|
void SaveLog_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var dlg = new Microsoft.Win32.SaveFileDialog
|
|
{
|
|
Filter = "텍스트 파일 (*.txt)|*.txt",
|
|
FileName = $"ErvSimLog_{DateTime.Now:yyyyMMdd_HHmmss}.txt"
|
|
};
|
|
if (dlg.ShowDialog() == true)
|
|
{
|
|
try
|
|
{
|
|
string h = $"========================================\r\n ERV 시뮬레이터 통신 로그\r\n 저장 일시: {DateTime.Now:yyyy-MM-dd HH:mm:ss}\r\n========================================\r\n\r\n";
|
|
System.IO.File.WriteAllText(dlg.FileName, h + LogList.Text);
|
|
MessageBox.Show($"저장 완료: {dlg.FileName}");
|
|
}
|
|
catch (Exception ex) { MessageBox.Show($"저장 실패: {ex.Message}"); }
|
|
}
|
|
}
|
|
|
|
protected override void OnClosed(EventArgs e)
|
|
{
|
|
_roomConCh.Dispose();
|
|
_homeNetCh.Dispose();
|
|
_diffuserCh.Dispose();
|
|
_hoodCh.Dispose();
|
|
base.OnClosed(e);
|
|
}
|
|
}
|
|
|
|
// ---- Converters ----
|
|
|
|
public class BoolToOpenCloseConverter : IValueConverter
|
|
{
|
|
public object Convert(object value, Type t, object p, CultureInfo c) =>
|
|
(value is bool b && b) ? "열림" : "닫힘";
|
|
public object ConvertBack(object v, Type t, object p, CultureInfo c) => DependencyProperty.UnsetValue;
|
|
}
|
|
|
|
public class BoolToBrushConverter : IValueConverter
|
|
{
|
|
static readonly Brush Open = (Brush)new BrushConverter().ConvertFromString("#9ECE6A")!;
|
|
static readonly Brush Close = (Brush)new BrushConverter().ConvertFromString("#F7768E")!;
|
|
public object Convert(object value, Type t, object p, CultureInfo c) =>
|
|
(value is bool b && b) ? Open : Close;
|
|
public object ConvertBack(object v, Type t, object p, CultureInfo c) => DependencyProperty.UnsetValue;
|
|
}
|
|
|
|
public class ColorTagToBrushConverter : IValueConverter
|
|
{
|
|
public object Convert(object value, Type t, object p, CultureInfo c) =>
|
|
value switch
|
|
{
|
|
"GREEN" => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#9ECE6A")!),
|
|
"YELLOW" => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#E0AF68")!),
|
|
"RED" => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F7768E")!),
|
|
"BLACK" => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#1E1E2E")!),
|
|
"BLUE" => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#7AA2F7")!),
|
|
"WHITE" => new SolidColorBrush(Colors.WhiteSmoke),
|
|
_ => new SolidColorBrush(Colors.Gray),
|
|
};
|
|
public object ConvertBack(object v, Type t, object p, CultureInfo c) => DependencyProperty.UnsetValue;
|
|
}
|
|
}
|