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,102 @@
|
||||
<Application x:Class="HoodSimulator.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
<Color x:Key="PrimaryBg">#1E1E2E</Color>
|
||||
<Color x:Key="SecondaryBg">#2B2B3D</Color>
|
||||
<Color x:Key="CardBg">#313147</Color>
|
||||
<Color x:Key="AccentBlue">#7AA2F7</Color>
|
||||
<Color x:Key="AccentGreen">#9ECE6A</Color>
|
||||
<Color x:Key="AccentRed">#F7768E</Color>
|
||||
<Color x:Key="AccentYellow">#E0AF68</Color>
|
||||
<Color x:Key="AccentCyan">#7DCFFF</Color>
|
||||
<Color x:Key="AccentPurple">#BB9AF7</Color>
|
||||
<Color x:Key="TextPrimary">#C0CAF5</Color>
|
||||
<Color x:Key="TextSecondary">#565F89</Color>
|
||||
<Color x:Key="BorderColor">#3B3B55</Color>
|
||||
|
||||
<SolidColorBrush x:Key="PrimaryBgBrush" Color="{StaticResource PrimaryBg}"/>
|
||||
<SolidColorBrush x:Key="SecondaryBgBrush" Color="{StaticResource SecondaryBg}"/>
|
||||
<SolidColorBrush x:Key="CardBgBrush" Color="{StaticResource CardBg}"/>
|
||||
<SolidColorBrush x:Key="AccentBlueBrush" Color="{StaticResource AccentBlue}"/>
|
||||
<SolidColorBrush x:Key="AccentGreenBrush" Color="{StaticResource AccentGreen}"/>
|
||||
<SolidColorBrush x:Key="AccentRedBrush" Color="{StaticResource AccentRed}"/>
|
||||
<SolidColorBrush x:Key="AccentYellowBrush" Color="{StaticResource AccentYellow}"/>
|
||||
<SolidColorBrush x:Key="AccentCyanBrush" Color="{StaticResource AccentCyan}"/>
|
||||
<SolidColorBrush x:Key="AccentPurpleBrush" Color="{StaticResource AccentPurple}"/>
|
||||
<SolidColorBrush x:Key="TextPrimaryBrush" Color="{StaticResource TextPrimary}"/>
|
||||
<SolidColorBrush x:Key="TextSecondaryBrush" Color="{StaticResource TextSecondary}"/>
|
||||
<SolidColorBrush x:Key="BorderBrush" Color="{StaticResource BorderColor}"/>
|
||||
|
||||
<Style x:Key="ModernButton" TargetType="Button">
|
||||
<Setter Property="Background" Value="{StaticResource AccentBlueBrush}"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Padding" Value="18,8"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="border" Background="{TemplateBinding Background}"
|
||||
CornerRadius="6" Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.85"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.4"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ToggleSwitch" TargetType="ToggleButton">
|
||||
<Setter Property="Width" Value="56"/>
|
||||
<Setter Property="Height" Value="28"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Grid>
|
||||
<Border x:Name="track" CornerRadius="14" Background="#3B3B55" Width="56" Height="28"/>
|
||||
<Border x:Name="thumb" CornerRadius="11" Background="#565F89" Width="22" Height="22"
|
||||
HorizontalAlignment="Left" Margin="3,0,0,0"/>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="track" Property="Background" Value="{StaticResource AccentGreenBrush}"/>
|
||||
<Setter TargetName="thumb" Property="Background" Value="White"/>
|
||||
<Setter TargetName="thumb" Property="HorizontalAlignment" Value="Right"/>
|
||||
<Setter TargetName="thumb" Property="Margin" Value="0,0,3,0"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ModernComboBox" TargetType="ComboBox">
|
||||
<Setter Property="Background" Value="{StaticResource CardBgBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="8,5"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="ItemContainerStyle">
|
||||
<Setter.Value>
|
||||
<Style TargetType="ComboBoxItem">
|
||||
<Setter Property="Foreground" Value="Black"/>
|
||||
</Style>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace HoodSimulator
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HoodSimulator
|
||||
{
|
||||
// 후드메인(Slave) 시뮬레이터 프로토콜
|
||||
// 규격 : Protocol/HOOD/주신전자_protocol_hood_전열교환기_Rev1.3_20241125.xlsx
|
||||
// - 9바이트 고정, 115200 8N1, 폴링주기 100~500ms, 응답 50ms 이내
|
||||
// - CS = Preamble~CS직전(byte0~7) 전체 XOR
|
||||
// Master(전열교환기) → Slave(후드) : AA 21 ID MODE FAN 연동EN 연동운전중 ERROR CS
|
||||
// Slave(후드) → Master(전열교환기) : AA 11 ID FANSTATUS LIGHTSTATUS 00 연동CMD ERROR CS
|
||||
// 시뮬레이터는 Slave 역할 — 마스터 폴 수신 시 현재 후드 상태로 응답.
|
||||
public class HoodProtocol : IDisposable
|
||||
{
|
||||
public const byte PREAMBLE = 0xAA;
|
||||
public const byte MS_MASTER = 0x21;
|
||||
public const byte MS_SLAVE = 0x11;
|
||||
public const byte HOOD_ID = 0x01;
|
||||
public const int FRAME_LEN = 9;
|
||||
|
||||
SerialPort? _port;
|
||||
CancellationTokenSource? _cts;
|
||||
readonly object _lock = new();
|
||||
bool _disposed;
|
||||
bool _responding;
|
||||
|
||||
// ---- 후드 상태 (UI 제어) ----
|
||||
public bool PowerOn; // 전원 on/off
|
||||
public byte FanStage; // 풍량 0(꺼짐)~5
|
||||
public bool Light; // 조명 on/off
|
||||
public byte ErrorCode; // ERROR : 0 정상 / 1 FAN 에러 / 2 기타 에러
|
||||
|
||||
public event Action<byte, byte, byte, byte>? MasterPacketReceived; // mode, fan, en, run
|
||||
public event Action<byte[]>? ResponseSent; // 송신한 9바이트 응답
|
||||
public event Action<string>? LogMessage;
|
||||
public event Action<bool>? ConnectionChanged;
|
||||
|
||||
public bool IsConnected => _port?.IsOpen == true;
|
||||
public bool IsResponding => _responding;
|
||||
|
||||
public static byte Xor(byte[] d, int start, int len)
|
||||
{
|
||||
byte x = 0;
|
||||
for (int i = 0; i < len; i++) x ^= d[start + i];
|
||||
return x;
|
||||
}
|
||||
|
||||
public string[] GetAvailablePorts() => SerialPort.GetPortNames();
|
||||
|
||||
public bool Connect(string portName)
|
||||
{
|
||||
try
|
||||
{
|
||||
Disconnect();
|
||||
_port = new SerialPort(portName)
|
||||
{
|
||||
BaudRate = 115200, DataBits = 8,
|
||||
StopBits = StopBits.One, Parity = Parity.None,
|
||||
ReadTimeout = 100, WriteTimeout = 500
|
||||
};
|
||||
_port.Open();
|
||||
Log($"[연결] {portName} (115200, 8N1)");
|
||||
ConnectionChanged?.Invoke(true);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"[오류] 연결 실패: {ex.Message}");
|
||||
ConnectionChanged?.Invoke(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
StopResponding();
|
||||
try { if (_port?.IsOpen == true) { _port.Close(); Log("[연결 해제]"); } } catch { }
|
||||
_port?.Dispose();
|
||||
_port = null;
|
||||
ConnectionChanged?.Invoke(false);
|
||||
}
|
||||
|
||||
public void StartResponding()
|
||||
{
|
||||
StopResponding();
|
||||
_responding = true;
|
||||
_cts = new CancellationTokenSource();
|
||||
var token = _cts.Token;
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_port?.IsOpen != true) { Thread.Sleep(50); continue; }
|
||||
if (_port.BytesToRead < 1) { Thread.Sleep(3); continue; }
|
||||
|
||||
byte b = (byte)_port.ReadByte();
|
||||
if (b != PREAMBLE) continue;
|
||||
|
||||
byte[] rx = new byte[FRAME_LEN];
|
||||
rx[0] = PREAMBLE;
|
||||
int total = 1, remain = FRAME_LEN - 1, retries = 100;
|
||||
while (remain > 0 && retries-- > 0)
|
||||
{
|
||||
if (_port.BytesToRead > 0)
|
||||
{ int r = _port.Read(rx, total, remain); total += r; remain -= r; }
|
||||
else Thread.Sleep(2);
|
||||
}
|
||||
if (total < FRAME_LEN) continue;
|
||||
|
||||
// 마스터 프레임만 처리
|
||||
if (rx[1] != MS_MASTER) continue;
|
||||
if (rx[2] != HOOD_ID) continue;
|
||||
byte cs = Xor(rx, 0, 8);
|
||||
if (cs != rx[8]) { Log($"[CS오류] 수신:0x{rx[8]:X2} 계산:0x{cs:X2} {BitConverter.ToString(rx)}"); continue; }
|
||||
|
||||
byte mode = rx[3], fan = rx[4], en = rx[5], run = rx[6];
|
||||
Log($"[RX] {BitConverter.ToString(rx)} MODE={mode} FAN={fan} 연동EN={en} 연동운전={run}");
|
||||
MasterPacketReceived?.Invoke(mode, fan, en, run);
|
||||
|
||||
// 응답 전송 (현재 후드 상태)
|
||||
byte[] tx = BuildResponse();
|
||||
lock (_lock) { _port?.Write(tx, 0, tx.Length); }
|
||||
Log($"[TX 응답] {BitConverter.ToString(tx)}");
|
||||
ResponseSent?.Invoke(tx);
|
||||
}
|
||||
catch (TimeoutException) { }
|
||||
catch (OperationCanceledException) { break; }
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!token.IsCancellationRequested) Log($"[오류] {ex.Message}");
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
}, token);
|
||||
Log("[통신 시작] 마스터 응답 모드");
|
||||
}
|
||||
|
||||
public void StopResponding()
|
||||
{
|
||||
_responding = false;
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
_cts = null;
|
||||
}
|
||||
|
||||
public byte[] BuildResponse()
|
||||
{
|
||||
byte fanStatus = PowerOn ? FanStage : (byte)0; // 후드 FAN STATUS 0~5
|
||||
byte lightStatus = (byte)((PowerOn && Light) ? 1 : 0); // 후드 LIGHT STATUS
|
||||
byte cmd = (byte)(PowerOn ? 1 : 0); // 연동 CMD : 0 꺼짐 / 1 켜짐
|
||||
|
||||
byte[] p = new byte[FRAME_LEN];
|
||||
p[0] = PREAMBLE;
|
||||
p[1] = MS_SLAVE;
|
||||
p[2] = HOOD_ID;
|
||||
p[3] = fanStatus;
|
||||
p[4] = lightStatus;
|
||||
p[5] = 0x00;
|
||||
p[6] = cmd;
|
||||
p[7] = ErrorCode; // ERROR : 0 정상 / 1 FAN / 2 기타
|
||||
p[8] = Xor(p, 0, 8); // CS
|
||||
return p;
|
||||
}
|
||||
|
||||
void Log(string msg) => LogMessage?.Invoke($"[{DateTime.Now:HH:mm:ss.fff}] {msg}");
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
Disconnect();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>HoodSimulator</RootNamespace>
|
||||
<AssemblyName>HoodSimulator</AssemblyName>
|
||||
<StartupObject>HoodSimulator.App</StartupObject>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.IO.Ports" Version="10.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,163 @@
|
||||
<Window x:Class="HoodSimulator.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="후드 시뮬레이터 - Hood Simulator"
|
||||
Width="500" Height="820"
|
||||
MinWidth="460" MinHeight="700"
|
||||
Background="{StaticResource PrimaryBgBrush}"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
|
||||
<Grid Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/> <!-- 0: 연결 -->
|
||||
<RowDefinition Height="Auto"/> <!-- 1: 후드 제어 -->
|
||||
<RowDefinition Height="Auto"/> <!-- 2: 통신 상태 -->
|
||||
<RowDefinition Height="*"/> <!-- 3: 로그 -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Row 0: 연결 설정 (2줄) -->
|
||||
<Border Grid.Row="0" Background="{StaticResource SecondaryBgBrush}"
|
||||
CornerRadius="10" Padding="14,10" Margin="0,0,0,8">
|
||||
<StackPanel>
|
||||
<DockPanel Margin="0,0,0,8">
|
||||
<TextBlock Text="후드 시뮬레이터" FontSize="17" FontWeight="Bold"
|
||||
Foreground="{StaticResource AccentCyanBrush}" VerticalAlignment="Center"/>
|
||||
<StackPanel DockPanel.Dock="Right" VerticalAlignment="Center" HorizontalAlignment="Right">
|
||||
<TextBlock Text="만든이 : 전경선" Foreground="{StaticResource TextSecondaryBrush}" FontSize="10" TextAlignment="Right"/>
|
||||
<TextBlock Text="수정일 : 2026.06.07" Foreground="{StaticResource TextSecondaryBrush}" FontSize="10" TextAlignment="Right"/>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ComboBox x:Name="cmbPort" Width="92" Style="{StaticResource ModernComboBox}"
|
||||
VerticalAlignment="Center" Margin="0,0,4,0"/>
|
||||
<Button Content="⟳" Width="28" Height="28" FontSize="13"
|
||||
Style="{StaticResource ModernButton}" Click="RefreshPorts_Click"
|
||||
Background="{StaticResource CardBgBrush}" Margin="0,0,8,0" Padding="0"/>
|
||||
<Ellipse x:Name="statusLed" Width="10" Height="10" Fill="#F7768E" Margin="0,0,5,0" VerticalAlignment="Center"/>
|
||||
<TextBlock x:Name="txtStatus" Text="미연결" Foreground="{StaticResource TextSecondaryBrush}"
|
||||
FontSize="12" VerticalAlignment="Center" Margin="0,0,8,0"/>
|
||||
<Button x:Name="btnConnect" Content="연결" Style="{StaticResource ModernButton}"
|
||||
Click="Connect_Click" Margin="0,0,4,0" Padding="12,6"/>
|
||||
<Button x:Name="btnStart" Content="시작" Style="{StaticResource ModernButton}"
|
||||
Background="{StaticResource AccentGreenBrush}" Click="Start_Click"
|
||||
IsEnabled="False" Margin="0,0,4,0" Padding="12,6"/>
|
||||
<Button x:Name="btnStop" Content="중지" Style="{StaticResource ModernButton}"
|
||||
Background="{StaticResource AccentRedBrush}" Click="Stop_Click"
|
||||
IsEnabled="False" Padding="12,6"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Row 1: 후드 제어 -->
|
||||
<Border Grid.Row="1" Background="{StaticResource SecondaryBgBrush}" CornerRadius="10" Padding="16" Margin="0,0,0,8">
|
||||
<StackPanel>
|
||||
<TextBlock Text="후드 제어" FontSize="14" FontWeight="Bold" Foreground="{StaticResource TextPrimaryBrush}" Margin="0,0,0,14"/>
|
||||
|
||||
<!-- 전원 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,16">
|
||||
<TextBlock Text="전원" Width="56" FontSize="13" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}" VerticalAlignment="Center"/>
|
||||
<ToggleButton x:Name="tglPower" Style="{StaticResource ToggleSwitch}" Click="Power_Click" VerticalAlignment="Center"/>
|
||||
<TextBlock x:Name="txtPower" Text="OFF" FontSize="13" FontWeight="Bold" Margin="12,0,0,0"
|
||||
Foreground="{StaticResource TextSecondaryBrush}" VerticalAlignment="Center"/>
|
||||
<TextBlock x:Name="txtMakeup" Text="" FontSize="13" FontWeight="Bold" Margin="10,0,0,0"
|
||||
Foreground="{StaticResource AccentCyanBrush}" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 풍량 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,16">
|
||||
<TextBlock Text="풍량" Width="56" FontSize="13" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}" VerticalAlignment="Center"/>
|
||||
<StackPanel Orientation="Horizontal" x:Name="FanPanel">
|
||||
<Button Content="0" Tag="0" Click="Fan_Click" Style="{StaticResource ModernButton}" Margin="0,0,4,0" MinWidth="40" Padding="0,7" Background="{StaticResource CardBgBrush}" ToolTip="꺼짐"/>
|
||||
<Button Content="1" Tag="1" Click="Fan_Click" Style="{StaticResource ModernButton}" Margin="0,0,4,0" MinWidth="40" Padding="0,7" Background="{StaticResource CardBgBrush}"/>
|
||||
<Button Content="2" Tag="2" Click="Fan_Click" Style="{StaticResource ModernButton}" Margin="0,0,4,0" MinWidth="40" Padding="0,7" Background="{StaticResource CardBgBrush}"/>
|
||||
<Button Content="3" Tag="3" Click="Fan_Click" Style="{StaticResource ModernButton}" Margin="0,0,4,0" MinWidth="40" Padding="0,7" Background="{StaticResource CardBgBrush}"/>
|
||||
<Button Content="4" Tag="4" Click="Fan_Click" Style="{StaticResource ModernButton}" Margin="0,0,4,0" MinWidth="40" Padding="0,7" Background="{StaticResource CardBgBrush}"/>
|
||||
<Button Content="5" Tag="5" Click="Fan_Click" Style="{StaticResource ModernButton}" Margin="0,0,0,0" MinWidth="40" Padding="0,7" Background="{StaticResource CardBgBrush}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 조명 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,16">
|
||||
<TextBlock Text="조명" Width="56" FontSize="13" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}" VerticalAlignment="Center"/>
|
||||
<ToggleButton x:Name="tglLight" Style="{StaticResource ToggleSwitch}" Click="Light_Click" VerticalAlignment="Center"/>
|
||||
<TextBlock x:Name="txtLight" Text="OFF" FontSize="13" FontWeight="Bold" Margin="12,0,0,0"
|
||||
Foreground="{StaticResource TextSecondaryBrush}" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 에러코드 (체크 선택하여 발생) -->
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="에러코드" Width="56" FontSize="13" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}" VerticalAlignment="Center"/>
|
||||
<Border x:Name="ErrCard_Fan" Tag="1" MouseDown="ErrorCard_Click" Cursor="Hand" CornerRadius="6" Padding="8,5" Margin="0,0,6,0"
|
||||
Background="{StaticResource CardBgBrush}" BorderBrush="{StaticResource BorderBrush}" BorderThickness="1" ToolTip="FAN 에러 (ERROR=1)">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Ellipse x:Name="ErrLed_Fan" Width="10" Height="10" Fill="#3B3B55" VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="FAN 에러" FontSize="12" FontWeight="Bold" VerticalAlignment="Center" Foreground="{StaticResource TextPrimaryBrush}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border x:Name="ErrCard_Etc" Tag="2" MouseDown="ErrorCard_Click" Cursor="Hand" CornerRadius="6" Padding="8,5"
|
||||
Background="{StaticResource CardBgBrush}" BorderBrush="{StaticResource BorderBrush}" BorderThickness="1" ToolTip="기타 에러 (ERROR=2)">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Ellipse x:Name="ErrLed_Etc" Width="10" Height="10" Fill="#3B3B55" VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="기타 에러" FontSize="12" FontWeight="Bold" VerticalAlignment="Center" Foreground="{StaticResource TextPrimaryBrush}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Row 2: 통신 상태 -->
|
||||
<Border Grid.Row="2" Background="{StaticResource SecondaryBgBrush}" CornerRadius="10" Padding="16" Margin="0,0,0,8">
|
||||
<StackPanel>
|
||||
<DockPanel Margin="0,0,0,12">
|
||||
<TextBlock Text="통신 상태" FontSize="14" FontWeight="Bold" Foreground="{StaticResource TextPrimaryBrush}"/>
|
||||
<TextBlock x:Name="txtRxCount" DockPanel.Dock="Right" Text="수신: 0" FontSize="12"
|
||||
Foreground="{StaticResource TextSecondaryBrush}" VerticalAlignment="Center"/>
|
||||
</DockPanel>
|
||||
|
||||
<TextBlock Text="◇ 마스터 수신 명령 (전열교환기 → 후드)" FontSize="11" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource AccentCyanBrush}" Margin="0,0,0,6"/>
|
||||
<Border Background="{StaticResource CardBgBrush}" CornerRadius="6" Padding="12,8" Margin="0,0,0,12"
|
||||
BorderBrush="{StaticResource BorderBrush}" BorderThickness="1">
|
||||
<StackPanel>
|
||||
<DockPanel Margin="0,0,0,3"><TextBlock Text="운전모드 (MODE)" FontSize="12" Foreground="{StaticResource TextSecondaryBrush}"/><TextBlock x:Name="txtRxMode" DockPanel.Dock="Right" Text="-" FontSize="12" FontWeight="Bold" Foreground="{StaticResource TextPrimaryBrush}"/></DockPanel>
|
||||
<DockPanel Margin="0,0,0,3"><TextBlock Text="전열교환기 풍량 (FAN)" FontSize="12" Foreground="{StaticResource TextSecondaryBrush}"/><TextBlock x:Name="txtRxFan" DockPanel.Dock="Right" Text="-" FontSize="12" FontWeight="Bold" Foreground="{StaticResource TextPrimaryBrush}"/></DockPanel>
|
||||
<DockPanel Margin="0,0,0,3"><TextBlock Text="연동 Enable" FontSize="12" Foreground="{StaticResource TextSecondaryBrush}"/><TextBlock x:Name="txtRxEn" DockPanel.Dock="Right" Text="-" FontSize="12" FontWeight="Bold" Foreground="{StaticResource TextPrimaryBrush}"/></DockPanel>
|
||||
<DockPanel><TextBlock Text="연동 운전중" FontSize="12" Foreground="{StaticResource TextSecondaryBrush}"/><TextBlock x:Name="txtRxRun" DockPanel.Dock="Right" Text="-" FontSize="12" FontWeight="Bold" Foreground="{StaticResource TextPrimaryBrush}"/></DockPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<TextBlock Text="◇ 후드 응답 송신 (후드 → 전열교환기)" FontSize="11" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource AccentGreenBrush}" Margin="0,0,0,6"/>
|
||||
<Border Background="{StaticResource CardBgBrush}" CornerRadius="6" Padding="12,8"
|
||||
BorderBrush="{StaticResource BorderBrush}" BorderThickness="1">
|
||||
<StackPanel>
|
||||
<DockPanel Margin="0,0,0,3"><TextBlock Text="후드 FAN STATUS" FontSize="12" Foreground="{StaticResource TextSecondaryBrush}"/><TextBlock x:Name="txtTxFan" DockPanel.Dock="Right" Text="-" FontSize="12" FontWeight="Bold" Foreground="{StaticResource AccentGreenBrush}"/></DockPanel>
|
||||
<DockPanel Margin="0,0,0,3"><TextBlock Text="후드 LIGHT STATUS" FontSize="12" Foreground="{StaticResource TextSecondaryBrush}"/><TextBlock x:Name="txtTxLight" DockPanel.Dock="Right" Text="-" FontSize="12" FontWeight="Bold" Foreground="{StaticResource AccentGreenBrush}"/></DockPanel>
|
||||
<DockPanel Margin="0,0,0,3"><TextBlock Text="연동 CMD" FontSize="12" Foreground="{StaticResource TextSecondaryBrush}"/><TextBlock x:Name="txtTxCmd" DockPanel.Dock="Right" Text="-" FontSize="12" FontWeight="Bold" Foreground="{StaticResource AccentGreenBrush}"/></DockPanel>
|
||||
<DockPanel><TextBlock Text="ERROR" FontSize="12" Foreground="{StaticResource TextSecondaryBrush}"/><TextBlock x:Name="txtTxError" DockPanel.Dock="Right" Text="-" FontSize="12" FontWeight="Bold" Foreground="{StaticResource AccentGreenBrush}"/></DockPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Row 3: 통신 로그 -->
|
||||
<Border Grid.Row="3" Background="{StaticResource SecondaryBgBrush}" CornerRadius="10" Padding="12">
|
||||
<DockPanel>
|
||||
<Grid DockPanel.Dock="Top" Margin="0,0,0,5">
|
||||
<TextBlock Text="통신 로그" FontSize="12" FontWeight="Bold" Foreground="{StaticResource TextPrimaryBrush}"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Content="저장" Style="{StaticResource ModernButton}" Background="{StaticResource AccentBlueBrush}"
|
||||
Padding="10,3" FontSize="11" Click="SaveLog_Click" Margin="0,0,6,0"/>
|
||||
<Button Content="지우기" Style="{StaticResource ModernButton}" Background="{StaticResource CardBgBrush}"
|
||||
Padding="10,3" FontSize="11" Click="ClearLog_Click"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<TextBox x:Name="txtLog" IsReadOnly="True" Background="{StaticResource CardBgBrush}"
|
||||
Foreground="{StaticResource TextPrimaryBrush}" BorderBrush="{StaticResource BorderBrush}"
|
||||
BorderThickness="1" FontFamily="Consolas" FontSize="10"
|
||||
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
|
||||
TextWrapping="NoWrap" Padding="6"/>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,300 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace HoodSimulator
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
readonly HoodProtocol _hood = new();
|
||||
int _rxCount;
|
||||
|
||||
// 조리 종료 후 메이크업 유지(잔여 냄새 배출) — 후드측이 담당. 유지중에는 ERV 에 계속 '켜짐' 보고,
|
||||
// 종료 시점에 OFF 전송 → ERV 가 원래 모드/풍량으로 복귀. (사양 260613 9p 3.3)
|
||||
readonly System.Windows.Threading.DispatcherTimer _makeupTimer =
|
||||
new() { Interval = TimeSpan.FromSeconds(1) };
|
||||
const int MakeupHoldSec = 10; // 메이크업 유지 시간 (10초)
|
||||
int _makeupRemainSec;
|
||||
|
||||
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 CardBg = (Brush)new BrushConverter().ConvertFromString("#313147")!;
|
||||
static readonly Brush TextPrimary = (Brush)new BrushConverter().ConvertFromString("#C0CAF5")!;
|
||||
static readonly Brush TextSecondary = (Brush)new BrushConverter().ConvertFromString("#565F89")!;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
_hood.LogMessage += OnLog;
|
||||
_hood.ConnectionChanged += OnConnectionChanged;
|
||||
_hood.MasterPacketReceived += OnMasterPacket;
|
||||
_hood.ResponseSent += OnResponseSent;
|
||||
_makeupTimer.Tick += MakeupTick;
|
||||
|
||||
RefreshPorts();
|
||||
UpdateFanButtons();
|
||||
Closed += (_, _) => { _makeupTimer.Stop(); _hood.Dispose(); };
|
||||
}
|
||||
|
||||
// ========== 연결 ==========
|
||||
void RefreshPorts()
|
||||
{
|
||||
cmbPort.Items.Clear();
|
||||
foreach (var p in _hood.GetAvailablePorts()) cmbPort.Items.Add(p);
|
||||
if (cmbPort.Items.Count > 0) cmbPort.SelectedIndex = 0;
|
||||
}
|
||||
void RefreshPorts_Click(object s, RoutedEventArgs e) => RefreshPorts();
|
||||
|
||||
void Connect_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
if (_hood.IsConnected)
|
||||
{
|
||||
_hood.Disconnect();
|
||||
btnConnect.Content = "연결";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cmbPort.SelectedItem == null) { MessageBox.Show("COM 포트를 선택하세요."); return; }
|
||||
if (_hood.Connect(cmbPort.SelectedItem.ToString()!)) btnConnect.Content = "연결 해제";
|
||||
}
|
||||
}
|
||||
|
||||
void Start_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
if (!_hood.IsConnected) return;
|
||||
_hood.StartResponding();
|
||||
btnStart.IsEnabled = false;
|
||||
btnStop.IsEnabled = true;
|
||||
}
|
||||
|
||||
void Stop_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
_hood.StopResponding();
|
||||
btnStart.IsEnabled = true;
|
||||
btnStop.IsEnabled = false;
|
||||
}
|
||||
|
||||
// ========== 후드 제어 ==========
|
||||
void Power_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
if (tglPower.IsChecked == true)
|
||||
{
|
||||
// 켜기 : 진행중인 메이크업 유지 취소 후 즉시 ON (풍량 1)
|
||||
StopMakeupHold();
|
||||
_hood.PowerOn = true;
|
||||
_hood.FanStage = 1;
|
||||
txtPower.Text = "ON";
|
||||
txtPower.Foreground = AccentGreen;
|
||||
UpdateFanButtons();
|
||||
OnLog("[제어] 전원 → ON (풍량 1)");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 끄기 : OFF 표시 + 옆에 메이크업 유지(1분) 카운트다운 시작. 그동안 ERV엔 계속 '켜짐' 보고.
|
||||
// 유지 종료 시 후드 OFF 전송 → ERV 가 원래 모드/풍량으로 복귀.
|
||||
txtPower.Text = "OFF";
|
||||
txtPower.Foreground = TextSecondary;
|
||||
if (_hood.PowerOn && _makeupRemainSec == 0)
|
||||
{
|
||||
_makeupRemainSec = MakeupHoldSec;
|
||||
_makeupTimer.Start();
|
||||
txtMakeup.Text = $"메이크업 {_makeupRemainSec}s";
|
||||
OnLog($"[제어] 전원 OFF 요청 → 메이크업 에어 {MakeupHoldSec}s 유지 (ERV엔 계속 켜짐 보고)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 메이크업 유지 카운트다운 (1초). 0이 되면 실제 OFF 전송.
|
||||
void MakeupTick(object? s, EventArgs e)
|
||||
{
|
||||
_makeupRemainSec--;
|
||||
if (_makeupRemainSec > 0)
|
||||
{
|
||||
txtMakeup.Text = $"메이크업 {_makeupRemainSec}s";
|
||||
}
|
||||
else
|
||||
{
|
||||
StopMakeupHold();
|
||||
_hood.PowerOn = false;
|
||||
_hood.FanStage = 0;
|
||||
UpdateFanButtons();
|
||||
OnLog("[제어] 메이크업 유지 종료 → 후드 OFF 전송 (ERV 원래 모드/풍량 복귀)");
|
||||
}
|
||||
}
|
||||
|
||||
void StopMakeupHold()
|
||||
{
|
||||
_makeupTimer.Stop();
|
||||
_makeupRemainSec = 0;
|
||||
txtMakeup.Text = "";
|
||||
}
|
||||
|
||||
void Fan_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
if (s is Button b && b.Tag is string tag && byte.TryParse(tag, out var f))
|
||||
{
|
||||
_hood.FanStage = f;
|
||||
UpdateFanButtons();
|
||||
OnLog($"[제어] 풍량 → {f}{(f == 0 ? " (꺼짐)" : "단")}");
|
||||
}
|
||||
}
|
||||
|
||||
void Light_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
_hood.Light = tglLight.IsChecked == true;
|
||||
txtLight.Text = _hood.Light ? "ON" : "OFF";
|
||||
txtLight.Foreground = _hood.Light ? AccentGreen : TextSecondary;
|
||||
OnLog($"[제어] 조명 → {(_hood.Light ? "ON" : "OFF")}");
|
||||
}
|
||||
|
||||
// 에러코드 토글 (FAN 에러=1 / 기타 에러=2). 둘 다 켜지면 FAN(1) 우선 송신.
|
||||
bool _errFan, _errEtc;
|
||||
void ErrorCard_Click(object s, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
if (s is not Border b || b.Tag is not string tag) return;
|
||||
if (tag == "1") _errFan = !_errFan;
|
||||
else if (tag == "2") _errEtc = !_errEtc;
|
||||
|
||||
_hood.ErrorCode = _errFan ? (byte)1 : _errEtc ? (byte)2 : (byte)0;
|
||||
UpdateErrorCards();
|
||||
OnLog($"[제어] 에러코드 → {ErrorName(_hood.ErrorCode)} (ERROR={_hood.ErrorCode})");
|
||||
|
||||
// 에러 발생 시 전원 OFF / 풍량 0 / 조명 OFF (다음 응답에 반영되어 전송)
|
||||
if (_hood.ErrorCode != 0)
|
||||
{
|
||||
StopMakeupHold(); // 진행중인 메이크업 유지 즉시 취소
|
||||
_hood.PowerOn = false;
|
||||
_hood.FanStage = 0;
|
||||
_hood.Light = false;
|
||||
|
||||
tglPower.IsChecked = false;
|
||||
txtPower.Text = "OFF"; txtPower.Foreground = TextSecondary;
|
||||
tglLight.IsChecked = false;
|
||||
txtLight.Text = "OFF"; txtLight.Foreground = TextSecondary;
|
||||
UpdateFanButtons();
|
||||
OnLog("[제어] 에러 발생 → 전원 OFF / 풍량 0 / 조명 OFF");
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateErrorCards()
|
||||
{
|
||||
UpdateErrLed(ErrCard_Fan, ErrLed_Fan, _errFan);
|
||||
UpdateErrLed(ErrCard_Etc, ErrLed_Etc, _errEtc);
|
||||
}
|
||||
|
||||
static void UpdateErrLed(Border card, System.Windows.Shapes.Ellipse led, bool on)
|
||||
{
|
||||
led.Fill = on ? AccentRed : (Brush)new BrushConverter().ConvertFromString("#3B3B55")!;
|
||||
card.BorderBrush = on ? AccentRed : (Brush)new BrushConverter().ConvertFromString("#3B3B55")!;
|
||||
card.BorderThickness = new Thickness(on ? 2 : 1);
|
||||
}
|
||||
|
||||
static string ErrorName(byte e) => e switch { 1 => "FAN 에러", 2 => "기타 에러", _ => "정상" };
|
||||
|
||||
void UpdateFanButtons()
|
||||
{
|
||||
foreach (var child in FanPanel.Children)
|
||||
{
|
||||
if (child is Button btn && btn.Tag is string tag && byte.TryParse(tag, out var f))
|
||||
{
|
||||
bool active = f == _hood.FanStage;
|
||||
btn.Background = active ? AccentCyan : CardBg;
|
||||
btn.Foreground = active ? Brushes.Black : TextPrimary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 마스터 패킷 수신 ==========
|
||||
void OnMasterPacket(byte mode, byte fan, byte en, byte run)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
_rxCount++;
|
||||
txtRxCount.Text = $"수신: {_rxCount}";
|
||||
txtRxMode.Text = $"{mode} ({ModeName(mode)})";
|
||||
txtRxFan.Text = fan == 0 ? "0 (OFF)" : $"{fan}단";
|
||||
txtRxEn.Text = en != 0 ? "Enable" : "Disable";
|
||||
txtRxRun.Text = run != 0 ? "운전중" : "정지";
|
||||
});
|
||||
}
|
||||
|
||||
// ========== 응답 송신 ==========
|
||||
void OnResponseSent(byte[] tx)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
txtTxFan.Text = tx[3] == 0 ? "0 (OFF)" : $"{tx[3]}단";
|
||||
txtTxLight.Text = tx[4] != 0 ? "ON" : "OFF";
|
||||
txtTxCmd.Text = tx[6] != 0 ? "1 (켜짐)" : "0 (꺼짐)";
|
||||
txtTxError.Text = $"{tx[7]} ({ErrorName(tx[7])})";
|
||||
});
|
||||
}
|
||||
|
||||
static string ModeName(byte m) => m switch
|
||||
{
|
||||
0 => "OFF", 1 => "환기", 2 => "자동", 3 => "공청", 4 => "바이패스", 5 => "기타", _ => "?"
|
||||
};
|
||||
|
||||
// ========== 로그 ==========
|
||||
void OnLog(string msg)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
txtLog.AppendText(msg + Environment.NewLine);
|
||||
if (txtLog.LineCount > 500)
|
||||
{
|
||||
var lines = txtLog.Text.Split(Environment.NewLine);
|
||||
txtLog.Text = string.Join(Environment.NewLine, lines[^300..]);
|
||||
}
|
||||
txtLog.ScrollToEnd();
|
||||
});
|
||||
}
|
||||
|
||||
void ClearLog_Click(object s, RoutedEventArgs e) => txtLog.Clear();
|
||||
|
||||
void SaveLog_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var dlg = new SaveFileDialog
|
||||
{
|
||||
Filter = "텍스트 파일 (*.txt)|*.txt",
|
||||
FileName = $"HoodSimLog_{DateTime.Now:yyyyMMdd_HHmmss}.txt"
|
||||
};
|
||||
if (dlg.ShowDialog() == true)
|
||||
{
|
||||
try
|
||||
{
|
||||
string h = $"========================================\r\n 후드 시뮬레이터 통신 로그\r\n 저장 일시: {DateTime.Now:yyyy-MM-dd HH:mm:ss}\r\n========================================\r\n\r\n";
|
||||
File.WriteAllText(dlg.FileName, h + txtLog.Text);
|
||||
MessageBox.Show($"저장 완료: {dlg.FileName}");
|
||||
}
|
||||
catch (Exception ex) { MessageBox.Show($"저장 실패: {ex.Message}"); }
|
||||
}
|
||||
}
|
||||
|
||||
void OnConnectionChanged(bool connected)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (connected)
|
||||
{
|
||||
statusLed.Fill = AccentGreen;
|
||||
txtStatus.Text = "연결됨";
|
||||
btnStart.IsEnabled = true;
|
||||
btnConnect.Content = "연결 해제";
|
||||
}
|
||||
else
|
||||
{
|
||||
statusLed.Fill = AccentRed;
|
||||
txtStatus.Text = "미연결";
|
||||
btnStart.IsEnabled = false;
|
||||
btnStop.IsEnabled = false;
|
||||
btnConnect.Content = "연결";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
# HoodSimulator 사양서
|
||||
|
||||
후드메인(Hood) 장치를 모사하는 C# WPF 시뮬레이터. ERVSimulator(전열교환기, Master)와 RS485로
|
||||
통신하여 후드 상태를 응답한다. 스타일은 DiffuserSimulator와 동일(Tokyo Night 다크 테마).
|
||||
|
||||
## 1. 통신
|
||||
- 프로토콜 : Protocol/HOOD/주신전자_protocol_hood_전열교환기_Rev1.3_20241125.xlsx
|
||||
- 역할 : Slave (마스터 폴 수신 → 현재 후드 상태로 응답)
|
||||
- 포트 : 115200 8N1, 9바이트 고정, 폴링주기 100~500ms, 응답 50ms 이내
|
||||
- 체크섬(CS) : Preamble~CS직전(byte0~7) 전체 XOR
|
||||
|
||||
### 프레임 구조
|
||||
- Master(전열교환기) → Slave(후드) : `AA 21 ID MODE FAN 연동EN 연동운전중 ERROR CS`
|
||||
- Slave(후드) → Master(전열교환기) : `AA 11 ID FANSTATUS LIGHTSTATUS 00 연동CMD ERROR CS`
|
||||
- Preamble 0xAA / M·S 0x21(Master)·0x11(Slave) / ID 0x01 고정
|
||||
|
||||
## 2. UI 기능
|
||||
|
||||
### 통신포트 설정
|
||||
- COM 포트 선택 + 새로고침(⟳)
|
||||
- 연결 / 연결 해제, 통신 시작(시작) / 통신 중지(중지)
|
||||
- 연결 상태 LED(녹색 연결됨 / 빨강 미연결)
|
||||
|
||||
### 전원 on / off
|
||||
- 전원 ON → 풍량 자동 1단
|
||||
- 전원 OFF → 풍량 0
|
||||
|
||||
### 풍량 0(꺼짐) / 1 / 2 / 3 / 4 / 5
|
||||
- 버튼 선택, 선택 단수 강조
|
||||
|
||||
### 조명 on / off
|
||||
|
||||
### 에러코드 (체크 선택하여 발생)
|
||||
- FAN 에러(ERROR=1) / 기타 에러(ERROR=2) 토글 카드 (LED 표시)
|
||||
- 둘 다 선택 시 FAN 에러(1) 우선 송신
|
||||
- **에러 발생 시 전원 OFF / 풍량 0 / 조명 OFF로 강제 전환** 후 상태값 전송
|
||||
|
||||
### 통신 상태 표시
|
||||
- 마스터 수신 명령 : MODE, 전열교환기 FAN, 연동 Enable, 연동 운전중
|
||||
- 후드 응답 송신 : 후드 FAN STATUS, LIGHT STATUS, 연동 CMD, ERROR
|
||||
- 수신 카운트
|
||||
|
||||
### 통신 로그
|
||||
- 송수신 패킷 hex 로그, 저장 / 지우기
|
||||
|
||||
## 3. 응답 상태값 산출 규칙
|
||||
| 응답 필드 | 값 |
|
||||
|---|---|
|
||||
| FAN STATUS (byte3) | 전원 ON 시 풍량 단수(0~5), OFF면 0 |
|
||||
| LIGHT STATUS (byte4) | 전원 ON & 조명 ON → 1, 아니면 0 |
|
||||
| 연동 CMD (byte6) | 전원 ON → 1(켜짐), OFF → 0(꺼짐) |
|
||||
| ERROR (byte7) | 0 정상 / 1 FAN 에러 / 2 기타 에러 |
|
||||
|
||||
## 4. ERVSimulator 연동
|
||||
- ERVSimulator는 Master로서 후드를 200ms 주기 폴 → 응답 수신
|
||||
- ERV 측 "후드연동" 버튼 : 미연결(기본색) / 통신중 정상(녹색) / 통신중 에러(빨강 + 에러명)
|
||||
Reference in New Issue
Block a user