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:
2026-06-15 21:44:23 +09:00
commit 5a96a696b1
265 changed files with 76458 additions and 0 deletions
@@ -0,0 +1,132 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using ErvDashboard.Model;
namespace ErvDashboard
{
// 스마트수면 시간설정 팝업 (원형 다이얼). 시작/종료 시각을 설정 → 적용 시 메인이 종료 시각에 자동 해제 예약.
// 대시보드 전용(ERV 프로토콜 무관). 시각은 자정 기준 분(0~1439, 30분 단위).
public partial class SmartSleepWindow : Window
{
readonly MainWindow _owner;
readonly DashboardState _state;
const double CX = 130, CY = 130, R = 104; // DialCanvas(260x260) 중심/반지름
int _startMin, _endMin;
bool _dragStart, _dragEnd, _updating;
public SmartSleepWindow(MainWindow owner, DashboardState state)
{
InitializeComponent();
_owner = owner;
_state = state;
_startMin = state.SleepStartMin;
_endMin = state.SleepEndMin;
for (int m = 0; m < 1440; m += 30)
{
StartCombo.Items.Add(new ComboBoxItem { Content = DashboardState.FmtTime(m), Tag = m });
EndCombo.Items.Add(new ComboBoxItem { Content = DashboardState.FmtTime(m), Tag = m });
}
StartCombo.SelectionChanged += StartCombo_Changed;
EndCombo.SelectionChanged += EndCombo_Changed;
UpdateDial();
}
// ===== 다이얼/콤보 갱신 =====
void UpdateDial()
{
_updating = true;
SleepArc.Data = BuildArc(_startMin, _endMin);
PositionHandle(StartHandle, _startMin);
PositionHandle(EndHandle, _endMin);
StartRun.Text = DashboardState.FmtTime(_startMin);
EndRun.Text = DashboardState.FmtTime(_endMin);
StartCombo.SelectedIndex = _startMin / 30;
EndCombo.SelectedIndex = _endMin / 30;
_updating = false;
}
Geometry BuildArc(int s, int e)
{
Point p0 = Polar(s / 1440.0 * 360.0);
Point p1 = Polar(e / 1440.0 * 360.0);
double sweep = ((e - s + 1440) % 1440) / 1440.0 * 360.0;
var fig = new System.Windows.Media.PathFigure { StartPoint = p0, IsClosed = false };
fig.Segments.Add(new ArcSegment(p1, new Size(R, R), 0, sweep > 180.0, SweepDirection.Clockwise, true));
var g = new PathGeometry();
g.Figures.Add(fig);
return g;
}
static Point Polar(double angleDeg) // 0°=위, 시계방향
{
double t = angleDeg * Math.PI / 180.0;
return new Point(CX + R * Math.Sin(t), CY - R * Math.Cos(t));
}
static void PositionHandle(FrameworkElement h, int min)
{
Point p = Polar(min / 1440.0 * 360.0);
Canvas.SetLeft(h, p.X - h.Width / 2);
Canvas.SetTop(h, p.Y - h.Height / 2);
}
static int AngleToMin(Point p)
{
double ang = Math.Atan2(p.X - CX, -(p.Y - CY)) * 180.0 / Math.PI; // 0=위, 시계방향
if (ang < 0) ang += 360;
int min = (int)Math.Round(ang / 360.0 * 1440.0 / 30.0) * 30;
return ((min % 1440) + 1440) % 1440;
}
// ===== 드래그 =====
void StartHandle_Down(object sender, MouseButtonEventArgs e) { _dragStart = true; StartHandle.CaptureMouse(); e.Handled = true; }
void EndHandle_Down(object sender, MouseButtonEventArgs e) { _dragEnd = true; EndHandle.CaptureMouse(); e.Handled = true; }
void Handle_Move(object sender, MouseEventArgs e)
{
if (!_dragStart && !_dragEnd) return;
int min = AngleToMin(e.GetPosition(DialCanvas));
if (_dragStart) _startMin = min; else _endMin = min;
UpdateDial();
}
void Handle_Up(object sender, MouseButtonEventArgs e)
{
_dragStart = _dragEnd = false;
(sender as UIElement)?.ReleaseMouseCapture();
}
// ===== 콤보 =====
void StartCombo_Changed(object sender, SelectionChangedEventArgs e)
{
if (_updating || StartCombo.SelectedItem is not ComboBoxItem it) return;
_startMin = (int)it.Tag;
UpdateDial();
}
void EndCombo_Changed(object sender, SelectionChangedEventArgs e)
{
if (_updating || EndCombo.SelectedItem is not ComboBoxItem it) return;
_endMin = (int)it.Tag;
UpdateDial();
}
// ===== 버튼 =====
void Apply_Click(object sender, RoutedEventArgs e)
{
_state.SleepStartMin = _startMin;
_state.SleepEndMin = _endMin;
_owner.ApplySmartSleep();
Close();
}
void Close_Click(object sender, RoutedEventArgs e) => Close();
}
}