feat(mcp): Dabit.Mcp 초기 공개 (리네임 + colors 수정 + scroll 검증)
- refactor: DabitChe.Mcp → Dabit.Mcp (namespace, csproj, exe 모두) - fix: display_message colors를 쉼표 구분 문자열로 변경 (ModelContextProtocol 1.3.0 배열 바인딩 버그 우회) - docs: scroll 4방향(left/right/up/down) 실기 검증 완료 (DIBD600T V3.6.1) 20 도구 (읽기 4 + 콘텐츠 2 + 설정 14), stdio 전송, Windows exe. 실기 검증: scroll/fill_screen 8색/display_message 글자별색 모두 정상. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
505
CLAUDE.md
Normal file
505
CLAUDE.md
Normal file
@@ -0,0 +1,505 @@
|
|||||||
|
# CLAUDE.md — DabitChe 모노레포 루트
|
||||||
|
|
||||||
|
LED 전광판 콘텐츠 제작 + 스케줄 관리 + 컨트롤러 전송 소프트웨어 (다빛솔루션 DABIT)
|
||||||
|
|
||||||
|
## 프로그램 정체성
|
||||||
|
|
||||||
|
DabitChe는 **LED 전광판 운영 소프트웨어**이다. 핵심 기능은 콘텐츠 제작(텍스트/이미지) → 스케줄 편집(PLA/BGP) → 컨트롤러 전송이다. 통신설정, 제어, 시뮬레이터는 보조 기능이다. dbSimulator(DBPS)는 dabitche의 부분집합으로, 프로토콜 시뮬레이터 기능에 해당한다. 사용자는 현장 설치기사/운영자이며, 프로토콜을 몰라도 사용 가능해야 한다.
|
||||||
|
|
||||||
|
## Naming 규칙 (3층) — 회사 / 제품 / 프로토콜
|
||||||
|
|
||||||
|
이름은 3층으로 나눈다. 새 프로젝트·모듈·MCP 명명 시 이 규칙을 따른다.
|
||||||
|
|
||||||
|
| 층 | 이름 | 적용 대상 | 예 |
|
||||||
|
|----|------|----------|-----|
|
||||||
|
| 회사/브랜드 | **Dabit** (다빛솔루션 DABIT) | 하드웨어 컨트롤러를 다루는 브랜드 레벨 자산 | MCP 서버 `Dabit.Mcp` (등록명 `dabit-led`) |
|
||||||
|
| 제품 | **DabitOne** (레거시명 = DabitChe) | PC 운영 앱 | `DabitOne.Desktop`, `DabitOne.AutoSend` |
|
||||||
|
| 프로토콜 | **DibdProtocol** | 제품 무관 프로토콜/전송 라이브러리 | `DibdProtocol.Core` / `.Transport` |
|
||||||
|
|
||||||
|
- **레거시 잔재 주의**: 폴더·namespace·sln은 아직 `DabitChe.*`(Desktop/AutoSend). 단 출력물(exe `DabitOne.exe`)·창 제목은 이미 `DabitOne`. 내부 `DabitChe`→`DabitOne` 전체 리네임은 **워크트리 다수 동시 진행**이라 충돌 위험 → main 단일 big-bang으로 일괄 처리 예정(보류).
|
||||||
|
- `Dabit.Mcp`가 형제 `DabitOne.*`와 이름이 다른 건 **의도된 계층 구분**(브랜드 vs 제품)이지 실수가 아니다. (2026-06-02 MCP만 선행 리네임: `DabitChe.Mcp`→`Dabit.Mcp`, 등록명 `dabitche-led`→`dabit-led`)
|
||||||
|
|
||||||
|
## UI/UX 원칙
|
||||||
|
|
||||||
|
| 원칙 | 설명 |
|
||||||
|
|------|------|
|
||||||
|
| **가로 우선 레이아웃** | 콘텐츠 편집 + 스케줄 그리드가 핵심 → 가로 공간을 넉넉히 확보 |
|
||||||
|
| **좁은 레이아웃 금지** | dbSimulator(580px 고정)의 좁은 배치를 그대로 가져오지 않음 — 가로 900px+ 기준으로 재배치 |
|
||||||
|
| **GroupBox 가로 병렬** | 통신설정 등에서 Serial/TCP/UDP를 세로로 쌓지 말고 가로 2~3열로 배치 |
|
||||||
|
| **입력 필드 폭 제한** | COM 포트(5글자), IP(15글자), Port(5글자) → 필드 폭을 내용에 맞춤, 우측 여백 과다 금지 |
|
||||||
|
| **전체 통일감** | 한 패널의 배치를 바꾸면 나머지 패널도 동일 원칙 적용 |
|
||||||
|
| **사이드바 5그룹** | 통신 → 설정 → 시뮬 → 편집 → 고급 (사용 순서 = 위→아래) |
|
||||||
|
| **Design Token System** | `Themes/` 폴더에 토큰 + implicit style 집중. **토큰 1곳 수정 → 전체 반영**이 핵심. 정본: `AI_Rules/080_ui-design-system.md` |
|
||||||
|
| **창 크기 기준** | 새 Window/팝업의 크기 요청 시 **메인 창(MainWindow) 대비 비율**로 확인. 예: "메인의 2/3", "메인의 1/2". 비율 미지정 시 사용자에게 "메인 창 대비 몇 분의 몇으로 할까요?" 확인 후 적용 |
|
||||||
|
|
||||||
|
### Design Token 필수 규칙
|
||||||
|
|
||||||
|
| 규칙 | 이유 |
|
||||||
|
|------|------|
|
||||||
|
| 인라인 FontSize/Height/Background/Foreground 금지 | 토큰 파일에서만 관리해야 전역 변경 가능 |
|
||||||
|
| RowDefinition Height="Auto" 필수 | 고정값은 Control.Margin을 찌그러뜨림 |
|
||||||
|
| 모든 입력 컨트롤에 FontWeight="Normal" 명시 | GroupBox SemiBold 상속 차단 |
|
||||||
|
| Window 태그에 `Background="{StaticResource Surface.Background}"` | Window는 implicit style 불가 |
|
||||||
|
| 새 컨트롤 추가 시 Themes/ implicit style 확인 | 토큰 시스템 밖에서 컨트롤이 생성되면 불일치 발생 |
|
||||||
|
|
||||||
|
### Design Token 현재 값 (참고)
|
||||||
|
|
||||||
|
값은 `Themes/_Typography.xaml`, `_Spacing.xaml`, `_Colors.xaml`에서 관리. 변경 시 해당 파일만 수정.
|
||||||
|
|
||||||
|
| 토큰 | 현재 값 | 파일 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `Control.Height` | 26 (MinHeight) | `_Typography.xaml` |
|
||||||
|
| `Control.Margin` | 0,4,0,4 | `_Spacing.xaml` |
|
||||||
|
| `Font.Size.MD` | 12 | `_Typography.xaml` |
|
||||||
|
|
||||||
|
**Button 규칙:**
|
||||||
|
- 전역 스타일이 `MinHeight=26`, `Padding="12,0"`으로 설정됨 — **ComboBox/TextBox와 동일 높이**
|
||||||
|
- **인라인 Padding 금지** — 버튼마다 다른 Padding을 넣으면 높이가 달라져 이질적. 전역 스타일만 사용
|
||||||
|
- 인라인 `FontWeight`, `FontFamily`, `Height` 지정 금지 — 전역 스타일(`Themes/Button.xaml`)에서 관리
|
||||||
|
- 예외적으로 큰 버튼이 필요하면 Named Style(x:Key)로 별도 정의. 인라인 Padding은 여전히 금지
|
||||||
|
|
||||||
|
## 핵심 규칙
|
||||||
|
|
||||||
|
- **한국어 응답**, 간결함 최우선 (3~5줄), 인사말/서론/결론 생략
|
||||||
|
- **PowerShell 7.0+** 필수 (`pwsh.exe`), Bash/Linux 명령어 금지
|
||||||
|
- **C# nullable enable** (WPF), **TypeScript strict** (Electron)
|
||||||
|
- camelCase/PascalCase/SCREAMING_SNAKE_CASE, UTF-8 LF
|
||||||
|
- **Conventional Commits**: `<type>(<scope>): <desc>` (scope: protocol, ui, transport, settings, desktop, electron, test, docs)
|
||||||
|
- 환각 방지: 근거 부족 시 명시, 불확실 시 추측 표기 + 대안 제시
|
||||||
|
|
||||||
|
### AI 코딩 4대 원칙
|
||||||
|
|
||||||
|
1. **Think Before Coding** — 불확실하면 추측하지 말고 먼저 확인
|
||||||
|
2. **Simplicity First** — 요청받은 것만 구현
|
||||||
|
3. **Surgical Changes** — 수정 대상만 변경
|
||||||
|
4. **Goal-Driven Execution** — 명령보다 목표로 지시
|
||||||
|
|
||||||
|
### 모듈화(Modularity) 원칙
|
||||||
|
|
||||||
|
핵심 기능은 나중에 독립 앱/CLI/REST API로 추출할 수 있게 만든다:
|
||||||
|
- **Service 계층에 WPF 타입(Window, Dispatcher, Application 등) 직접 참조 금지** — UI 알림은 Result<T> 반환 후 ViewModel이 처리
|
||||||
|
- **DibdProtocol/은 플랫폼 무관 라이브러리로 유지** — System.Windows 참조 절대 금지
|
||||||
|
- **새 Service 추가 시 `DialogService.Instance` 직접 호출 금지** — IDialogService 주입 또는 ViewModel에서 처리
|
||||||
|
- **`DibdProtocol.Transport/Rpc/`** — DIBD JSON-RPC 2.0 envelope 96 method 본문, namespace `Dabit.LedProtocol` (Blazor WASM 회피 영역이라 Transport 어셈블리 적재, 0517a 트랙)
|
||||||
|
|
||||||
|
### 영속성(Persistence) 필수 규칙
|
||||||
|
|
||||||
|
새 기능/UI 추가 시 반드시 확인:
|
||||||
|
- **사용자가 입력한 모든 설정값은 앱 재시작 후에도 유지**되어야 한다
|
||||||
|
- 새 입력 컨트롤(ComboBox, TextBox, CheckBox 등) 추가 시 → AppSettings + config.properties 저장/복원 구현 필수
|
||||||
|
- 저장 경로: `AppSettings` → `PropertiesConfigRepository` → `config.properties`
|
||||||
|
- 슬롯 구조 데이터는 `SettingService.SetProperties()` 배치 저장 사용
|
||||||
|
- 앱 종료 시 `App.OnExit()`에서 자동 저장됨 — 새 ViewModel 추가 시 여기에 SaveSettings 호출 추가
|
||||||
|
|
||||||
|
## FORBIDDEN (19개)
|
||||||
|
|
||||||
|
위반 시 현장 장비 오동작 또는 아키텍처 붕괴. 예외 없음.
|
||||||
|
|
||||||
|
### 프로토콜/바이너리 (F3–F5, F7–F8, F16–F19)
|
||||||
|
|
||||||
|
| ID | 금지 | 이유 |
|
||||||
|
|----|------|------|
|
||||||
|
| F3 | OPCode 값 변경 금지 | 컨트롤러 펌웨어가 고정 OPCode에 의존 |
|
||||||
|
| F4 | HEX 프레임(DLE-STX) 포맷 변경 금지 | 컨트롤러 파싱 고정 |
|
||||||
|
| F5 | ASCII 프레임(`![...!]`) 포맷 변경 금지 | 레거시 호환 |
|
||||||
|
| F7 | `pendingDle` 상태 제거 금지 | TCP 버퍼 경계 DLE(0x10) 분할 시 패킷 손실 |
|
||||||
|
| F8 | `DLE_OPCODES` Set 변경 금지 | C# `ShouldApplyDle()`과 정확히 일치 |
|
||||||
|
| F16 | DisplaySignal 시그널 코드 변경 금지 | 70+ 모델이 의존 |
|
||||||
|
| F17 | CrcCalculator 수정 금지 | 펌웨어 CRC 테이블과 일치 필수 |
|
||||||
|
| F18 | MSchedule 25바이트 구조 변경 금지 | 펌웨어 `memcpy` 직접 매핑 |
|
||||||
|
| F19 | tsParameter 792바이트 구조/오프셋 변경 금지 | 바이너리 레이아웃 고정 |
|
||||||
|
|
||||||
|
### INI/설정 (F1–F2, F9, F11)
|
||||||
|
|
||||||
|
| ID | 금지 | 이유 |
|
||||||
|
|----|------|------|
|
||||||
|
| F1 | INI 키 오타 수정 금지 (`ColorOder`, `ShowInfomation`, `PorotocolType`) | 현장 장비 하위 호환성 |
|
||||||
|
| F2 | Boolean INI 값을 `true`/`false` 변경 금지 | 레거시 앱·컨트롤러가 `0`/`1`만 읽음 |
|
||||||
|
| F9 | API Key/비밀번호 하드코딩 금지 | 환경변수 사용 필수 |
|
||||||
|
| F11 | AppSettings static 필드 직접 수정 금지 | `IConfigRepository` 경유 필수 |
|
||||||
|
|
||||||
|
### 아키텍처 (F6, F10, F12–F15)
|
||||||
|
|
||||||
|
| ID | 금지 | 이유 |
|
||||||
|
|----|------|------|
|
||||||
|
| F6 | BrowserRouter 사용 금지 | Electron file:// 미지원, HashRouter 필수 (WPF 해당 없음) |
|
||||||
|
| F10 | Bash 명령어 금지 | PowerShell 7.0+ 사용 |
|
||||||
|
| F12 | SerialTransport `portLock`(SemaphoreSlim) 우회 금지 | 동시 접근 시 포트 충돌 |
|
||||||
|
| F13 | DI 외부에서 Service 직접 생성 금지 | DI 컨테이너 일관성 |
|
||||||
|
| F14 | ViewModel에서 직접 Transport 호출 금지 | Service 계층 경유 필수 |
|
||||||
|
| F15 | MQTT 토픽명 변경 금지 (`/db_msg`, `/db_set`, `/db_sch`, `/db_msg_r`, `/db_sch_r`) | 장비 구독 고정 |
|
||||||
|
|
||||||
|
### 읽기 전용
|
||||||
|
|
||||||
|
| 금지 | 이유 |
|
||||||
|
|------|------|
|
||||||
|
| `references/DabitChe-V2.2.1/` 소스 수정 | 읽기 전용 레거시 참조 — 마이그레이션 대조 기준 훼손 |
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
dabitche/
|
||||||
|
├── DabitChe.Desktop/ ← [NEW] C# .NET 9.0 + WPF + CommunityToolkit.Mvvm
|
||||||
|
│ ├── Views/ │ XAML UI (19 FU 매핑)
|
||||||
|
│ ├── ViewModels/ │ MVVM ViewModel
|
||||||
|
│ ├── Services/ │ 비즈니스 로직
|
||||||
|
│ ├── Config/ │ AppSettings, IConfigRepository
|
||||||
|
│ └── Helpers/ │ EventBus, Result<T>
|
||||||
|
├── DabitChe.Desktop.Tests/ ← xUnit 데스크톱 테스트
|
||||||
|
├── DibdProtocol/ ← [SHARED] 프로토콜 라이브러리 (Desktop + Simulator 공유)
|
||||||
|
│ ├── Protocol/ │ HexCommand, AsciiCommand, PacketBuilder, PacketParser
|
||||||
|
│ ├── Codec/ │ CrcCalculator, DleFramer, BccCalculator, EncodingConverter
|
||||||
|
│ ├── Transport/ │ ITransport, Serial/TCP/UDP/MQTT/BT
|
||||||
|
│ ├── Model/ │ DisplayConfig, ConnectionState, MessageSlot
|
||||||
|
│ └── Image/ │ DatBuilder, RgbConverter
|
||||||
|
├── DibdProtocol.Tests/ ← xUnit 프로토콜 테스트
|
||||||
|
├── DabitChe.Electron/ ← [현행] Electron 33 + React 19 + Vite 6 + Zustand 5
|
||||||
|
│ ├── src/main/ │ Electron 메인 (IPC, services, transport)
|
||||||
|
│ ├── src/renderer/ │ React UI (10 pages, 44 components, 8 stores)
|
||||||
|
│ └── src/shared/ │ 공유 타입/프로토콜/i18n
|
||||||
|
├── references/ ← 참조 자료 집중
|
||||||
|
│ ├── DabitChe-V2.2.1/ │ [읽기 전용] C++ Builder 6 레거시
|
||||||
|
│ ├── DabitChe_Java/ │ [로컬 참조] Java 포팅 (git 추적 안 함)
|
||||||
|
│ ├── Font/ │ .fnt + FontConverter (릴리즈에 복사)
|
||||||
|
│ ├── firmware/ │ .bin 펌웨어 (릴리즈에 복사)
|
||||||
|
│ └── DabitChe_UserManual_v9.0.pdf
|
||||||
|
└── docs/ ← 통합 문서
|
||||||
|
```
|
||||||
|
|
||||||
|
### 전환 전략
|
||||||
|
|
||||||
|
- **현행**: DabitChe.Electron 운영 중
|
||||||
|
- **목표**: DabitChe.Desktop(C# WPF)으로 점진적 대체
|
||||||
|
- **공유**: DibdProtocol/ 라이브러리를 Desktop과 dbSimulator가 공유
|
||||||
|
- 프로토콜 엔진은 DibdProtocol/에 집중, 앱별 중복 구현 금지
|
||||||
|
|
||||||
|
## Build & Test
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# ── C# WPF (새) ──
|
||||||
|
cd DabitChe.Desktop
|
||||||
|
dotnet build
|
||||||
|
dotnet test ../DibdProtocol.Tests
|
||||||
|
dotnet test ../DabitChe.Desktop.Tests
|
||||||
|
|
||||||
|
# ── Electron (현행) ──
|
||||||
|
cd DabitChe.Electron
|
||||||
|
npm test # Vitest 387+개
|
||||||
|
npm run test:e2e # Playwright E2E
|
||||||
|
npm run dev # Vite dev (:5173)
|
||||||
|
npm run dev:electron # 전체 앱
|
||||||
|
npm run package:win # NSIS 인스톨러
|
||||||
|
```
|
||||||
|
|
||||||
|
### DibdProtocol.Core 변경 시 브라우저 안전성 게이트
|
||||||
|
|
||||||
|
`DibdProtocol.Core/` 하위 소스를 수정한 커밋은 CI(`.gitea/workflows/ci.yml`)에서
|
||||||
|
다음을 자동 실행·검증한다.
|
||||||
|
|
||||||
|
1. `dotnet build DabitChe.sln --configuration Release`
|
||||||
|
2. `dotnet test DibdProtocol.Tests` + `dotnet test DibdProtocol.Core.Tests`
|
||||||
|
3. `dotnet publish DibdProtocol.Core.BlazorCheck/... --configuration Release`
|
||||||
|
|
||||||
|
3번이 실패하면 Core에 브라우저 비호환 API(System.IO.Ports / System.Net.Sockets /
|
||||||
|
MQTTnet 등)가 섞인 것. 로컬에서도 Core 수정 후
|
||||||
|
`dotnet publish DibdProtocol.Core.BlazorCheck -c Release` 수동 실행을 권장한다.
|
||||||
|
`dotnet build`만으로는 WASM 링커 회귀를 감지 못 한다.
|
||||||
|
|
||||||
|
**단, publish 성공은 "WASM 링커·트림 통과"까지만 증명하고, 브라우저에서
|
||||||
|
`Pages/Home.razor`의 `OnInitialized()` smoke가 실제 실행됨을 보장하지는 않는다.**
|
||||||
|
런타임 증명이 필요하면 `dotnet run --project DibdProtocol.Core.BlazorCheck`로
|
||||||
|
브라우저에서 5-way smoke(CRC·DLE·EUC-KR·byte-font·ASCII)를 육안 확인하거나,
|
||||||
|
Phase 1+에서 Playwright 1회 접속 step을 CI에 추가한다.
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
DabitChe.Electron에 Claude Code hooks가 설정되어 있다.
|
||||||
|
|
||||||
|
| 패턴 | 동작 |
|
||||||
|
|------|------|
|
||||||
|
| 커밋 전 품질 게이트 | git commit 시 `npm run quality` 자동 실행, 실패 시 차단 |
|
||||||
|
| FORBIDDEN 위반 감지 | 파일 수정 시 FORBIDDEN 패턴 정규식 매칭, 위반 시 경고 |
|
||||||
|
| /careful 안전 모드 | `.claude/.careful` 존재 시 위험 명령 차단 |
|
||||||
|
| Gotchas 자동 수집 | 세션 종료 시 빌드/테스트 실패 패턴 분석 |
|
||||||
|
|
||||||
|
## 로그 작성 원칙
|
||||||
|
|
||||||
|
`LogService`는 4 레벨(Info/Warn/Error/Debug)을 제공한다. UI는 평소 Info/Warn/Error만 표시하고, Debug는 로그 헤더의 "Debug 보기" 토글(`MainWindow.xaml`)을 켜야 보인다. 호출부에서 적절한 레벨을 선택해 노이즈를 줄인다.
|
||||||
|
|
||||||
|
### 레벨 분류 기준
|
||||||
|
|
||||||
|
- **Info** — 사용자 시나리오 흐름. 작업 시작/완료, 핵심 결과, 세션 라이프사이클. **TX/RX 단발 명령은 Info 유지** (업체가 시스템 구현 시 protocol 참고용 — `_logService.Info($"TX: 시간동기화 ASCII ![0030...!]")` 같은 라인은 의도적으로 보임)
|
||||||
|
- **Warn** — 사용자 조치가 필요한 비정상. 파일 누락, 응답 status 비정상, 펌웨어 거부 가능성
|
||||||
|
- **Error** — 작업 실패. 통신 끊김, 예외
|
||||||
|
- **Debug** — 평소 OFF, 트러블슈팅 시 ON. 패킷 단위 hex 덤프(`TX[cmd] NB → RX MB (Tms)`), 반복 호출 진행률(블록 N/M), 분기 결정 trace, 동일값 반복 entry 출력
|
||||||
|
|
||||||
|
### 연속 전송 (펌웨어/폰트/이미지 다블록)
|
||||||
|
|
||||||
|
블록 단위 진행률은 **Debug**. 시작·요약(블록 수)·완료만 **Info**.
|
||||||
|
|
||||||
|
```
|
||||||
|
Info: 펌웨어 전송 시작 219블록
|
||||||
|
Debug: 펌웨어 전송: 1/219 (0%) ← 블록 단위 진행률
|
||||||
|
Debug: 펌웨어 전송: 51/219 (23%)
|
||||||
|
...
|
||||||
|
Info: 펌웨어 전송 완료
|
||||||
|
```
|
||||||
|
|
||||||
|
### 임시 디버그 (`DEBUG-TEMP`)
|
||||||
|
|
||||||
|
특정 버그 추적·신규 기능 검증을 위해 박은 일회성 로그는 다음 중 하나로 마킹한다:
|
||||||
|
|
||||||
|
1. 메시지 접두사: `_logService.Debug($"[DEBUG-TEMP] ...")`
|
||||||
|
2. 코드 주석: `// DEBUG-TEMP: <목적>, <날짜>`
|
||||||
|
|
||||||
|
검증 완료 시점에 두 갈래로 정리:
|
||||||
|
|
||||||
|
- "환경에 따라 또 깨질 수 있나?" (예: TCP fragment, 재부팅 응답) → **영구 Debug로 강등** — `[DEBUG-TEMP]` 접두사 제거하고 `_logService.Debug`만 유지
|
||||||
|
- "한번 검증으로 끝났나?" (예: 특정 byte offset 확인) → **제거**
|
||||||
|
|
||||||
|
월 1회 또는 PR 직전 `git grep "DEBUG-TEMP"`로 잔존 검토.
|
||||||
|
|
||||||
|
### 영구 Debug 후보 — 시스템 경계
|
||||||
|
|
||||||
|
코드 안쪽 깊은 곳의 trace는 임시 디버그(검증 후 제거)지만, **시스템 경계의 객관적 사실**은 영구 Debug로 둔다:
|
||||||
|
|
||||||
|
- 외부 I/O raw byte (TX/RX hex, latency `(Tms)`)
|
||||||
|
- 분기 결정 (어떤 path 탔는지: legacy vs modern, ImageDir fallback 등)
|
||||||
|
- 재시도/fallback 작동 (`ack timeout, retry 2/3`)
|
||||||
|
- 외부 환경 로드 결과 (`config.properties NN키 로드, 인코딩=EUC-KR`)
|
||||||
|
- 변환 입출력 byte count (`ConvertToSplitDat: 3549B → 612B+893B`)
|
||||||
|
|
||||||
|
기준 한 줄: "사용자 환경에서만 재현되는 문제를 원격 진단할 때 결정적 단서인가?"
|
||||||
|
|
||||||
|
## Data Flow Chains (흐름 기반 탐색)
|
||||||
|
|
||||||
|
함수 시그니처는 AI 도구(grep/glob)로 직접 탐색한다.
|
||||||
|
|
||||||
|
### C# WPF (DabitChe.Desktop + DibdProtocol)
|
||||||
|
|
||||||
|
| 시나리오 | 호출 체인 |
|
||||||
|
|---------|----------|
|
||||||
|
| UI → 컨트롤러 명령 | View → ViewModel → Service → DibdProtocol.Protocol → DibdProtocol.Transport |
|
||||||
|
| 프로토콜 수신 파싱 | Transport → PacketParser → Service → ViewModel → View |
|
||||||
|
| INI 설정 로드 | Config → IConfigRepository → fs (EUC-KR → UTF-8) |
|
||||||
|
| 파일 블록 전송 | Service → 블록 분할 (1024B) → Protocol → Transport |
|
||||||
|
| 스케줄 파일 | Service → PLA 직렬화/역직렬화 (리틀엔디언) → fs |
|
||||||
|
|
||||||
|
### Electron (DabitChe.Electron)
|
||||||
|
|
||||||
|
| 시나리오 | 호출 체인 |
|
||||||
|
|---------|----------|
|
||||||
|
| UI → 컨트롤러 명령 | Renderer → preload (contextBridge) → IPC → DeviceCommandService → HexProtocolBuilder → Transport |
|
||||||
|
| 프로토콜 수신 파싱 | Transport → ProtocolParser (상태 머신) → CRC 검증 → DeviceCommandService → IPC → Renderer |
|
||||||
|
| INI 설정 로드 | Renderer → IPC → IniSettingsService → fs.readFileSync → iconv.decode (EUC-KR) |
|
||||||
|
| 파일 블록 전송 | Renderer → IPC → 블록 분할 (1024B) → DeviceCommandService → HexProtocolBuilder → Transport |
|
||||||
|
| 스케줄 파일 | Renderer → IPC → ScheduleFileService → PLA 직렬화/역직렬화 (리틀엔디언) |
|
||||||
|
|
||||||
|
## Symptom → Where to Look
|
||||||
|
|
||||||
|
| 증상 | 확인 위치 (C# WPF) | 확인 위치 (Electron) |
|
||||||
|
|------|--------------------|--------------------|
|
||||||
|
| 프로토콜 바이트 불일치 | `DibdProtocol/Protocol/` | `src/shared/protocol/` |
|
||||||
|
| UI 렌더링 문제 | `DabitChe.Desktop/Views/` | `src/renderer/pages/`, `src/renderer/components/` |
|
||||||
|
| 통신/시리얼 문제 | `DibdProtocol/Transport/` | `src/main/services/transport/` |
|
||||||
|
| INI 설정 문제 | `DabitChe.Desktop/Config/` | `src/main/services/settings/ini-settings-service.ts` |
|
||||||
|
| 빌드/테스트 실패 | `dotnet build` 출력 | `DabitChe.Electron/package.json` 스크립트 |
|
||||||
|
| 레거시 동작 참조 | `references/DabitChe-V2.2.1/CLAUDE.md` | 동일 |
|
||||||
|
|
||||||
|
## Decisions (왜 이렇게 했는지)
|
||||||
|
|
||||||
|
| 결정 | 이유 |
|
||||||
|
|------|------|
|
||||||
|
| C++ Builder → C# → Electron → C# WPF | 레거시 65개 TForm 직접 포팅 불가, 단계적 재작성 |
|
||||||
|
| dbSimulator DibdProtocol 공유 | 프로토콜 엔진 중복 개발 방지 |
|
||||||
|
| INI 오타(`ColorOder` 등) 보존 | 현장 장비 하위 호환성 유지 |
|
||||||
|
| INI 읽기 EUC-KR → 저장 UTF-8 | 점진적 인코딩 전환 |
|
||||||
|
| BCD 시간 인코딩 | 레거시 컨트롤러 RTC 호환 |
|
||||||
|
| 13바이트 밝기 payload | 시간대별 밝기 + CDS 센서 레거시 구조 |
|
||||||
|
| HexProtocolBuilder C# byte-identical | V3.0.0(C#)과 바이트 일치 보장 |
|
||||||
|
| Electron IPC preload.ts 경유 | contextIsolation 보안 모델 준수 |
|
||||||
|
| WPF ViewModel → Service → Transport | MVVM 계층 분리, Transport 직접 호출 금지 (F14) |
|
||||||
|
| DI 컨테이너 필수 | Service 생명주기 관리 일관성 (F13) |
|
||||||
|
|
||||||
|
## Markdown 작성 규칙 (`docs/` 편집 시)
|
||||||
|
|
||||||
|
1. **Frontmatter 필수**: title, tags, last_updated 3필드
|
||||||
|
2. **H1 1개**, 레벨 스킵 금지 (H2→H3 순서)
|
||||||
|
3. **첫 문장 = 문맥 앵커**: H2 직후 첫 문장에 주어+문맥 포함
|
||||||
|
4. **한 문단 = 한 줄** (Git Diff 가독성), 코드블록 독립 (언어 태그 필수)
|
||||||
|
5. **TL;DR**: 문서 상단에 무엇/왜/어떻게 3줄 요약
|
||||||
|
|
||||||
|
## Deep Context (심층 문서)
|
||||||
|
|
||||||
|
심볼 검색은 AI 도구(grep/glob)에 맡기고, 문서에는 코드가 말 못 하는 것만 담는다.
|
||||||
|
|
||||||
|
| 작업 유형 | 읽을 문서 |
|
||||||
|
|----------|----------|
|
||||||
|
| 정본 매트릭스 (SSOT) | `docs/00_AI_가이드/README.md` |
|
||||||
|
| 로드맵 | `docs/00_AI_가이드/10_ROADMAP.md` |
|
||||||
|
| 프로젝트 구조 | `docs/00_AI_가이드/11_프로젝트컨텍스트.md` |
|
||||||
|
| 코딩 패턴 | `docs/00_AI_가이드/20_CODE_PATTERNS.md` |
|
||||||
|
| 모듈/프로토콜 참조 | `docs/00_AI_가이드/30_MODULE_INDEX.md` |
|
||||||
|
| 워크플로우 절차 | `docs/00_AI_가이드/40_WORKFLOW.md` |
|
||||||
|
| 스킬 레퍼런스 | `docs/00_AI_가이드/41_SKILL_REFERENCE.md` |
|
||||||
|
| 빌드 함정/에러 패턴 | `docs/00_AI_가이드/50_BUILD_ERRORS.md` |
|
||||||
|
| 테스트 카탈로그 | `docs/00_AI_가이드/60_TEST_CATALOG.md` |
|
||||||
|
| 자율 루프 | `docs/00_AI_가이드/70_AUTONOMOUS_LOOP.md` |
|
||||||
|
| 시각 검증 | `docs/00_AI_가이드/72_VISUAL_VERIFY.md` |
|
||||||
|
| DeepDive (모듈별) | `docs/00_AI_가이드/Modules/` |
|
||||||
|
| 플랜+회고 | `docs/00_AI_가이드/Decisions/` |
|
||||||
|
| 프로토콜 바이트 명세 | `docs/01_protocols/protocol-reference.md` |
|
||||||
|
| 레거시 참조 | `references/DabitChe-V2.2.1/CLAUDE.md` |
|
||||||
|
| UI 디자인 시스템 | `AI_Rules/080_ui-design-system.md` (토큰 정의, 컴포넌트 스펙, 적용 절차) |
|
||||||
|
|
||||||
|
### PM 시스템 (장기 매니저 운영 + 자동 분업)
|
||||||
|
|
||||||
|
DabitONe의 통합 매니저 역할은 별도 문서·슬래시 커맨드·claude agents로 운영된다. 새 세션에서 PM 직책을 인수하려면 `/pm-start`로 부트스트랩, 종료 직전 `/pm-end`로 인계. 작업 분해 후 PM이 `@worker-isolated`, `@researcher`, `@code-reviewer`, `@dabitone-protocol-expert`를 자동 호출.
|
||||||
|
|
||||||
|
**시간축 (PM 시스템 문서·커맨드):**
|
||||||
|
|
||||||
|
| 산출물 | 용도 |
|
||||||
|
|--------|------|
|
||||||
|
| `PROJECT_BRAIN.md` (루트) | 현재 동적 상태 — 진행·보류·우선순위. 매 세션 갱신 |
|
||||||
|
| `docs/PM/PM_CONTEXT_MAP.md` | 영역 색인 — "어디에 뭐가 있나" |
|
||||||
|
| `docs/PM/PM_PROTOCOL.md` | PM 운영 룰 — 4-Role·검토·팀빌딩·가드레일 |
|
||||||
|
| `docs/PM/templates/spec.md` | Worker brief 표준 템플릿 |
|
||||||
|
| `docs/PM/templates/retrospective.md` | Phase 회고 템플릿 |
|
||||||
|
| `/pm-start` | PM 세션 부트스트랩 슬래시 커맨드 |
|
||||||
|
| `/pm-end` | PM 세션 종료 + `PROJECT_BRAIN.md` 갱신 |
|
||||||
|
|
||||||
|
**공간축 (claude agents 4종 — `.claude/agents/`):**
|
||||||
|
|
||||||
|
| Agent | 역할 | 호출 시점 |
|
||||||
|
|-------|------|---------|
|
||||||
|
| `worker-isolated` | 격리된 worktree에서 spec.md 따라 한 Phase·기능 구현. 빌드·테스트·commit | PM이 Phase 분해 후 호출 |
|
||||||
|
| `researcher` | 외부 자료·라이브러리·기술 비교 조사. WebFetch·WebSearch | 결정 분기 전 호출 |
|
||||||
|
| `code-reviewer` | Worker 완료 코드 독립 검토 (worker 대화 안 봄, 확증편향 회피) | Worker 완료 후 자동 호출 |
|
||||||
|
| `dabitone-protocol-expert` | DIBD 프로토콜 + 콘텐츠 포맷(DAT/PLA/BGP/ANI) + 전송 절차 전문가 | 프로토콜·콘텐츠·전송 영역 변경 시 자동 호출 |
|
||||||
|
|
||||||
|
**시행 강제 (Hook 1개 — `.claude/hooks/`):**
|
||||||
|
|
||||||
|
| 산출물 | 역할 | 호출 시점 |
|
||||||
|
|--------|------|---------|
|
||||||
|
| `r-pm-1-guard.ps1` | **R-PM-1 시행 강제** — PM 직접 grep/glob/read 3회 경고·5회 차단 | PreToolUse 자동 (`.claude/.pm-session-active` 존재 시 발동) |
|
||||||
|
|
||||||
|
**R-PM 직책 한도 (PM 세션에서 매번 준수)**:
|
||||||
|
- **R-PM-1**: 다중 grep/glob/read 직접 금지 — sub-agent(Explore/general-purpose)에 위임 필수. Hook이 3회 경고·5회 차단으로 시행 강제
|
||||||
|
- **R-PM-2**: 단일 핵심 문서 Read·작은 fix·사용자 인터뷰·PROJECT_BRAIN 갱신은 PM 직접 OK
|
||||||
|
- **R-PM-5**: 사용자 이미 답한 내용 재질문 금지·다음 단계 진입 전 명시 동의 받기·"잘못됐다" 지적 시 즉시 중단·결정 항목은 1~3개 선택지로 좁힘
|
||||||
|
- **R-PM-7**: Evidence Tier 강제 — 모든 결론 ✅사실(카메라/실기 확인) / 🟡추정(코드·논리 미검증) / ❌가정(미입증) 3분류 자동 분리. **코드 변경 ≠ 검증.** 사용자가 "근거는?" 묻기 *전에* 자발 분리. 사례: 2026-05-26 retry 정책 — PM이 코드 변경만 보고 "회복 + 견고" 단정했다가 사용자 지적
|
||||||
|
- 상세: `docs/PM/PM_PROTOCOL.md` 2.5 섹션, `AI_Rules/100_pm-system.md` "PM 직책 한도 R-PM-7"
|
||||||
|
|
||||||
|
**🚨 세션 종료 직전 반드시 `/pm-end`** — `PROJECT_BRAIN.md` 갱신 누락은 다음 PM 세션의 인계 단절 = 시스템 무결성 손상.
|
||||||
|
|
||||||
|
정본 가이드: `D:\Note\20_Marketing_Team\23_Resource\AI_Rules\100_pm-system.md` + `101_pm-system-guide.md`
|
||||||
|
|
||||||
|
### 프로토콜 점검 필수 참조
|
||||||
|
|
||||||
|
프로토콜 전송 코드(패킷 빌드, 바이트 매핑, msgType, /P 페이지 등)를 수정할 때 **반드시** 아래 5개 문서를 검토한다:
|
||||||
|
|
||||||
|
| 문서 | 용도 |
|
||||||
|
|------|------|
|
||||||
|
| `docs/01_protocols/ascii-reference.md` | ASCII 명령어 전체 목록 + 속성코드 |
|
||||||
|
| `docs/01_protocols/hex-protocol.md` | HEX 프레임 구조 + DLE 이스케이프 |
|
||||||
|
| `docs/01_protocols/hex-reference.md` | HEX 명령별 실전 예시 |
|
||||||
|
| `docs/01_protocols/protocol-reference.md` | DIBD 완전 레퍼런스 (ASCII+HEX 통합) |
|
||||||
|
| `docs/01_protocols/protocol-schema.json` | AI 파싱용 구조화 스키마 |
|
||||||
|
|
||||||
|
### 콘텐츠 편집 필수 참조 — **🚨 강제 규칙**
|
||||||
|
|
||||||
|
콘텐츠 편집(`TextEditorWindow`, `DatFileService`, `LegacyDatHeader`, `ConvertToSplitDat` 등) 관련 코드를 **수정·추가·리팩토링**할 때, 작업 전에 **반드시** 아래 문서를 읽는다. 호환성 가정을 깨뜨리면 레거시 파일을 읽을 수 없게 되어 마이그레이션이 실패한다.
|
||||||
|
|
||||||
|
| 문서 | 용도 |
|
||||||
|
|------|------|
|
||||||
|
| `docs/00_AI_가이드/85_EDITOR_LEGACY_COMPAT.md` | 레거시 분할 DAT 호환성 현황 + 파싱 누락 지점 + 수정 제안 |
|
||||||
|
| `docs/01_protocols/dat-bgp-pla-textbook.md` | DAT/PLA/BGP 바이트 포맷 전체 정의 (분할 DAT 2-섹션 구조 포함) |
|
||||||
|
| `memory/project_split_block_format.md` | 분할 블록 펌웨어 검증 기록 (Option[0], BFormKind=2, dsrCnt 필수) |
|
||||||
|
|
||||||
|
**원칙**: 바이트 포맷 호환성은 이미 맞춰져 있으므로 깨지 않는다. 파싱 로직은 누락되어 있으므로 수정 시 이 문서의 수정 범위(P0/P1/P2)를 따른다.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- **Gitea 릴리즈 한국어 깨짐 방지** — Git Bash curl로 한국어 JSON 전송 시 인코딩 깨짐. 반드시 `pwsh.exe -NoProfile`에서 `ConvertTo-Json` + `[System.Text.Encoding]::UTF8.GetBytes()` + `Invoke-RestMethod` 조합 사용. curl 금지
|
||||||
|
- **ASCII 페이지 메시지 msgType은 항상 "1"** — 페이지 번호는 `/P{xx}` 속성(0-based)으로 지정. msgType에 페이지 번호를 넣으면 안됨
|
||||||
|
- **분할 DAT Section 2에 TextInfo 넣으면 XY 어긋남** — FCH(32B)만 기록, TextInfo(256B) 금지
|
||||||
|
- **BFormKind=1 대신 2 사용** — 펌웨어가 BFormKind=1을 다르게 처리하여 좌표 계산 오류 발생
|
||||||
|
- **분할 시 Option[0]=1 필수** — 누락 시 Y 5px 어긋남
|
||||||
|
- **분할 시 dsrCnt EDEffect 최소 0x01** — 0이면 컨트롤러가 분할을 인식 못함
|
||||||
|
- **레거시 분할 DAT의 Section1 FCH `RH`는 "메인 블록 폭"** — 전체 폭이 아님. H1F 샘플 파일의 `RH=16`인데 실제 전광판 폭은 96. `DatFileService.Load`가 현재 이를 전체 폭으로 오해하여 레거시 분할 파일을 못 읽음. 수정 전 반드시 `docs/00_AI_가이드/85_EDITOR_LEGACY_COMPAT.md` 참조
|
||||||
|
- **레거시 DAT 열기는 사이드카 기반** — `TextEditorWindow.OnLoadDat`는 `.blocks.json` 사이드카가 없으면 `BackgroundImagePath`에 경로만 할당하고 TextInfo 파싱을 건너뜀. 레거시 파일(사이드카 없음) 호환 작업 시 이 경로 수정 필요
|
||||||
|
- **레거시 BGP/PLA의 dat 참조는 bare 파일명** — V2.x는 `.bgp`(`Data\Playlist\`)와 `.dat`(`Data\Image\`)이 다른 폴더. `ScheduleFileService.ParseLegacyBinary`가 BGP 같은 폴더만 뒤지면 못 찾고 bare name(`"test_r.dat"`)으로 fallback → 전송 단계 `File.Exists` 실패 → 0블록 전송 → 컨트롤러 슬롯 모두 동일 메모리(블록 0) 가리킴 → "1~7번 모두 RED" 증상. 수정 후 해석 우선순위: **`AppPaths.ImageDir`/`VideoDir`(레거시 V2.x DestTextPath와 동일) → BGP 같은 폴더 → bare name**. `ScheduleTransferService.ResolveContentPath`가 전송 단계에서도 한번 더 구제(2026-05-07 fix)
|
||||||
|
- **펌웨어/폰트 청크 전송은 ACK 대기 필수 — Fire-and-forget 금지** — 시리얼/TCP에서 컨트롤러 처리 지연(플래시 쓰기 등)으로 일부 청크가 누락되면 침묵 회귀(전송 성공 같지만 미적용·한글 폰트 비트맵 손상). `FirmwareService` + `FontSettingsViewModel`의 청크 루프는 반드시 `AckPolicy.Required` + 청크별 3회 retry. timeout warning이 가끔 뜨더라도 **무결성 > 속도**. 정본: 67f1fe88(Fire-and-forget 도입)의 회귀 fix (2026-05-09 카메라 검증 — 한글 폰트가 색 채움 형태로 깨졌던 증상이 ACK 대기 복원으로 해결). 향후 속도 개선 시 청크 간 sleep 등 다른 방향으로 (ACK 대기는 유지)
|
||||||
|
- **FNT 폰트 글리프 시작 ASCII는 글리프 수로 분기** — `FntRenderer.RenderEngGlyph`는 `asciiCode * glyphSize`로 lookup하지만 폰트 파일별 시작 ASCII가 다름. 256/128 글리프 폰트(DABIT 표준, Gothic, Roman 8x16 등)는 ASCII 0부터, **95 글리프 폰트(ENG 16x16-ASCII-Roman/RN90, ENG 16x32-굴림체 등)는 ASCII 0x20(space)부터** 저장. 95 글리프는 `glyphIdx = asciiCode - 0x20` 보정 필요. 미리보기에서 "1" 입력 시 "Q"/"R" 같은 다른 글자가 나오면 이 보정 누락. 헤더 byte 12=W, byte 13=H, byte 14=glyphSize(B). 시작 ASCII는 헤더 명시 없어 글리프 수로 추정. 정본: FntRenderer 2026-05-10 fix
|
||||||
|
- **.NET 7+ SerialPort timeout은 IOException** — `SerialPort.ReadAsync` timeout이 .NET 6까지 `TimeoutException`이었으나 .NET 7부터 **`IOException`**으로 throw (GitHub dotnet/runtime#81035). 외부 catch가 `TimeoutException`만 잡으면 .NET 9 환경에서 회귀 — 시리얼 일시 timeout이 외부 retry 발동 안 함. transport retry 정책 작업 시 catch에 `IOException` + `SocketException` 추가 필수. `TransferSessionService.IsRetriable`은 이미 세 가지 다 잡으나, `FirmwareService` + `FontSettingsViewModel`의 청크 외부 retry는 좁아져 있을 수 있음. 정본: memory `reference_dotnet7_serialport_breaking`, 인계 doc `docs/00_AI_가이드/Decisions/2026-05-26-transport-retry-attempt-revert.md`
|
||||||
|
|
||||||
|
### Phase 1b Parser + VirtualControllerCore Gotchas (2026-04-21 v1.2 설계·플랜 반영)
|
||||||
|
|
||||||
|
- **Parser는 wire-faithful — text canonicalization 금지** — `PacketParser.TryParseDisplayMessagePayload`는 decode된 문자열을 **원문 그대로** 반환해야 한다. `\r`(0x0D) 제거 / `\n`→공백 변환 같은 canonicalization을 **parser 내부에서 절대 하지 말 것**. M07(CR/LF 원문 전송) Inv-1 byte stability를 보장하려면 wire-faithful 필수. Canonicalization은 `DisplayMessageSpecWireCanonicalizer.CanonicalizeForRoundTrip`에서만 수행
|
||||||
|
- **`CharColorCodec.Normalize`에 3-bit 마스킹 추가 금지** — Phase 1a `CharColorCodecTests`가 `{1, 2, 9, 9, 9}` 보존을 기대하므로 마스킹 추가 시 breaking. 3-bit (fg·bg ∈ [0, 7]) 정책은 **Phase 1b `DisplayMessageSpecWireCanonicalizer`에만** 적용. Phase 1a 정본 Codec은 건드리지 않는다
|
||||||
|
- **`VirtualControllerCore.ApplyHexFrame`에는 framed packet만 — payload 단독 입력 금지** — `ApplyHexFrame`은 `DleFramer.TryDeframe`을 경유하므로 입력은 `DLE STX DST LEN CMD DATA DLE ETX` 전체. payload(0x94 data)만 넣으면 `ApplyResult.MalformedFrame` 반환. payload만 있으면 `ApplyHexParsed(HexParsedResponse)`로 직접 진입할 것. 테스트 `FrameVsPayloadContractTests`가 계약 오사용을 검증
|
||||||
|
- **Empty-run canonical form 고정** — 빈 텍스트(M18, payload.Length == 16) payload는 `DefaultFg/DefaultBg/CharColors/FontGroup` 복원 불가. Parser와 `DisplayMessageSpecWireCanonicalizer` 모두 **`Text="", CharColors=null, DefaultFg=0, DefaultBg=0, FontGroup=Group0`** 으로 고정. `DisplayMessageSpecStructuralComparer`를 빈 텍스트 특례로 약화시키지 말 것
|
||||||
|
- **`VirtualControllerCore`는 Phase 1b에서 단일 스레드 전용** — 내부 `Dictionary`·`List` 기반이라 `Apply*`/`Snapshot`/`GetSection` concurrent 호출 시 레이스 → 동작 미정의. 호출자가 UI 스레드 or 전용 worker로 직렬화해야 한다. Phase 2+에서 concurrent 필요 시 `lock` 또는 `ConcurrentDictionary` 전환 검토. 구현 시 XML doc에 반드시 "Thread-safety: Phase 1b 단일 스레드 전용" 명시
|
||||||
|
- **`ControllerState.Sections`/`GetSection`은 deep-copy snapshot** — `ImmutableDictionary`/`ImmutableArray`는 컨테이너 불변성만 보장. `DisplayMessageSpec.Run.CharColors.Colors`가 가변 `int[]`이므로 `SectionManager.CloneSpec`에서 `Array.Copy`로 복제해야 내부 상태 aliasing 차단. Snapshot/GetSection 호출자는 반환값의 `NormalizedCharColors.Colors[0] = 999` 변경이 내부에 영향 주지 않음을 가정하고 설계. `SnapshotAliasMutationTests` 2건이 계약을 증명
|
||||||
|
|
||||||
|
## 커밋 시 문서 업데이트 규칙 — **🚨 강제 규칙**
|
||||||
|
|
||||||
|
### MANDATORY: 커밋 전 체크리스트
|
||||||
|
|
||||||
|
**커밋하기 직전에 반드시 다음 4가지를 확인한다. 하나라도 놓치면 커밋 금지.**
|
||||||
|
|
||||||
|
```
|
||||||
|
☐ 1. 프로토콜 바이트 구조 변경/발견이 있으면 → docs/01_protocols/*.md 업데이트
|
||||||
|
☐ 2. 레거시 대비 새 기능이면 → docs/00_AI_가이드/80_LEGACY_ENHANCEMENTS.md 업데이트
|
||||||
|
☐ 3. AI가 반복 실수한 패턴이면 → CLAUDE.md Gotchas 섹션 업데이트
|
||||||
|
☐ 4. 펌웨어 동작 검증 결과면 → memory/project_*.md 추가
|
||||||
|
```
|
||||||
|
|
||||||
|
### 발견 유형별 업데이트 대상
|
||||||
|
|
||||||
|
| 발견 유형 | 업데이트 대상 |
|
||||||
|
|----------|-------------|
|
||||||
|
| 프로토콜 바이트 구조, DAT/PLA/BGP/ANI 포맷 | `docs/01_protocols/dat-bgp-pla-textbook.md` |
|
||||||
|
| HEX/ASCII 명령 응답 포맷 | `docs/01_protocols/hex-reference.md`, `ascii-reference.md` |
|
||||||
|
| 레거시 대비 변경/확장 기능 | `docs/00_AI_가이드/80_LEGACY_ENHANCEMENTS.md` |
|
||||||
|
| AI가 반복한 실수 패턴 | 이 파일의 `Gotchas` 섹션 |
|
||||||
|
| 펌웨어 동작 검증 결과 | `memory/` (project 타입 메모리) |
|
||||||
|
| 새 ViewModel/Service 패턴 | `docs/00_AI_가이드/20_CODE_PATTERNS.md` |
|
||||||
|
|
||||||
|
### 강제 시행 절차
|
||||||
|
|
||||||
|
**커밋 프로세스:**
|
||||||
|
|
||||||
|
1. **코드 변경 완료** → `git diff`로 변경 내용 확인
|
||||||
|
2. **체크리스트 순회** → 4가지 업데이트 대상 중 해당하는 것 식별
|
||||||
|
3. **문서 업데이트** → 발견 사실을 해당 문서에 기록
|
||||||
|
4. **문서도 함께 스테이징** → `git add <code files> <doc files>`
|
||||||
|
5. **커밋 실행** → 코드+문서를 같은 커밋에 포함
|
||||||
|
|
||||||
|
### 위반 시 복구 절차
|
||||||
|
|
||||||
|
**이미 코드만 커밋했고 문서를 빼먹은 경우:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 직후 커밋으로 문서 추가
|
||||||
|
git add docs/
|
||||||
|
git commit -m "docs: [커밋해시] 후속 — {발견 내용} 반영"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why 이 규칙이 강력한가
|
||||||
|
|
||||||
|
- **반복 실수 방지**: 세션마다 레거시 바이트를 재분석하면 시간 낭비
|
||||||
|
- **다음 세션 컨텍스트**: 문서가 최신이어야 다른 세션/에이전트가 올바른 정보로 작업
|
||||||
|
- **장기 유지보수**: 6개월 후 이 코드를 볼 때 "왜 이렇게 했지?"에 답해야 함
|
||||||
|
- **검증 이력**: "컨트롤러 검증 완료 (날짜)" 표시로 회귀 방지
|
||||||
|
|
||||||
|
### 구체적 사례 (2026-04-10)
|
||||||
|
|
||||||
|
ANI 파일 포맷 수정 세션에서 놓친 것:
|
||||||
|
- ✗ FCDavHeader 16B (32B가 아님) — 레거시 소스에서 발견
|
||||||
|
- ✗ TextInfo가 프레임 데이터 뒤에 옴 (앞이 아님)
|
||||||
|
- ✗ BlockText[1] Y=16 하드코딩 — 레거시 TFormMessageText.cpp:1936
|
||||||
|
- ✗ 0x9B BGP 비디오 opcode — 문서에 미기재
|
||||||
|
- ✗ HEX SetScreenSize 응답 [0xF1, rows, cols] — 기존 문서 [status] 오기재
|
||||||
|
|
||||||
|
**교훈**: 레거시 소스(`references/DabitChe-V2.2.1/`)를 분석했다면 그 발견을 즉시 텍스트북에 반영해야 다음 세션에서 같은 분석을 반복하지 않는다.
|
||||||
46
Dabit.Mcp/Config/McpConnectionSettings.cs
Normal file
46
Dabit.Mcp/Config/McpConnectionSettings.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
using DibdProtocol.Transport;
|
||||||
|
|
||||||
|
namespace Dabit.Mcp.Config;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MCP 서버가 붙을 전광판 연결 설정. 환경변수(DABIT_*)에서 읽는다.
|
||||||
|
/// Claude Desktop이 claude_desktop_config.json의 env로 주입하는 것을 기본으로 한다
|
||||||
|
/// (서버 프로세스의 작업 디렉터리가 불확실하므로 파일보다 환경변수가 견고).
|
||||||
|
/// </summary>
|
||||||
|
public sealed record McpConnectionSettings
|
||||||
|
{
|
||||||
|
public TransportType ConnectType { get; init; } = TransportType.TcpClient;
|
||||||
|
public string PortName { get; init; } = "COM1";
|
||||||
|
public int BaudRate { get; init; } = 9600;
|
||||||
|
public string Host { get; init; } = "192.168.0.201";
|
||||||
|
public int Port { get; init; } = 5000;
|
||||||
|
public int ResponseLatencyMs { get; init; } = 1000;
|
||||||
|
|
||||||
|
/// <summary>환경변수에서 설정을 읽는다. 누락 항목은 기본값.</summary>
|
||||||
|
public static McpConnectionSettings FromEnvironment()
|
||||||
|
{
|
||||||
|
static string? Env(string key) => Environment.GetEnvironmentVariable(key);
|
||||||
|
|
||||||
|
var transport = (Env("DABIT_TRANSPORT") ?? "").Trim().ToLowerInvariant() switch
|
||||||
|
{
|
||||||
|
"serial" => TransportType.Serial,
|
||||||
|
"tcp" or "tcpclient" => TransportType.TcpClient,
|
||||||
|
_ => TransportType.TcpClient,
|
||||||
|
};
|
||||||
|
|
||||||
|
return new McpConnectionSettings
|
||||||
|
{
|
||||||
|
ConnectType = transport,
|
||||||
|
PortName = Env("DABIT_COM") ?? "COM1",
|
||||||
|
BaudRate = int.TryParse(Env("DABIT_BAUD"), out var b) ? b : 9600,
|
||||||
|
Host = Env("DABIT_HOST") ?? "192.168.0.201",
|
||||||
|
Port = int.TryParse(Env("DABIT_PORT"), out var p) ? p : 5000,
|
||||||
|
ResponseLatencyMs = int.TryParse(Env("DABIT_RESPONSE_LATENCY"), out var r) ? r : 1000,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>사람이 읽을 연결 요약 문자열.</summary>
|
||||||
|
public string Describe() => ConnectType == TransportType.Serial
|
||||||
|
? $"Serial {PortName} @ {BaudRate}bps (timeout {ResponseLatencyMs}ms)"
|
||||||
|
: $"TcpClient {Host}:{Port} (timeout {ResponseLatencyMs}ms)";
|
||||||
|
}
|
||||||
20
Dabit.Mcp/Dabit.Mcp.csproj
Normal file
20
Dabit.Mcp/Dabit.Mcp.csproj
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net9.0-windows</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ModelContextProtocol" Version="1.3.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
|
||||||
|
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DibdProtocol\DibdProtocol.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
40
Dabit.Mcp/Program.cs
Normal file
40
Dabit.Mcp/Program.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Dabit.Mcp.Config;
|
||||||
|
using Dabit.Mcp.Services;
|
||||||
|
using DibdProtocol.Transport;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
// EUC-KR 인코딩 등록 — PacketBuilder/PacketParser가 의존한다. 누락 시 런타임 예외.
|
||||||
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
|
|
||||||
|
var builder = Host.CreateApplicationBuilder(args);
|
||||||
|
|
||||||
|
// ★ stdio transport는 stdout을 JSON-RPC 채널로 쓴다.
|
||||||
|
// 기본 로깅 provider(Windows EventLog 포함)를 모두 제거한 뒤 콘솔 로그를 전부 stderr로 보낸다.
|
||||||
|
// EventLog는 표준 사용자 계정에서 '.NET Runtime' 소스 쓰기 실패로 예외를 던져 MCP 요청을 깨뜨릴 수 있다(codex 교차 리뷰 P2).
|
||||||
|
builder.Logging.ClearProviders();
|
||||||
|
builder.Logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace);
|
||||||
|
|
||||||
|
// 전광판 연결 설정 (환경변수 DABIT_*)
|
||||||
|
builder.Services.AddSingleton(McpConnectionSettings.FromEnvironment());
|
||||||
|
|
||||||
|
// Transport 레지스트리 — Serial + TcpClient 등록 (AutoSend과 동일 범위)
|
||||||
|
builder.Services.AddSingleton(_ =>
|
||||||
|
{
|
||||||
|
var registry = new TransportRegistry();
|
||||||
|
registry.Register(TransportType.Serial, new SerialTransport());
|
||||||
|
registry.Register(TransportType.TcpClient, new TcpClientTransport());
|
||||||
|
return registry;
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<LedControlService>();
|
||||||
|
|
||||||
|
// MCP 서버 — stdio transport + 어셈블리에서 [McpServerToolType] 도구 자동 검색
|
||||||
|
builder.Services
|
||||||
|
.AddMcpServer()
|
||||||
|
.WithStdioServerTransport()
|
||||||
|
.WithToolsFromAssembly();
|
||||||
|
|
||||||
|
await builder.Build().RunAsync();
|
||||||
130
Dabit.Mcp/README.md
Normal file
130
Dabit.Mcp/README.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# Dabit.Mcp — AI로 LED 전광판을 제어하는 MCP 서버
|
||||||
|
|
||||||
|
Claude Desktop 같은 AI 클라이언트가 자연어로 LED 전광판을 **읽고(상태 조회) + 표시하고(텍스트/색) + 설정하는(화면·밝기·시간·보드)** MCP(Model Context Protocol) 서버다.
|
||||||
|
|
||||||
|
> 예) "정문 전광판 밝기 50%로", "화면 빨강으로 채워", "시간 맞춰", "가나다라를 4색으로 왼쪽으로 흘려" → AI가 해당 도구를 호출 → 이 서버가 기존 검증된 `DibdProtocol` 코드로 바이트를 조립해 컨트롤러에 전송.
|
||||||
|
|
||||||
|
## 동작 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
[사람] → [Claude Desktop (AI)] ──MCP(stdio, JSON-RPC)──> [Dabit.Mcp.exe] ──> [전광판 컨트롤러]
|
||||||
|
(이 프로젝트) (COM/TCP)
|
||||||
|
```
|
||||||
|
|
||||||
|
- 새로 만든 건 이 서버(어댑터)뿐. 바이트 조립·통신은 `DibdProtocol.Core`(프로토콜) + `DibdProtocol.Transport`(시리얼/TCP)를 그대로 재사용한다.
|
||||||
|
- 각 도구의 조립은 정본 `DabitChe.Desktop/ViewModels/SettingsViewModel.cs`를 본떠 **byte-identical**.
|
||||||
|
- `LedControlService`는 `DabitChe.AutoSend`의 전송 패턴을 축약한 것(WPF 의존성 제거). **Phase 2에서 `DibdProtocol.Transport.Rpc.DibdRpcClient`(신 프로토콜 96 method)로 교체 예정인 폐기성 코드.**
|
||||||
|
|
||||||
|
## 도구 (20개)
|
||||||
|
|
||||||
|
### 읽기 / 상태
|
||||||
|
| 도구 | 설명 |
|
||||||
|
|------|------|
|
||||||
|
| `get_firmware_info` | 펌웨어 모델/버전/화면 크기 (HEX 0x6F) |
|
||||||
|
| `get_screen_size` | 화면 가로×세로 (펌웨어 응답에서 추출) |
|
||||||
|
| `get_time` | 컨트롤러 RTC 시각 (HEX 0x66, BCD 파싱) |
|
||||||
|
| `get_connection_status` | 설정된 연결 정보 — 통신 안 함(스모크용) |
|
||||||
|
|
||||||
|
### 콘텐츠 표시 ⚠️ 쓰기
|
||||||
|
| 도구 | 설명 |
|
||||||
|
|------|------|
|
||||||
|
| `display_text` | 단색 텍스트 한 줄 즉시 표시 (실시간 메시지) |
|
||||||
|
| `display_message` | 글자별 색 + 입장/퇴장 이동효과 + 속도/유지 |
|
||||||
|
|
||||||
|
### 화면 · 밝기 · 전원 ⚠️ 쓰기
|
||||||
|
| 도구 | 설명 |
|
||||||
|
|------|------|
|
||||||
|
| `set_screen_size` | 화면 크기(단/열) 설정 (HEX 0x40) |
|
||||||
|
| `fill_screen` | 전체 단색 채우기, 8색 (HEX 0x42) |
|
||||||
|
| `load_background_image` | 저장된 배경이미지 로드 (HEX 0x4F) |
|
||||||
|
| `set_brightness` | 밝기 0~4단계 (100/75/50/25/5%) (HEX 0x44) |
|
||||||
|
| `led_power_on` / `led_power_off` | LED 전원 (HEX 0x41) |
|
||||||
|
|
||||||
|
### 페이지 메시지 ⚠️ 쓰기
|
||||||
|
| 도구 | 설명 |
|
||||||
|
|------|------|
|
||||||
|
| `set_page_count` | 페이지 개수 1~10 (ASCII "60") |
|
||||||
|
| `reset_page_message` | 페이지 초기화 (전체/개별) (ASCII "61") |
|
||||||
|
| `set_section_effect` | 섹션 동시/개별 표출 (ASCII "62") |
|
||||||
|
|
||||||
|
### 표출신호 (HUB75) ⚠️ 쓰기
|
||||||
|
| 도구 | 설명 |
|
||||||
|
|------|------|
|
||||||
|
| `set_display_signal` | 대표 신호 7종 이름으로 설정 (HEX 0x49) |
|
||||||
|
| `send_custom_signal` | 사용자 신호 코드 직접 전송 (ASCII "44") |
|
||||||
|
|
||||||
|
### 시간 · 보드 설정 (고급)
|
||||||
|
| 도구 | 종류 | 설명 |
|
||||||
|
|------|------|------|
|
||||||
|
| `sync_controller_time` | ⚠️ 쓰기 | PC 시간으로 RTC 동기화 (HEX 0x47, 7B BCD) |
|
||||||
|
| `read_board_settings` | 읽기 | 디버그/포트기능/통신속도 (ASCII "B3") |
|
||||||
|
| `save_board_settings` | ⚠️ 쓰기 | 보드기능 저장 (ASCII "B2") |
|
||||||
|
|
||||||
|
> **제외(블록 전송)**: 폰트(0x98)·펌웨어(0x9F)·이미지(DAT/BGP/PLA)·스케줄. 대용량 다블록 전송이라 PoC 범위 밖.
|
||||||
|
|
||||||
|
## 빌드 · 실행
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
dotnet build Dabit.Mcp/Dabit.Mcp.csproj
|
||||||
|
dotnet run --project Dabit.Mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Claude Desktop 연결
|
||||||
|
|
||||||
|
`claude_desktop_config.json`의 `mcpServers`에 등록한다(아래는 실증된 Serial COM2 설정):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"dabit-led": {
|
||||||
|
"command": "D:\\Gitea\\dabitche-mcp\\Dabit.Mcp\\bin\\Debug\\net9.0-windows\\Dabit.Mcp.exe",
|
||||||
|
"env": {
|
||||||
|
"DABIT_TRANSPORT": "Serial",
|
||||||
|
"DABIT_COM": "COM2",
|
||||||
|
"DABIT_BAUD": "115200"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
(개발 중엔 `command`를 `dotnet` + `args:["run","--project","...Dabit.Mcp"]`로 둘 수도 있으나, exe 직접 실행이 빠르고 안정적.)
|
||||||
|
|
||||||
|
### 환경변수 (연결 설정)
|
||||||
|
|
||||||
|
| 변수 | 의미 | 기본값 |
|
||||||
|
|------|------|--------|
|
||||||
|
| `DABIT_TRANSPORT` | `TcpClient` 또는 `Serial` | `TcpClient` |
|
||||||
|
| `DABIT_HOST` / `DABIT_PORT` | TCP 호스트 / 포트 | `192.168.0.201` / `5000` |
|
||||||
|
| `DABIT_COM` / `DABIT_BAUD` | 시리얼 포트 / 속도 | `COM1` / `9600` |
|
||||||
|
| `DABIT_RESPONSE_LATENCY` | 응답 타임아웃(ms) | `1000` |
|
||||||
|
|
||||||
|
## 제약 · 주의
|
||||||
|
|
||||||
|
- ⚠️ **COM 포트 동시 점유 불가** — WPF 앱(DabitChe.Desktop/AutoSend)과 같은 COM 포트를 동시에 못 연다. 시리얼 사용 시 **WPF 앱을 끄고** MCP 서버를 실행한다.
|
||||||
|
- ⚠️ **로그는 반드시 stderr** — stdio는 stdout을 JSON-RPC에 쓴다. `Program.cs`에서 `ClearProviders()` + 콘솔 로그를 stderr로 보낸다(Windows EventLog 로거 제거 포함).
|
||||||
|
- ⚠️ **요청 순서 비보장** — MCP 서버가 여러 요청을 병렬 처리한다. `LedControlService._gate`가 전송은 한 번에 하나로 직렬화하지만 **진입 순서는 보장 안 함**. 한 번에 여러 명령을 몰아 보내면(예: sync 직후 get_time) 실행 순서가 뒤바뀔 수 있다. 순서 의존 작업은 응답을 받고 다음을 보낸다.
|
||||||
|
- **FlashWrite 명령**(시간·보드·화면크기·페이지·밝기·표출신호·fill·배경)은 응답이 느려 `timeoutMsOverride: 5000` 적용.
|
||||||
|
- ✅ **scroll 4방향(left/right/up/down) 실기 검증 완료**(2026-06-02, DIBD600T V3.6.1). 🟡 미검증: `bpp` 색심도 의미, 밝기 index1 ASCII(75)≠HEX(0x48) 레거시값.
|
||||||
|
|
||||||
|
## 로드맵
|
||||||
|
|
||||||
|
| Phase | 내용 |
|
||||||
|
|-------|------|
|
||||||
|
| **0 (완료)** | 읽기 4 + 콘텐츠 2 + 설정 14 = 20 도구. stdio. 얇은 LedControlService |
|
||||||
|
| 1 | 밝기 고급(13B 시간대별/CDS)·컨트롤러 재시작/초기화·기타 고급(스케줄속도/깜빡임/잔상/릴레이) |
|
||||||
|
| 2 | 백엔드를 `DibdRpcClient`(JSON-RPC 96 method)로 승격 — 신 프로토콜 실기 검증 후 |
|
||||||
|
| 3 | HTTP/SSE transport + HMAC 인증 → 원격/B2B |
|
||||||
|
|
||||||
|
## 검증 상태 (2026-06-01)
|
||||||
|
|
||||||
|
- ✅ 빌드(경고 0/오류 0), stdio 스모크(20개 도구 노출, tools/call 왕복, 환경변수 로딩, stdout 청결성)
|
||||||
|
- ✅ 이중 코드 리뷰 반영 — Anthropic `code-reviewer`(byte 조립 정합·죽은코드·timeout) + OpenAI `codex`(HEX status 체크: 거부 응답을 성공으로 오보하던 것 수정)
|
||||||
|
- ✅ **실기 검증** (DIBD500S 펌웨어 V3.4.8, COM2 @ 115200):
|
||||||
|
- 읽기: `get_firmware_info`, `get_time`(BCD), `read_board_settings`(7필드)
|
||||||
|
- 표시: `display_text`(환영합니다 빨강), `display_message`(가나다라 4색 좌이동)
|
||||||
|
- 설정: `set_brightness`(50%), `fill_screen`(빨강), `sync_controller_time`(2026 적용 확인) — 모두 status 0x00
|
||||||
|
- ✅ **실기 검증 추가** (2026-06-02, DIBD600T 펌웨어 V3.6.1, COM2 @ 115200):
|
||||||
|
- scroll 4방향 전부 정상: `scroll_left`(우→좌) · `scroll_right`(좌→우) · `scroll_up`(아래→위) · `scroll_down`(위→아래) — 육안 확인
|
||||||
|
- `display_message` 글자별 색(`colors`) 정상 — colors를 쉼표 구분 문자열로 변경해 배열 바인딩 버그 수정
|
||||||
|
- 🟡 미검증: bpp 색심도, save_board_settings 실기 반영
|
||||||
156
Dabit.Mcp/Services/LedControlService.cs
Normal file
156
Dabit.Mcp/Services/LedControlService.cs
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
using Dabit.Mcp.Config;
|
||||||
|
using DibdProtocol.Protocol;
|
||||||
|
using DibdProtocol.Transport;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Dabit.Mcp.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 전광판과의 송수신을 담당하는 얇은 서비스.
|
||||||
|
/// DabitChe.AutoSend.Services.MessageService를 축약했다(WPF 의존성 제거).
|
||||||
|
/// PoC 폐기성 코드 — Phase 2에서 DibdProtocol.Transport.Rpc.DibdRpcClient(96 method)로 대체 예정.
|
||||||
|
///
|
||||||
|
/// 동시성: MCP 서버는 요청을 직렬화하지 않으므로 _gate(SemaphoreSlim)로 명령을 한 번에 하나씩 처리한다.
|
||||||
|
/// 연결: KeepOpen=true로 프로세스 수명 동안 포트를 유지하고 DisposeAsync에서 1회만 닫는다.
|
||||||
|
/// timeout: FlashWrite류 명령(시간/보드/화면크기/페이지 설정 등)은 응답이 느리므로 호출자가
|
||||||
|
/// timeoutMsOverride로 5초 등을 지정한다(기본은 설정값 ResponseLatencyMs).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class LedControlService : IAsyncDisposable
|
||||||
|
{
|
||||||
|
private readonly TransportRegistry _registry;
|
||||||
|
private readonly McpConnectionSettings _settings;
|
||||||
|
private readonly ILogger<LedControlService> _log;
|
||||||
|
private readonly SemaphoreSlim _gate = new(1, 1);
|
||||||
|
|
||||||
|
public LedControlService(TransportRegistry registry, McpConnectionSettings settings, ILogger<LedControlService> log)
|
||||||
|
{
|
||||||
|
_registry = registry;
|
||||||
|
_settings = settings;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public McpConnectionSettings Settings => _settings;
|
||||||
|
|
||||||
|
private async Task<ITransport> EnsureConnectedAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
_registry.CurrentType = _settings.ConnectType;
|
||||||
|
var transport = _registry.GetCurrent();
|
||||||
|
if (transport.IsConnected)
|
||||||
|
return transport;
|
||||||
|
|
||||||
|
var info = new ConnectionInfo
|
||||||
|
{
|
||||||
|
PortName = _settings.PortName,
|
||||||
|
BaudRate = _settings.BaudRate,
|
||||||
|
Host = _settings.Host,
|
||||||
|
Port = _settings.Port,
|
||||||
|
ResponseLatencyMs = _settings.ResponseLatencyMs,
|
||||||
|
KeepOpen = true,
|
||||||
|
};
|
||||||
|
_log.LogDebug("연결 시도: {Conn}", _settings.Describe());
|
||||||
|
await transport.ConnectAsync(info, ct);
|
||||||
|
return transport;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TimeSpan Timeout(int? overrideMs) => TimeSpan.FromMilliseconds(overrideMs ?? _settings.ResponseLatencyMs);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HEX 쓰기 명령 응답의 status를 판정한다(정본 MessageService.DescribeHexResponse 기준).
|
||||||
|
/// SetScreenSize(0x40)는 Data[0]=0xF1 echo가 정상, 나머지 쓰기는 Data[0]=0x00이 성공.
|
||||||
|
/// </summary>
|
||||||
|
public static (bool Ok, string Message) CheckWriteStatus(HexCommand cmd, HexParsedResponse? resp)
|
||||||
|
{
|
||||||
|
if (resp is null) return (false, "응답 없음 — 연결 또는 통신 오류.");
|
||||||
|
if (resp.Data.Length == 0) return (false, "빈 응답.");
|
||||||
|
var b = resp.Data[0];
|
||||||
|
var ok = cmd == HexCommand.SetScreenSize ? b == 0xF1 : b == 0x00;
|
||||||
|
if (ok) return (true, "성공");
|
||||||
|
var reason = b switch
|
||||||
|
{
|
||||||
|
0x10 => "명령 없음(No Command)",
|
||||||
|
0x20 => "기능 없음(No Function)",
|
||||||
|
0x40 => "데이터 오류(Data Error)",
|
||||||
|
0x80 => "알 수 없는 오류",
|
||||||
|
_ => $"상태 0x{b:X2}",
|
||||||
|
};
|
||||||
|
return (false, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>HEX 명령 송신 후 응답 파싱. 연결/통신 실패는 null로 정규화한다.</summary>
|
||||||
|
public async Task<HexParsedResponse?> SendHexAsync(HexCommand cmd, byte[] data, CancellationToken ct = default, int? timeoutMsOverride = null)
|
||||||
|
{
|
||||||
|
await _gate.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var transport = await EnsureConnectedAsync(ct);
|
||||||
|
var packet = PacketBuilder.BuildHex(cmd, data);
|
||||||
|
_log.LogInformation("TX HEX cmd=0x{Cmd:X2} ({Len}B)", (byte)cmd, data.Length);
|
||||||
|
var response = await transport.SendAndReceiveAsync(packet, Timeout(timeoutMsOverride), ct);
|
||||||
|
var parsed = PacketParser.ParseHex(response);
|
||||||
|
_log.LogInformation("RX HEX {Result}", parsed is null ? "파싱 실패" : $"cmd=0x{(byte)parsed.Cmd:X2} {parsed.Data.Length}B");
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "HEX 전송 실패 cmd=0x{Cmd:X2}", (byte)cmd);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
finally { _gate.Release(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>ASCII 명령 송신 후 응답 파싱. 연결/통신 실패는 null로 정규화한다.</summary>
|
||||||
|
public async Task<AsciiParsedResponse?> SendAsciiAsync(string msgType, string data, CancellationToken ct = default, int? timeoutMsOverride = null)
|
||||||
|
{
|
||||||
|
await _gate.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var transport = await EnsureConnectedAsync(ct);
|
||||||
|
var packet = PacketBuilder.BuildAscii(msgType, data);
|
||||||
|
_log.LogInformation("TX ASCII msgType={MsgType} data=\"{Data}\"", msgType, data);
|
||||||
|
var response = await transport.SendAndReceiveAsync(packet, Timeout(timeoutMsOverride), ct);
|
||||||
|
var parsed = PacketParser.ParseAscii(response);
|
||||||
|
_log.LogInformation("RX ASCII {Result}", parsed is null ? "파싱 실패" : $"result={parsed.Result} data=\"{parsed.Data}\"");
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "ASCII 전송 실패 msgType={MsgType}", msgType);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
finally { _gate.Release(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>ASCII 혼합(속성 EUC-KR + 텍스트 raw bytes) 송신. 연결/통신 실패는 null.</summary>
|
||||||
|
public async Task<AsciiParsedResponse?> SendAsciiMixedAsync(string msgType, string attributes, byte[] textBytes, CancellationToken ct = default, int? timeoutMsOverride = null)
|
||||||
|
{
|
||||||
|
await _gate.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var transport = await EnsureConnectedAsync(ct);
|
||||||
|
var packet = PacketBuilder.BuildAsciiMixed(msgType, attributes, textBytes);
|
||||||
|
_log.LogInformation("TX ASCII-mixed msgType={MsgType} attrs=\"{Attrs}\" text={Len}B", msgType, attributes, textBytes.Length);
|
||||||
|
var response = await transport.SendAndReceiveAsync(packet, Timeout(timeoutMsOverride), ct);
|
||||||
|
var parsed = PacketParser.ParseAscii(response);
|
||||||
|
_log.LogInformation("RX ASCII {Result}", parsed is null ? "파싱 실패" : $"result={parsed.Result} data=\"{parsed.Data}\"");
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "ASCII-mixed 전송 실패 msgType={MsgType}", msgType);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
finally { _gate.Release(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var transport = _registry.GetCurrent();
|
||||||
|
if (transport.IsConnected)
|
||||||
|
await transport.DisconnectAsync();
|
||||||
|
}
|
||||||
|
catch { /* 종료 시 닫기 실패 무시 */ }
|
||||||
|
_gate.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
54
Dabit.Mcp/Tools/BoardTools.cs
Normal file
54
Dabit.Mcp/Tools/BoardTools.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using Dabit.Mcp.Services;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
namespace Dabit.Mcp.Tools;
|
||||||
|
|
||||||
|
/// <summary>보드기능설정 읽기/저장 도구(디버그·포트기능·통신속도). ⚠️ 저장은 쓰기.</summary>
|
||||||
|
[McpServerToolType]
|
||||||
|
public sealed class BoardTools
|
||||||
|
{
|
||||||
|
private readonly LedControlService _led;
|
||||||
|
private readonly ILogger<BoardTools> _log;
|
||||||
|
|
||||||
|
public BoardTools(LedControlService led, ILogger<BoardTools> log)
|
||||||
|
{
|
||||||
|
_led = led;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("보드기능설정을 읽는다(디버그/BH1·J4 기능/J2·J3·BH1 통신속도 인덱스). 읽기 — 표시 변경 없음.")]
|
||||||
|
public async Task<string> ReadBoardSettings(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var resp = await _led.SendAsciiAsync("B3", "0", ct, 5000);
|
||||||
|
if (resp is null)
|
||||||
|
return "보드설정 읽기 실패 — 연결 또는 통신 오류.";
|
||||||
|
var f = resp.Data.Trim().Split(',');
|
||||||
|
if (f.Length < 6)
|
||||||
|
return $"보드설정 응답 형식이 예상과 다릅니다: \"{resp.Data}\"";
|
||||||
|
var rs = f.Length >= 7 ? $", RS485주소={f[6]}" : "";
|
||||||
|
return $"보드설정 — debug={f[0]}, BH1기능={f[1]}, J4기능={f[2]}, J2속도={f[3]}, J3속도={f[4]}, BH1속도={f[5]}{rs}";
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("보드기능설정을 저장한다. ⚠️ 쓰기·설정 저장. 범위: debug 0~15, 나머지 0~7. 잘못 설정하면 통신이 끊길 수 있으니 주의.")]
|
||||||
|
public async Task<string> SaveBoardSettings(
|
||||||
|
[Description("디버그 레벨 0~15")] int debug,
|
||||||
|
[Description("BH1 포트 기능 0~7")] int bh1Func,
|
||||||
|
[Description("J4 포트 기능 0~7")] int j4Func,
|
||||||
|
[Description("J2 통신속도 인덱스 0~7")] int j2Baud,
|
||||||
|
[Description("J3 통신속도 인덱스 0~7")] int j3Baud,
|
||||||
|
[Description("BH1 통신속도 인덱스 0~7")] int bh1Baud,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 정본 B2: data 맨 앞 공백 1개 + 6필드. debug만 HEX 대문자, 나머지 10진수.
|
||||||
|
var hexDebug = Math.Clamp(debug, 0, 15).ToString("X");
|
||||||
|
var data = $" {hexDebug},{Math.Clamp(bh1Func, 0, 7)},{Math.Clamp(j4Func, 0, 7)},{Math.Clamp(j2Baud, 0, 7)},{Math.Clamp(j3Baud, 0, 7)},{Math.Clamp(bh1Baud, 0, 7)}";
|
||||||
|
var resp = await _led.SendAsciiAsync("B2", data, ct, 5000);
|
||||||
|
if (resp is null)
|
||||||
|
return "보드설정 저장 실패 — 연결 또는 통신 오류.";
|
||||||
|
return resp.Result == "F" ? "전광판이 거부했습니다 (Result=F)." : "보드설정 저장 완료.";
|
||||||
|
}
|
||||||
|
}
|
||||||
59
Dabit.Mcp/Tools/BrightnessPowerTools.cs
Normal file
59
Dabit.Mcp/Tools/BrightnessPowerTools.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using Dabit.Mcp.Services;
|
||||||
|
using DibdProtocol.Protocol;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
namespace Dabit.Mcp.Tools;
|
||||||
|
|
||||||
|
/// <summary>밝기·전원 설정 도구. ⚠️ 쓰기.</summary>
|
||||||
|
[McpServerToolType]
|
||||||
|
public sealed class BrightnessPowerTools
|
||||||
|
{
|
||||||
|
private readonly LedControlService _led;
|
||||||
|
private readonly ILogger<BrightnessPowerTools> _log;
|
||||||
|
|
||||||
|
public BrightnessPowerTools(LedControlService led, ILogger<BrightnessPowerTools> log)
|
||||||
|
{
|
||||||
|
_led = led;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 밝기 단계 0~4 → (표시%, HEX byte). 정본 SettingsViewModel 매핑 그대로(byte-identical).
|
||||||
|
private static readonly (int Pct, byte Hex)[] BrightnessMap =
|
||||||
|
{
|
||||||
|
(100, 0x64), (75, 0x48), (50, 0x32), (25, 0x19), (5, 0x05),
|
||||||
|
};
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("전광판 밝기를 설정한다. ⚠️ 쓰기·설정 저장. level 0~4: 0=100%, 1=75%, 2=50%, 3=25%, 4=5%.")]
|
||||||
|
public async Task<string> SetBrightness(
|
||||||
|
[Description("밝기 단계 0~4 (0=100% 1=75% 2=50% 3=25% 4=5%)")] int level,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (level < 0 || level > 4)
|
||||||
|
return "밝기 단계는 0~4여야 합니다 (0=100% 1=75% 2=50% 3=25% 4=5%).";
|
||||||
|
var (pct, hex) = BrightnessMap[level];
|
||||||
|
var resp = await _led.SendHexAsync(HexCommand.SetBrightness, new[] { hex }, ct, 5000);
|
||||||
|
var (ok, msg) = LedControlService.CheckWriteStatus(HexCommand.SetBrightness, resp);
|
||||||
|
return ok ? $"밝기 {pct}% 설정." : $"밝기 설정 실패 — {msg}";
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("전광판 LED 전원을 켠다. ⚠️ 쓰기.")]
|
||||||
|
public async Task<string> LedPowerOn(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var resp = await _led.SendHexAsync(HexCommand.LedPowerControl, new byte[] { 0x01 }, ct);
|
||||||
|
var (ok, msg) = LedControlService.CheckWriteStatus(HexCommand.LedPowerControl, resp);
|
||||||
|
return ok ? "LED 전원 켜짐." : $"전원 켜기 실패 — {msg}";
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("전광판 LED 전원을 끈다. ⚠️ 쓰기 — 화면이 꺼진다.")]
|
||||||
|
public async Task<string> LedPowerOff(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var resp = await _led.SendHexAsync(HexCommand.LedPowerControl, new byte[] { 0x00 }, ct);
|
||||||
|
var (ok, msg) = LedControlService.CheckWriteStatus(HexCommand.LedPowerControl, resp);
|
||||||
|
return ok ? "LED 전원 꺼짐." : $"전원 끄기 실패 — {msg}";
|
||||||
|
}
|
||||||
|
}
|
||||||
163
Dabit.Mcp/Tools/DisplayControlTools.cs
Normal file
163
Dabit.Mcp/Tools/DisplayControlTools.cs
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Text;
|
||||||
|
using Dabit.Mcp.Services;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
namespace Dabit.Mcp.Tools;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 전광판 표시를 바꾸는 도구. ⚠️ 쓰기 — 실제 장비의 표시 내용을 변경한다.
|
||||||
|
/// </summary>
|
||||||
|
[McpServerToolType]
|
||||||
|
public sealed class DisplayControlTools
|
||||||
|
{
|
||||||
|
private readonly LedControlService _led;
|
||||||
|
private readonly ILogger<DisplayControlTools> _log;
|
||||||
|
|
||||||
|
public DisplayControlTools(LedControlService led, ILogger<DisplayControlTools> log)
|
||||||
|
{
|
||||||
|
_led = led;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>color 문자열 → ASCII /C 색상 코드(0~7). 8색 고정(정본 ascii-reference).</summary>
|
||||||
|
private static readonly Dictionary<string, string> ColorCodes = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["black"] = "0", ["red"] = "1", ["green"] = "2", ["yellow"] = "3",
|
||||||
|
["blue"] = "4", ["magenta"] = "5", ["cyan"] = "6", ["white"] = "7",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// effect 문자열 → /E 효과 코드(2자리). 입장·퇴장에 각각 적용한다(/E{입장}{퇴장}).
|
||||||
|
/// stop(01)·scroll 4방향(06~09) 모두 실기 검증됨(2026-06-02, DIBD600T 펌웨어 V3.6.1, 육안 확인).
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Dictionary<string, string> EffectCodes = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["stop"] = "01", // 정지 — 검증
|
||||||
|
["scroll_left"] = "06", // 좌이동(오른쪽→왼쪽) — 검증
|
||||||
|
["scroll_right"] = "07", // 우이동(왼쪽→오른쪽) — 검증(2026-06-02)
|
||||||
|
["scroll_up"] = "08", // 상향(아래→위) — 검증(2026-06-02)
|
||||||
|
["scroll_down"] = "09", // 하향(위→아래) — 검증(2026-06-02)
|
||||||
|
};
|
||||||
|
|
||||||
|
private const int MaxTextLength = 100;
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("전광판에 단색 텍스트 한 줄을 즉시 표시한다(실시간 메시지, 정지). " +
|
||||||
|
"⚠️ 쓰기 동작 — 현재 표시 중인 실시간 메시지를 즉시 덮어쓴다. " +
|
||||||
|
"지원 색상 8종: red, green, yellow, blue, magenta, cyan, white, black (그 외는 노랑으로 근사). " +
|
||||||
|
"글자별 색이나 이동 효과가 필요하면 display_message를 쓴다.")]
|
||||||
|
public async Task<string> DisplayText(
|
||||||
|
[Description("표시할 텍스트(한글 가능, 최대 100자). 예: \"환영합니다\"")] string text,
|
||||||
|
[Description("문자 색상. red/green/yellow/blue/magenta/cyan/white/black 중 하나. 기본 yellow")] string color = "yellow",
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(text))
|
||||||
|
return "표시할 텍스트가 비어 있습니다.";
|
||||||
|
if (!ValidateText(text, out var err))
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (!ColorCodes.TryGetValue(color.Trim(), out var code))
|
||||||
|
{
|
||||||
|
_log.LogWarning("지원하지 않는 색상 '{Color}' → 노랑(yellow)으로 근사", color);
|
||||||
|
code = "3";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 실시간 메시지(msgType "0") + 색상/정지(즉시)/속도유지/EUC-KR 16x16. (실기 검증된 형식)
|
||||||
|
var data = $"/C{code}/E0101/S2002/F0003{text}";
|
||||||
|
_log.LogInformation("display_text: \"{Text}\" color={Color}(/C{Code})", text, color, code);
|
||||||
|
|
||||||
|
return Format(await _led.SendAsciiAsync("0", data, ct), $"\"{text}\" ({color})");
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("전광판에 텍스트를 글자별 색상·입장/퇴장 이동효과·속도와 함께 표시한다(실시간 메시지). " +
|
||||||
|
"⚠️ 쓰기 동작 — 현재 표시 중인 실시간 메시지를 즉시 덮어쓴다. " +
|
||||||
|
"colors: 글자별 색을 쉼표로 구분(text 글자 수와 1:1, 예 \"red,green,yellow,blue\", 생략 시 전체 노랑). " +
|
||||||
|
"effect=입장 효과, exit_effect=퇴장 효과: stop/scroll_left/scroll_right/scroll_up/scroll_down. " +
|
||||||
|
"연속으로 흐르게 하려면 입장·퇴장을 같은 scroll로 준다(예: 둘 다 scroll_left). " +
|
||||||
|
"speed 0~99(작을수록 빠름, 0=최고속). hold_sec 유지 시간(초). " +
|
||||||
|
"scroll 4방향(left/right/up/down) 모두 실기 검증 완료(2026-06-02, DIBD600T V3.6.1).")]
|
||||||
|
public async Task<string> DisplayMessage(
|
||||||
|
[Description("표시할 텍스트(한글 가능). 예: \"가나다라\"")] string text,
|
||||||
|
[Description("글자별 색을 쉼표로 구분한 문자열, text 글자와 1:1 대응. 예: \"red,green,yellow,blue\". 생략 시 전체 노랑")] string? colors = null,
|
||||||
|
[Description("입장 효과: stop/scroll_left/scroll_right/scroll_up/scroll_down. 기본 stop")] string effect = "stop",
|
||||||
|
[Description("퇴장 효과: 위와 동일 목록. 입장과 같은 scroll을 주면 멈춤 없이 연속으로 흐른다. 기본 stop")] string exitEffect = "stop",
|
||||||
|
[Description("이동 속도 0~99(작을수록 빠름, 0=최고속). 기본 20")] int speed = 20,
|
||||||
|
[Description("유지 시간(초), 0=대기 없음. 기본 1")] int holdSec = 1,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(text))
|
||||||
|
return "표시할 텍스트가 비어 있습니다.";
|
||||||
|
if (!ValidateText(text, out var err))
|
||||||
|
return err;
|
||||||
|
|
||||||
|
var enter = ResolveEffect(effect);
|
||||||
|
var exit = ResolveEffect(exitEffect);
|
||||||
|
var sp = Math.Clamp(speed, 0, 99);
|
||||||
|
var hold2 = Math.Clamp(holdSec * 2, 0, 99); // 유지시간은 0.5초 단위(1초=02)
|
||||||
|
|
||||||
|
// colors는 쉼표 구분 문자열("red,green,..."). ModelContextProtocol 1.3.0이 string[]? 파라미터의
|
||||||
|
// JSON 스키마를 깨뜨려(type 누락) 배열 인자 바인딩 시 호출 자체가 실패하므로, 문자열로 받아 직접 분리한다.
|
||||||
|
var colorArr = string.IsNullOrWhiteSpace(colors)
|
||||||
|
? null
|
||||||
|
: colors.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
// 속성(입장/퇴장 효과·속도·폰트) 먼저, 그 뒤에 글자별 색을 인라인으로 붙인다.
|
||||||
|
var sb = new StringBuilder($"/E{enter}{exit}/S{sp:D2}{hold2:D2}/F0003");
|
||||||
|
for (int i = 0; i < text.Length; i++)
|
||||||
|
{
|
||||||
|
var code = "3"; // 기본 노랑
|
||||||
|
var want = colorArr is not null && i < colorArr.Length ? colorArr[i] : null;
|
||||||
|
if (want is not null && ColorCodes.TryGetValue(want.Trim(), out var cc))
|
||||||
|
code = cc;
|
||||||
|
else if (want is not null)
|
||||||
|
_log.LogWarning("지원하지 않는 색상 '{Color}'(글자 {Idx}) → 노랑", want, i);
|
||||||
|
sb.Append($"/C{code}{text[i]}");
|
||||||
|
}
|
||||||
|
var data = sb.ToString();
|
||||||
|
_log.LogInformation("display_message: 입장={Enter} 퇴장={Exit} speed={Speed} hold={Hold}s data=\"{Data}\"", effect, exitEffect, sp, holdSec, data);
|
||||||
|
|
||||||
|
return Format(await _led.SendAsciiAsync("0", data, ct), $"\"{text}\" (입장 {effect}, 퇴장 {exitEffect}, 속도 {sp}, 유지 {holdSec}s)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>effect 문자열 → /E 코드. 모르는 값은 정지(01)로.</summary>
|
||||||
|
private string ResolveEffect(string name)
|
||||||
|
{
|
||||||
|
if (EffectCodes.TryGetValue(name.Trim(), out var c))
|
||||||
|
return c;
|
||||||
|
_log.LogWarning("알 수 없는 효과 '{Effect}' → stop", name);
|
||||||
|
return "01";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>본문에 ASCII 속성/프레임 제어코드가 섞이면 표시가 깨지거나 속성이 주입된다. 차단.</summary>
|
||||||
|
private bool ValidateText(string text, out string error)
|
||||||
|
{
|
||||||
|
if (text.Contains('/') || text.Contains("![") || text.Contains("!]"))
|
||||||
|
{
|
||||||
|
error = "텍스트에 제어 문자('/', '![', '!]')는 사용할 수 없습니다 — 속성 코드로 해석되어 표시가 깨집니다.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (text.Length > MaxTextLength)
|
||||||
|
{
|
||||||
|
error = $"텍스트가 너무 깁니다(최대 {MaxTextLength}자, 입력 {text.Length}자).";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
error = "";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>응답을 사용자 메시지로 변환. 성공 판정은 Result=="0" allowlist.</summary>
|
||||||
|
private static string Format(DibdProtocol.Protocol.AsciiParsedResponse? resp, string what)
|
||||||
|
{
|
||||||
|
if (resp is null)
|
||||||
|
return "전송 실패 — 연결 또는 통신 오류 (전광판 응답 없음).";
|
||||||
|
return resp.Result switch
|
||||||
|
{
|
||||||
|
"0" => $"표시 완료: {what}.",
|
||||||
|
"F" => "전광판이 명령을 거부했습니다 (Result=F).",
|
||||||
|
_ => $"전송했으나 응답 형식이 예상과 다릅니다 (Result=\"{resp.Result}\") — 실기 확인이 필요합니다.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
72
Dabit.Mcp/Tools/DisplayStatusTools.cs
Normal file
72
Dabit.Mcp/Tools/DisplayStatusTools.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Dabit.Mcp.Services;
|
||||||
|
using DibdProtocol.Protocol;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
namespace Dabit.Mcp.Tools;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 전광판 상태를 읽는 도구 모음. 모두 읽기 전용 — 표시 내용을 바꾸지 않는다.
|
||||||
|
/// </summary>
|
||||||
|
[McpServerToolType]
|
||||||
|
public sealed class DisplayStatusTools
|
||||||
|
{
|
||||||
|
private readonly LedControlService _led;
|
||||||
|
|
||||||
|
public DisplayStatusTools(LedControlService led)
|
||||||
|
{
|
||||||
|
_led = led;
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("연결된 LED 전광판의 펌웨어 정보(모델/버전/현재 화면 크기 등)를 읽는다. 표시 내용은 변경하지 않는다.")]
|
||||||
|
public async Task<string> GetFirmwareInfo(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var resp = await _led.SendHexAsync(HexCommand.ReadBoardInfo, [0xF1], ct);
|
||||||
|
if (resp is null || resp.Data.Length == 0)
|
||||||
|
return "펌웨어 정보를 읽지 못했습니다 (응답 없음 또는 연결 실패).";
|
||||||
|
|
||||||
|
// 응답 data[0] == 0xF1 (요청 마커 에코)이면 건너뛴다 (FirmwareService 선례)
|
||||||
|
var start = (resp.Data.Length > 1 && resp.Data[0] == 0xF1) ? 1 : 0;
|
||||||
|
var text = Encoding.ASCII.GetString(resp.Data, start, resp.Data.Length - start).TrimEnd('\0', ' ');
|
||||||
|
return string.IsNullOrWhiteSpace(text) ? "펌웨어 정보가 비어 있습니다." : text;
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("전광판의 현재 화면 크기(가로 x 세로 픽셀)를 읽는다. 펌웨어 정보 응답에서 추출한다.")]
|
||||||
|
public async Task<string> GetScreenSize(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var info = await GetFirmwareInfo(ct);
|
||||||
|
// 펌웨어 문자열에서 "96x320" 같은 패턴 추출 (실기 검증 후 정밀화)
|
||||||
|
var m = Regex.Match(info, @"(\d+)\s*[xX]\s*(\d{2,4})");
|
||||||
|
return m.Success
|
||||||
|
? $"화면 크기: 가로 {m.Groups[1].Value} x 세로 {m.Groups[2].Value} 픽셀"
|
||||||
|
: $"화면 크기를 펌웨어 응답에서 찾지 못했습니다. 원문: {info}";
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("전광판 컨트롤러의 현재 시각(RTC)을 읽는다.")]
|
||||||
|
public async Task<string> GetTime(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var resp = await _led.SendHexAsync(HexCommand.ReadTime, [0x00], ct);
|
||||||
|
if (resp is null || resp.Data.Length == 0)
|
||||||
|
return "시간을 읽지 못했습니다 (응답 없음 또는 연결 실패).";
|
||||||
|
|
||||||
|
// 응답 7바이트 BCD: [yy, MM, dd, dow, HH, mm, ss] (sync_controller_time과 동일 레이아웃)
|
||||||
|
var d = resp.Data;
|
||||||
|
if (d.Length < 7)
|
||||||
|
return $"시간 응답이 예상보다 짧습니다 (raw {Convert.ToHexString(d)}).";
|
||||||
|
static int FromBcd(byte b) => (b >> 4) * 10 + (b & 0x0F);
|
||||||
|
var year = 2000 + FromBcd(d[0]);
|
||||||
|
return $"컨트롤러 시각: {year}-{FromBcd(d[1]):D2}-{FromBcd(d[2]):D2} {FromBcd(d[4]):D2}:{FromBcd(d[5]):D2}:{FromBcd(d[6]):D2}";
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("현재 MCP 서버에 설정된 전광판 연결 정보를 반환한다. 실제 통신은 하지 않는다(연결 점검·스모크용).")]
|
||||||
|
public string GetConnectionStatus()
|
||||||
|
{
|
||||||
|
return $"전광판 연결 설정 — {_led.Settings.Describe()}";
|
||||||
|
}
|
||||||
|
}
|
||||||
62
Dabit.Mcp/Tools/PageTools.cs
Normal file
62
Dabit.Mcp/Tools/PageTools.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using Dabit.Mcp.Services;
|
||||||
|
using DibdProtocol.Protocol;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
namespace Dabit.Mcp.Tools;
|
||||||
|
|
||||||
|
/// <summary>페이지 메시지 개수·초기화·섹션 효과 도구. ⚠️ 쓰기.</summary>
|
||||||
|
[McpServerToolType]
|
||||||
|
public sealed class PageTools
|
||||||
|
{
|
||||||
|
private readonly LedControlService _led;
|
||||||
|
private readonly ILogger<PageTools> _log;
|
||||||
|
|
||||||
|
public PageTools(LedControlService led, ILogger<PageTools> log)
|
||||||
|
{
|
||||||
|
_led = led;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("페이지 메시지 개수를 설정한다. ⚠️ 쓰기·설정 저장. count 1~10.")]
|
||||||
|
public async Task<string> SetPageCount(
|
||||||
|
[Description("페이지 수 1~10")] int count,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (count < 1 || count > 10)
|
||||||
|
return "페이지 수는 1~10이어야 합니다.";
|
||||||
|
var resp = await _led.SendAsciiAsync("60", count.ToString("D2"), ct, 5000);
|
||||||
|
return Format(resp, $"페이지 개수 {count}개 설정.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("페이지 메시지를 초기화(삭제)한다. ⚠️ 쓰기. page: 0=전체 삭제, 1~10=개별 페이지.")]
|
||||||
|
public async Task<string> ResetPageMessage(
|
||||||
|
[Description("0=전체 삭제, 1~10=개별 페이지")] int page,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (page < 0 || page > 10)
|
||||||
|
return "page는 0(전체)~10이어야 합니다.";
|
||||||
|
// 정본: 전체→"99", 개별 N(1~10)→(N-1) zero-pad 2자리
|
||||||
|
var data = page == 0 ? "99" : (page - 1).ToString("D2");
|
||||||
|
var resp = await _led.SendAsciiAsync("61", data, ct, 5000);
|
||||||
|
return Format(resp, page == 0 ? "전체 페이지 초기화." : $"{page}번 페이지 초기화.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("섹션별 표출 효과 모드를 설정한다. ⚠️ 쓰기. mode: 0=동시표출, 1=개별표출.")]
|
||||||
|
public async Task<string> SetSectionEffect(
|
||||||
|
[Description("0=동시표출, 1=개별표출")] int mode,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 정본: N=동시표출, Y=개별표출
|
||||||
|
var data = mode == 0 ? "N" : "Y";
|
||||||
|
var resp = await _led.SendAsciiAsync("62", data, ct, 5000);
|
||||||
|
return Format(resp, mode == 0 ? "섹션 동시표출 설정." : "섹션 개별표출 설정.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Format(DibdProtocol.Protocol.AsciiParsedResponse? r, string ok)
|
||||||
|
=> r is null ? "전송 실패 — 연결 또는 통신 오류." : (r.Result == "F" ? "전광판이 거부했습니다 (Result=F)." : ok);
|
||||||
|
}
|
||||||
76
Dabit.Mcp/Tools/ScreenTools.cs
Normal file
76
Dabit.Mcp/Tools/ScreenTools.cs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using Dabit.Mcp.Services;
|
||||||
|
using DibdProtocol.Protocol;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
namespace Dabit.Mcp.Tools;
|
||||||
|
|
||||||
|
/// <summary>화면 크기·채우기·배경이미지 설정 도구. ⚠️ 쓰기.</summary>
|
||||||
|
[McpServerToolType]
|
||||||
|
public sealed class ScreenTools
|
||||||
|
{
|
||||||
|
private readonly LedControlService _led;
|
||||||
|
private readonly ILogger<ScreenTools> _log;
|
||||||
|
|
||||||
|
public ScreenTools(LedControlService led, ILogger<ScreenTools> log)
|
||||||
|
{
|
||||||
|
_led = led;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 색 인덱스 0~7 → RGB332 colorByte (정본 SettingsViewModel, R[2:0]G[5:3]B[7:6])
|
||||||
|
private static readonly byte[] FillColors = { 0x00, 0x07, 0x38, 0x3F, 0xC0, 0xC7, 0xF8, 0xFF };
|
||||||
|
private static readonly string[] ColorNames = { "검정", "빨강", "초록", "노랑", "파랑", "분홍", "청록", "하양" };
|
||||||
|
|
||||||
|
// bpp 입력(0/1/2) → payload byte (정본: 0→0x02, 2→0x08, 그 외→0x03=3bpp 8색 기본)
|
||||||
|
private static byte BppByte(int bpp) => bpp switch { 0 => 0x02, 2 => 0x08, _ => 0x03 };
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("전광판 화면 크기(단/열)를 설정한다. ⚠️ 쓰기·설정 저장. layout은 모듈 배열(0~5).")]
|
||||||
|
public async Task<string> SetScreenSize(
|
||||||
|
[Description("단(행) 수 1~16")] int rows,
|
||||||
|
[Description("열 수 1~32")] int cols,
|
||||||
|
[Description("색심도 0/1/2 (기본 1=3bpp 8색)")] int bpp = 1,
|
||||||
|
[Description("배열 0~5 (기본 0)")] int layout = 0,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
byte[] payload = { BppByte(bpp), (byte)Math.Clamp(rows, 1, 16), (byte)Math.Clamp(cols, 1, 32), (byte)Math.Clamp(layout, 0, 5), 0x00, 0xF1 };
|
||||||
|
var resp = await _led.SendHexAsync(HexCommand.SetScreenSize, payload, ct, 5000);
|
||||||
|
var (ok, msg) = LedControlService.CheckWriteStatus(HexCommand.SetScreenSize, resp);
|
||||||
|
if (!ok) return $"화면 크기 설정 실패 — {msg}";
|
||||||
|
// 응답 [0xF1, rows, cols] — 실제 적용된 값
|
||||||
|
return resp!.Data.Length >= 3
|
||||||
|
? $"화면 크기 설정됨: {resp.Data[1]}단 x {resp.Data[2]}열."
|
||||||
|
: "화면 크기 설정됨.";
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("전광판 전체를 단색으로 채운다. ⚠️ 쓰기 — 현재 화면을 덮어쓴다. color 0~7: 0검정 1빨강 2초록 3노랑 4파랑 5분홍 6청록 7하양.")]
|
||||||
|
public async Task<string> FillScreen(
|
||||||
|
[Description("색 인덱스 0~7 (0검정 1빨강 2초록 3노랑 4파랑 5분홍 6청록 7하양)")] int color,
|
||||||
|
[Description("색심도 0/1/2 (기본 1)")] int bpp = 1,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (color < 0 || color > 7)
|
||||||
|
return "색 인덱스는 0~7이어야 합니다 (0검정 1빨강 2초록 3노랑 4파랑 5분홍 6청록 7하양).";
|
||||||
|
byte[] payload = { BppByte(bpp), FillColors[color], 0x00, 0x00, 0x00 };
|
||||||
|
var resp = await _led.SendHexAsync(HexCommand.FillScreen, payload, ct, 5000);
|
||||||
|
var (ok, msg) = LedControlService.CheckWriteStatus(HexCommand.FillScreen, resp);
|
||||||
|
return ok ? $"화면 채움: {ColorNames[color]}." : $"화면 채우기 실패 — {msg}";
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("저장된 배경이미지를 불러와 표시한다. ⚠️ 쓰기. num 0~255 (0=사용 안 함).")]
|
||||||
|
public async Task<string> LoadBackgroundImage(
|
||||||
|
[Description("이미지 번호 0~255 (0=사용 안 함)")] int num,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (num < 0 || num > 255)
|
||||||
|
return "이미지 번호는 0~255여야 합니다.";
|
||||||
|
var resp = await _led.SendHexAsync(HexCommand.LoadBackgroundImage, new[] { (byte)num }, ct, 5000);
|
||||||
|
var (ok, msg) = LedControlService.CheckWriteStatus(HexCommand.LoadBackgroundImage, resp);
|
||||||
|
if (!ok) return $"배경이미지 로드 실패 — {msg}";
|
||||||
|
return num == 0 ? "배경이미지 사용 안 함으로 설정." : $"배경이미지 {num}번 로드.";
|
||||||
|
}
|
||||||
|
}
|
||||||
69
Dabit.Mcp/Tools/SignalTools.cs
Normal file
69
Dabit.Mcp/Tools/SignalTools.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using Dabit.Mcp.Services;
|
||||||
|
using DibdProtocol.Protocol;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
namespace Dabit.Mcp.Tools;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 표출신호(HUB75 패턴) 설정 도구. ⚠️ 쓰기 — F16 영역(70+ 모델 의존).
|
||||||
|
/// 대표 신호만 이름으로 지원하고, 그 외는 send_custom_signal로 raw 코드 전송.
|
||||||
|
/// </summary>
|
||||||
|
[McpServerToolType]
|
||||||
|
public sealed class SignalTools
|
||||||
|
{
|
||||||
|
private readonly LedControlService _led;
|
||||||
|
private readonly ILogger<SignalTools> _log;
|
||||||
|
|
||||||
|
public SignalTools(LedControlService led, ILogger<SignalTools> log)
|
||||||
|
{
|
||||||
|
_led = led;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 대표 표출신호 → HexData 4바이트 (정본 DisplaySignalData 일부). 전체 70+는 send_custom_signal로.
|
||||||
|
private static readonly Dictionary<string, byte[]> SignalHexData = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["16D-P16D1S11"] = new byte[] { 0x00, 0x10, 0x01, 0x11 },
|
||||||
|
["16D-P16D1S10-1"] = new byte[] { 0x00, 0x10, 0x01, 0x01 },
|
||||||
|
["08D-P32D1S11"] = new byte[] { 0x00, 0x08, 0x01, 0x11 },
|
||||||
|
["08D-P64D1S21"] = new byte[] { 0x00, 0x08, 0x01, 0x21 },
|
||||||
|
["04D-P32D2S11"] = new byte[] { 0x00, 0x04, 0x02, 0x11 },
|
||||||
|
["04D-P64D1S11"] = new byte[] { 0x00, 0x04, 0x01, 0x11 },
|
||||||
|
["32D-P16D1S11"] = new byte[] { 0x00, 0x20, 0x01, 0x11 },
|
||||||
|
};
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("전광판 표출신호(HUB75 패턴)를 이름으로 설정한다. ⚠️ 쓰기 — 잘못 설정하면 화면이 깨진다. " +
|
||||||
|
"지원: 16D-P16D1S11, 16D-P16D1S10-1, 08D-P32D1S11, 08D-P64D1S21, 04D-P32D2S11, 04D-P64D1S11, 32D-P16D1S11. " +
|
||||||
|
"목록에 없으면 send_custom_signal을 쓴다. (08D-P64D1S21은 138 IC 스캔 고정)")]
|
||||||
|
public async Task<string> SetDisplaySignal(
|
||||||
|
[Description("신호 이름. 예: \"16D-P16D1S11\"")] string signalName,
|
||||||
|
[Description("색 순서 0~6 (기본 0)")] int colorOrder = 0,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (!SignalHexData.TryGetValue(signalName.Trim(), out var hd))
|
||||||
|
return $"지원하지 않는 신호 '{signalName}'. 지원 목록: {string.Join(", ", SignalHexData.Keys)}. 또는 send_custom_signal 사용.";
|
||||||
|
|
||||||
|
var colorHex = (byte)(Math.Clamp(colorOrder, 0, 6) + 1); // 정본: idx+1
|
||||||
|
byte[] payload = { hd[0], hd[1], hd[2], hd[3], colorHex, 0x01 }; // scanHex 기본 0x01(138 IC)
|
||||||
|
var resp = await _led.SendHexAsync(HexCommand.SetHub75Pattern, payload, ct, 5000);
|
||||||
|
var (ok, msg) = LedControlService.CheckWriteStatus(HexCommand.SetHub75Pattern, resp);
|
||||||
|
return ok ? $"표출신호 '{signalName}' 설정 (color={colorOrder})." : $"표출신호 설정 실패 — {msg}";
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("사용자 지정 표출신호 코드를 직접 전송한다(고급). ⚠️ 쓰기 — 코드 형식을 정확히 알 때만 사용. 잘못된 값은 화면을 깨뜨릴 수 있다.")]
|
||||||
|
public async Task<string> SendCustomSignal(
|
||||||
|
[Description("신호 코드 문자열. 예: \"8121\"")] string code,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(code) || code.Contains('/') || code.Contains("![") || code.Contains("!]"))
|
||||||
|
return "유효한 신호 코드가 아닙니다 ('/', '![', '!]' 불가).";
|
||||||
|
// 정본: ASCII msgType "44", data 맨 앞 공백 1개 필수
|
||||||
|
var resp = await _led.SendAsciiAsync("44", " " + code.Trim(), ct, 5000);
|
||||||
|
if (resp is null) return "전송 실패 — 연결 또는 통신 오류.";
|
||||||
|
return resp.Result == "F" ? "전광판이 거부했습니다 (Result=F)." : $"사용자 표출신호 '{code}' 전송.";
|
||||||
|
}
|
||||||
|
}
|
||||||
50
Dabit.Mcp/Tools/TimeTools.cs
Normal file
50
Dabit.Mcp/Tools/TimeTools.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using Dabit.Mcp.Services;
|
||||||
|
using DibdProtocol.Protocol;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
namespace Dabit.Mcp.Tools;
|
||||||
|
|
||||||
|
/// <summary>컨트롤러 시각 동기화 도구. ⚠️ 쓰기.</summary>
|
||||||
|
[McpServerToolType]
|
||||||
|
public sealed class TimeTools
|
||||||
|
{
|
||||||
|
private readonly LedControlService _led;
|
||||||
|
private readonly ILogger<TimeTools> _log;
|
||||||
|
|
||||||
|
public TimeTools(LedControlService led, ILogger<TimeTools> log)
|
||||||
|
{
|
||||||
|
_led = led;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 정본 SettingsViewModel: ToBcd(v) = (v/10*16)+(v%10). 26 → 0x26.
|
||||||
|
private static byte ToBcd(int v) => (byte)(v / 10 * 16 + v % 10);
|
||||||
|
|
||||||
|
private static string KoreanDow(DayOfWeek d) => d switch
|
||||||
|
{
|
||||||
|
DayOfWeek.Sunday => "일", DayOfWeek.Monday => "월", DayOfWeek.Tuesday => "화",
|
||||||
|
DayOfWeek.Wednesday => "수", DayOfWeek.Thursday => "목", DayOfWeek.Friday => "금",
|
||||||
|
_ => "토",
|
||||||
|
};
|
||||||
|
|
||||||
|
[McpServerTool]
|
||||||
|
[Description("컨트롤러의 시각(RTC)을 현재 PC 시간으로 동기화한다. ⚠️ 쓰기·설정 저장.")]
|
||||||
|
public async Task<string> SyncControllerTime(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var now = DateTime.Now;
|
||||||
|
// 정본 0x47: [yy(BCD), MM(BCD), dd(BCD), dow(raw 0=일~6=토), HH(BCD), mm(BCD), ss(BCD)]
|
||||||
|
byte[] payload =
|
||||||
|
{
|
||||||
|
ToBcd(now.Year % 100), ToBcd(now.Month), ToBcd(now.Day),
|
||||||
|
(byte)(int)now.DayOfWeek,
|
||||||
|
ToBcd(now.Hour), ToBcd(now.Minute), ToBcd(now.Second),
|
||||||
|
};
|
||||||
|
var resp = await _led.SendHexAsync(HexCommand.SyncTime, payload, ct, 5000);
|
||||||
|
var (ok, msg) = LedControlService.CheckWriteStatus(HexCommand.SyncTime, resp);
|
||||||
|
return ok
|
||||||
|
? $"시간 동기화 완료: {now:yyyy-MM-dd HH:mm:ss} ({KoreanDow(now.DayOfWeek)})."
|
||||||
|
: $"시간 동기화 실패 — {msg}";
|
||||||
|
}
|
||||||
|
}
|
||||||
1181
Dabit.Mcp/bin/Debug/net9.0-windows/Dabit.Mcp.deps.json
Normal file
1181
Dabit.Mcp/bin/Debug/net9.0-windows/Dabit.Mcp.deps.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/Dabit.Mcp.dll
Normal file
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/Dabit.Mcp.dll
Normal file
Binary file not shown.
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/Dabit.Mcp.exe
Normal file
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/Dabit.Mcp.exe
Normal file
Binary file not shown.
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/Dabit.Mcp.pdb
Normal file
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/Dabit.Mcp.pdb
Normal file
Binary file not shown.
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"runtimeOptions": {
|
||||||
|
"tfm": "net9.0",
|
||||||
|
"framework": {
|
||||||
|
"name": "Microsoft.NETCore.App",
|
||||||
|
"version": "9.0.0"
|
||||||
|
},
|
||||||
|
"configProperties": {
|
||||||
|
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false,
|
||||||
|
"CSWINRT_USE_WINDOWS_UI_XAML_PROJECTIONS": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/DibdProtocol.Core.dll
Normal file
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/DibdProtocol.Core.dll
Normal file
Binary file not shown.
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/DibdProtocol.Core.pdb
Normal file
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/DibdProtocol.Core.pdb
Normal file
Binary file not shown.
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/DibdProtocol.Transport.dll
Normal file
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/DibdProtocol.Transport.dll
Normal file
Binary file not shown.
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/DibdProtocol.Transport.pdb
Normal file
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/DibdProtocol.Transport.pdb
Normal file
Binary file not shown.
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/DibdProtocol.dll
Normal file
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/DibdProtocol.dll
Normal file
Binary file not shown.
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/DibdProtocol.pdb
Normal file
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/DibdProtocol.pdb
Normal file
Binary file not shown.
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/MQTTnet.dll
Normal file
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/MQTTnet.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/ModelContextProtocol.Core.dll
Normal file
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/ModelContextProtocol.Core.dll
Normal file
Binary file not shown.
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/ModelContextProtocol.dll
Normal file
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/ModelContextProtocol.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/System.IO.Pipelines.dll
Normal file
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/System.IO.Pipelines.dll
Normal file
Binary file not shown.
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/System.IO.Ports.dll
Normal file
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/System.IO.Ports.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/System.Text.Encodings.Web.dll
Normal file
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/System.Text.Encodings.Web.dll
Normal file
Binary file not shown.
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/System.Text.Json.dll
Normal file
BIN
Dabit.Mcp/bin/Debug/net9.0-windows/System.Text.Json.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
293
Dabit.Mcp/obj/Dabit.Mcp.csproj.nuget.dgspec.json
Normal file
293
Dabit.Mcp/obj/Dabit.Mcp.csproj.nuget.dgspec.json
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
{
|
||||||
|
"format": 1,
|
||||||
|
"restore": {
|
||||||
|
"D:\\Gitea\\dabitche-mcp\\Dabit.Mcp\\Dabit.Mcp.csproj": {}
|
||||||
|
},
|
||||||
|
"projects": {
|
||||||
|
"D:\\Gitea\\dabitche-mcp\\Dabit.Mcp\\Dabit.Mcp.csproj": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"restore": {
|
||||||
|
"projectUniqueName": "D:\\Gitea\\dabitche-mcp\\Dabit.Mcp\\Dabit.Mcp.csproj",
|
||||||
|
"projectName": "Dabit.Mcp",
|
||||||
|
"projectPath": "D:\\Gitea\\dabitche-mcp\\Dabit.Mcp\\Dabit.Mcp.csproj",
|
||||||
|
"packagesPath": "C:\\Users\\INSU LEE\\.nuget\\packages\\",
|
||||||
|
"outputPath": "D:\\Gitea\\dabitche-mcp\\Dabit.Mcp\\obj\\",
|
||||||
|
"projectStyle": "PackageReference",
|
||||||
|
"configFilePaths": [
|
||||||
|
"C:\\Users\\INSU LEE\\AppData\\Roaming\\NuGet\\NuGet.Config"
|
||||||
|
],
|
||||||
|
"originalTargetFrameworks": [
|
||||||
|
"net9.0-windows"
|
||||||
|
],
|
||||||
|
"sources": {
|
||||||
|
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||||
|
"https://api.nuget.org/v3/index.json": {}
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net9.0-windows7.0": {
|
||||||
|
"targetAlias": "net9.0-windows",
|
||||||
|
"projectReferences": {
|
||||||
|
"D:\\Gitea\\dabitche-mcp\\DibdProtocol\\DibdProtocol.csproj": {
|
||||||
|
"projectPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol\\DibdProtocol.csproj"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"warningProperties": {
|
||||||
|
"warnAsError": [
|
||||||
|
"NU1605"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"restoreAuditProperties": {
|
||||||
|
"enableAudit": "true",
|
||||||
|
"auditLevel": "low",
|
||||||
|
"auditMode": "direct"
|
||||||
|
},
|
||||||
|
"SdkAnalysisLevel": "9.0.300"
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net9.0-windows7.0": {
|
||||||
|
"targetAlias": "net9.0-windows",
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Extensions.Hosting": {
|
||||||
|
"target": "Package",
|
||||||
|
"version": "[9.0.0, )"
|
||||||
|
},
|
||||||
|
"ModelContextProtocol": {
|
||||||
|
"target": "Package",
|
||||||
|
"version": "[1.3.0, )"
|
||||||
|
},
|
||||||
|
"System.Text.Encoding.CodePages": {
|
||||||
|
"target": "Package",
|
||||||
|
"version": "[10.0.5, )"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imports": [
|
||||||
|
"net461",
|
||||||
|
"net462",
|
||||||
|
"net47",
|
||||||
|
"net471",
|
||||||
|
"net472",
|
||||||
|
"net48",
|
||||||
|
"net481"
|
||||||
|
],
|
||||||
|
"assetTargetFallback": true,
|
||||||
|
"warn": true,
|
||||||
|
"frameworkReferences": {
|
||||||
|
"Microsoft.NETCore.App": {
|
||||||
|
"privateAssets": "all"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.314/PortableRuntimeIdentifierGraph.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"D:\\Gitea\\dabitche-mcp\\DibdProtocol.Core\\DibdProtocol.Core.csproj": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"restore": {
|
||||||
|
"projectUniqueName": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Core\\DibdProtocol.Core.csproj",
|
||||||
|
"projectName": "DibdProtocol.Core",
|
||||||
|
"projectPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Core\\DibdProtocol.Core.csproj",
|
||||||
|
"packagesPath": "C:\\Users\\INSU LEE\\.nuget\\packages\\",
|
||||||
|
"outputPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Core\\obj\\",
|
||||||
|
"projectStyle": "PackageReference",
|
||||||
|
"configFilePaths": [
|
||||||
|
"C:\\Users\\INSU LEE\\AppData\\Roaming\\NuGet\\NuGet.Config"
|
||||||
|
],
|
||||||
|
"originalTargetFrameworks": [
|
||||||
|
"net9.0"
|
||||||
|
],
|
||||||
|
"sources": {
|
||||||
|
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||||
|
"https://api.nuget.org/v3/index.json": {}
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net9.0": {
|
||||||
|
"targetAlias": "net9.0",
|
||||||
|
"projectReferences": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"warningProperties": {
|
||||||
|
"warnAsError": [
|
||||||
|
"NU1605"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"restoreAuditProperties": {
|
||||||
|
"enableAudit": "true",
|
||||||
|
"auditLevel": "low",
|
||||||
|
"auditMode": "direct"
|
||||||
|
},
|
||||||
|
"SdkAnalysisLevel": "9.0.300"
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net9.0": {
|
||||||
|
"targetAlias": "net9.0",
|
||||||
|
"dependencies": {
|
||||||
|
"System.Text.Encoding.CodePages": {
|
||||||
|
"target": "Package",
|
||||||
|
"version": "[10.0.5, )"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imports": [
|
||||||
|
"net461",
|
||||||
|
"net462",
|
||||||
|
"net47",
|
||||||
|
"net471",
|
||||||
|
"net472",
|
||||||
|
"net48",
|
||||||
|
"net481"
|
||||||
|
],
|
||||||
|
"assetTargetFallback": true,
|
||||||
|
"warn": true,
|
||||||
|
"frameworkReferences": {
|
||||||
|
"Microsoft.NETCore.App": {
|
||||||
|
"privateAssets": "all"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.314/PortableRuntimeIdentifierGraph.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"D:\\Gitea\\dabitche-mcp\\DibdProtocol.Transport\\DibdProtocol.Transport.csproj": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"restore": {
|
||||||
|
"projectUniqueName": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Transport\\DibdProtocol.Transport.csproj",
|
||||||
|
"projectName": "DibdProtocol.Transport",
|
||||||
|
"projectPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Transport\\DibdProtocol.Transport.csproj",
|
||||||
|
"packagesPath": "C:\\Users\\INSU LEE\\.nuget\\packages\\",
|
||||||
|
"outputPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Transport\\obj\\",
|
||||||
|
"projectStyle": "PackageReference",
|
||||||
|
"configFilePaths": [
|
||||||
|
"C:\\Users\\INSU LEE\\AppData\\Roaming\\NuGet\\NuGet.Config"
|
||||||
|
],
|
||||||
|
"originalTargetFrameworks": [
|
||||||
|
"net9.0"
|
||||||
|
],
|
||||||
|
"sources": {
|
||||||
|
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||||
|
"https://api.nuget.org/v3/index.json": {}
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net9.0": {
|
||||||
|
"targetAlias": "net9.0",
|
||||||
|
"projectReferences": {
|
||||||
|
"D:\\Gitea\\dabitche-mcp\\DibdProtocol.Core\\DibdProtocol.Core.csproj": {
|
||||||
|
"projectPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Core\\DibdProtocol.Core.csproj"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"warningProperties": {
|
||||||
|
"warnAsError": [
|
||||||
|
"NU1605"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"restoreAuditProperties": {
|
||||||
|
"enableAudit": "true",
|
||||||
|
"auditLevel": "low",
|
||||||
|
"auditMode": "direct"
|
||||||
|
},
|
||||||
|
"SdkAnalysisLevel": "9.0.300"
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net9.0": {
|
||||||
|
"targetAlias": "net9.0",
|
||||||
|
"dependencies": {
|
||||||
|
"MQTTnet": {
|
||||||
|
"target": "Package",
|
||||||
|
"version": "[4.3.7.1207, )"
|
||||||
|
},
|
||||||
|
"System.IO.Ports": {
|
||||||
|
"target": "Package",
|
||||||
|
"version": "[9.0.5, )"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imports": [
|
||||||
|
"net461",
|
||||||
|
"net462",
|
||||||
|
"net47",
|
||||||
|
"net471",
|
||||||
|
"net472",
|
||||||
|
"net48",
|
||||||
|
"net481"
|
||||||
|
],
|
||||||
|
"assetTargetFallback": true,
|
||||||
|
"warn": true,
|
||||||
|
"frameworkReferences": {
|
||||||
|
"Microsoft.NETCore.App": {
|
||||||
|
"privateAssets": "all"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.314/PortableRuntimeIdentifierGraph.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"D:\\Gitea\\dabitche-mcp\\DibdProtocol\\DibdProtocol.csproj": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"restore": {
|
||||||
|
"projectUniqueName": "D:\\Gitea\\dabitche-mcp\\DibdProtocol\\DibdProtocol.csproj",
|
||||||
|
"projectName": "DibdProtocol",
|
||||||
|
"projectPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol\\DibdProtocol.csproj",
|
||||||
|
"packagesPath": "C:\\Users\\INSU LEE\\.nuget\\packages\\",
|
||||||
|
"outputPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol\\obj\\",
|
||||||
|
"projectStyle": "PackageReference",
|
||||||
|
"configFilePaths": [
|
||||||
|
"C:\\Users\\INSU LEE\\AppData\\Roaming\\NuGet\\NuGet.Config"
|
||||||
|
],
|
||||||
|
"originalTargetFrameworks": [
|
||||||
|
"net9.0"
|
||||||
|
],
|
||||||
|
"sources": {
|
||||||
|
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||||
|
"https://api.nuget.org/v3/index.json": {}
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net9.0": {
|
||||||
|
"targetAlias": "net9.0",
|
||||||
|
"projectReferences": {
|
||||||
|
"D:\\Gitea\\dabitche-mcp\\DibdProtocol.Core\\DibdProtocol.Core.csproj": {
|
||||||
|
"projectPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Core\\DibdProtocol.Core.csproj"
|
||||||
|
},
|
||||||
|
"D:\\Gitea\\dabitche-mcp\\DibdProtocol.Transport\\DibdProtocol.Transport.csproj": {
|
||||||
|
"projectPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Transport\\DibdProtocol.Transport.csproj"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"warningProperties": {
|
||||||
|
"warnAsError": [
|
||||||
|
"NU1605"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"restoreAuditProperties": {
|
||||||
|
"enableAudit": "true",
|
||||||
|
"auditLevel": "low",
|
||||||
|
"auditMode": "direct"
|
||||||
|
},
|
||||||
|
"SdkAnalysisLevel": "9.0.300"
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net9.0": {
|
||||||
|
"targetAlias": "net9.0",
|
||||||
|
"imports": [
|
||||||
|
"net461",
|
||||||
|
"net462",
|
||||||
|
"net47",
|
||||||
|
"net471",
|
||||||
|
"net472",
|
||||||
|
"net48",
|
||||||
|
"net481"
|
||||||
|
],
|
||||||
|
"assetTargetFallback": true,
|
||||||
|
"warn": true,
|
||||||
|
"frameworkReferences": {
|
||||||
|
"Microsoft.NETCore.App": {
|
||||||
|
"privateAssets": "all"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.314/PortableRuntimeIdentifierGraph.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Dabit.Mcp/obj/Dabit.Mcp.csproj.nuget.g.props
Normal file
18
Dabit.Mcp/obj/Dabit.Mcp.csproj.nuget.g.props
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||||
|
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||||
|
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||||
|
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||||
|
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||||
|
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||||
|
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\INSU LEE\.nuget\packages\</NuGetPackageFolders>
|
||||||
|
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||||
|
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.14.3</NuGetToolVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||||
|
<SourceRoot Include="C:\Users\INSU LEE\.nuget\packages\" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||||
|
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets\9.0.0\buildTransitive\net8.0\Microsoft.Extensions.Configuration.UserSecrets.props" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets\9.0.0\buildTransitive\net8.0\Microsoft.Extensions.Configuration.UserSecrets.props')" />
|
||||||
|
</ImportGroup>
|
||||||
|
</Project>
|
||||||
10
Dabit.Mcp/obj/Dabit.Mcp.csproj.nuget.g.targets
Normal file
10
Dabit.Mcp/obj/Dabit.Mcp.csproj.nuget.g.targets
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||||
|
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||||
|
<Import Project="$(NuGetPackageRoot)system.text.json\10.0.6\buildTransitive\net8.0\System.Text.Json.targets" Condition="Exists('$(NuGetPackageRoot)system.text.json\10.0.6\buildTransitive\net8.0\System.Text.Json.targets')" />
|
||||||
|
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\10.0.7\buildTransitive\net8.0\Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\10.0.7\buildTransitive\net8.0\Microsoft.Extensions.Logging.Abstractions.targets')" />
|
||||||
|
<Import Project="$(NuGetPackageRoot)microsoft.extensions.options\10.0.7\buildTransitive\net8.0\Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options\10.0.7\buildTransitive\net8.0\Microsoft.Extensions.Options.targets')" />
|
||||||
|
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.binder\9.0.0\buildTransitive\netstandard2.0\Microsoft.Extensions.Configuration.Binder.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.binder\9.0.0\buildTransitive\netstandard2.0\Microsoft.Extensions.Configuration.Binder.targets')" />
|
||||||
|
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets\9.0.0\buildTransitive\net8.0\Microsoft.Extensions.Configuration.UserSecrets.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets\9.0.0\buildTransitive\net8.0\Microsoft.Extensions.Configuration.UserSecrets.targets')" />
|
||||||
|
</ImportGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// <autogenerated />
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")]
|
||||||
24
Dabit.Mcp/obj/Debug/net9.0-windows/Dabit.Mcp.AssemblyInfo.cs
Normal file
24
Dabit.Mcp/obj/Debug/net9.0-windows/Dabit.Mcp.AssemblyInfo.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
[assembly: System.Reflection.AssemblyCompanyAttribute("Dabit.Mcp")]
|
||||||
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+ac1891aedbf9906ac5f44fa8c087ff49e6444d59")]
|
||||||
|
[assembly: System.Reflection.AssemblyProductAttribute("Dabit.Mcp")]
|
||||||
|
[assembly: System.Reflection.AssemblyTitleAttribute("Dabit.Mcp")]
|
||||||
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
[assembly: System.Runtime.Versioning.TargetPlatformAttribute("Windows7.0")]
|
||||||
|
[assembly: System.Runtime.Versioning.SupportedOSPlatformAttribute("Windows7.0")]
|
||||||
|
|
||||||
|
// MSBuild WriteCodeFragment 클래스에서 생성되었습니다.
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
a58c2ac0574aaa40dc89832cefa5ad0f0c2c7f550929d01c266b6eecff930bba
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
is_global = true
|
||||||
|
build_property.TargetFramework = net9.0-windows
|
||||||
|
build_property.TargetPlatformMinVersion = 7.0
|
||||||
|
build_property.UsingMicrosoftNETSdkWeb =
|
||||||
|
build_property.ProjectTypeGuids =
|
||||||
|
build_property.InvariantGlobalization =
|
||||||
|
build_property.PlatformNeutralAssembly =
|
||||||
|
build_property.EnforceExtendedAnalyzerRules =
|
||||||
|
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||||
|
build_property.RootNamespace = Dabit.Mcp
|
||||||
|
build_property.ProjectDir = D:\Gitea\dabitche-mcp\Dabit.Mcp\
|
||||||
|
build_property.EnableComHosting =
|
||||||
|
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||||
|
build_property.CsWinRTUseWindowsUIXamlProjections = false
|
||||||
|
build_property.EffectiveAnalysisLevelStyle = 9.0
|
||||||
|
build_property.EnableCodeStyleSeverity =
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// <auto-generated/>
|
||||||
|
global using global::System;
|
||||||
|
global using global::System.Collections.Generic;
|
||||||
|
global using global::System.IO;
|
||||||
|
global using global::System.Linq;
|
||||||
|
global using global::System.Net.Http;
|
||||||
|
global using global::System.Threading;
|
||||||
|
global using global::System.Threading.Tasks;
|
||||||
BIN
Dabit.Mcp/obj/Debug/net9.0-windows/Dabit.Mcp.assets.cache
Normal file
BIN
Dabit.Mcp/obj/Debug/net9.0-windows/Dabit.Mcp.assets.cache
Normal file
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
599f2696c7a3ad4ebfa014e6b19e2ed952ac76c1c22d37596f60595ad29efe1a
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Dabit.Mcp.exe
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Dabit.Mcp.deps.json
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Dabit.Mcp.runtimeconfig.json
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Dabit.Mcp.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Dabit.Mcp.pdb
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.AI.Abstractions.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Caching.Abstractions.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Configuration.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Configuration.Abstractions.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Configuration.Binder.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Configuration.CommandLine.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Configuration.EnvironmentVariables.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Configuration.FileExtensions.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Configuration.Json.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Configuration.UserSecrets.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.DependencyInjection.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.DependencyInjection.Abstractions.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Diagnostics.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Diagnostics.Abstractions.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.FileProviders.Abstractions.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.FileProviders.Physical.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.FileSystemGlobbing.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Hosting.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Hosting.Abstractions.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Logging.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Logging.Abstractions.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Logging.Configuration.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Logging.Console.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Logging.Debug.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Logging.EventLog.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Logging.EventSource.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Options.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Options.ConfigurationExtensions.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Primitives.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\ModelContextProtocol.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\ModelContextProtocol.Core.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\MQTTnet.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\System.Diagnostics.DiagnosticSource.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\System.Diagnostics.EventLog.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\System.IO.Pipelines.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\System.IO.Ports.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\System.Net.ServerSentEvents.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\System.Text.Encoding.CodePages.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\System.Text.Encodings.Web.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\System.Text.Json.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\android-arm\native\libSystem.IO.Ports.Native.so
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\android-arm64\native\libSystem.IO.Ports.Native.so
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\android-x64\native\libSystem.IO.Ports.Native.so
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\android-x86\native\libSystem.IO.Ports.Native.so
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\linux-arm\native\libSystem.IO.Ports.Native.so
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\linux-arm64\native\libSystem.IO.Ports.Native.so
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\linux-bionic-arm64\native\libSystem.IO.Ports.Native.so
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\linux-bionic-x64\native\libSystem.IO.Ports.Native.so
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\linux-musl-arm\native\libSystem.IO.Ports.Native.so
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\linux-musl-arm64\native\libSystem.IO.Ports.Native.so
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\linux-musl-x64\native\libSystem.IO.Ports.Native.so
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\linux-x64\native\libSystem.IO.Ports.Native.so
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\maccatalyst-arm64\native\libSystem.IO.Ports.Native.dylib
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\maccatalyst-x64\native\libSystem.IO.Ports.Native.dylib
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\osx-arm64\native\libSystem.IO.Ports.Native.dylib
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\osx-x64\native\libSystem.IO.Ports.Native.dylib
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\win\lib\net9.0\System.Diagnostics.EventLog.Messages.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\win\lib\net9.0\System.Diagnostics.EventLog.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\unix\lib\net9.0\System.IO.Ports.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\win\lib\net9.0\System.IO.Ports.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\win\lib\net9.0\System.Text.Encoding.CodePages.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\browser\lib\net8.0\System.Text.Encodings.Web.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\win\lib\net9.0\System.Text.Encodings.Web.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\DibdProtocol.Core.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\DibdProtocol.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\DibdProtocol.Transport.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\DibdProtocol.pdb
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\DibdProtocol.Core.pdb
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\DibdProtocol.Transport.pdb
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.csproj.AssemblyReference.cache
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.GeneratedMSBuildEditorConfig.editorconfig
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.AssemblyInfoInputs.cache
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.AssemblyInfo.cs
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.csproj.CoreCompileInputs.cache
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.csproj.Up2Date
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\refint\Dabit.Mcp.dll
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.pdb
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.genruntimeconfig.cache
|
||||||
|
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\ref\Dabit.Mcp.dll
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user