Test_ACS 프로젝트 추가 - ACS 시뮬레이터 (v1.4.0)

## 신규 기능
- ACS(중앙제어시스템) 시뮬레이터 프로젝트 생성
- 8가지 AGV 제어 명령어 지원:
  * SetCurrent: 현재 위치 설정
  * Goto: RFID 이동
  * GotoAlias: 별칭 이동 (v1.1.0)
  * Stop: 정지
  * Reset: 에러 리셋
  * Manual: 수동 제어
  * MarkStop: 마크센서 정지
  * LiftControl: 리프트 제어

## AGV 상태 실시간 표시 (v1.3.0)
- AGV 상태 그룹박스 추가 (8가지 상태 정보)
- Status 메시지(cmd=3) 자동 수신 및 UI 업데이트
- 상태별 색상 표시로 직관적 모니터링

## 설정 관리
- 실행 폴더에 JSON 형식 설정 파일 저장 (v1.4.0)
- COM 포트, 보레이트, RFID, 별칭, AGV 선택 자동 저장
- 설정 파일 직접 편집 가능

## 기술 스택
- .NET Framework 4.8
- ENIGProtocol 프로젝트 참조
- RS232/Xbee 통신
- Newtonsoft.Json

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
backuppc
2025-11-12 17:35:36 +09:00
parent 883c75b2b8
commit 1c12beb3e7
16 changed files with 2369 additions and 1 deletions

Submodule Cs_HMI/SubProject/EnigProtocol updated: 070aa848c9...e8cae2c4ee

View File

@@ -0,0 +1,67 @@
using System;
using System.IO;
using System.Windows.Forms;
using Newtonsoft.Json;
namespace Test_ACS
{
/// <summary>
/// 애플리케이션 설정을 관리하는 클래스
/// </summary>
public class AppSettings
{
public string LastPort { get; set; } = "COM1";
public string LastBaudRate { get; set; } = "9600";
public string LastRFID { get; set; } = "0001";
public string LastAlias { get; set; } = "CHARGER1";
public int LastAGV { get; set; } = 11;
private static string GetConfigFilePath()
{
// 실행 파일과 같은 폴더에 같은 이름으로 .config 파일 생성
string exePath = Application.ExecutablePath;
string exeName = Path.GetFileNameWithoutExtension(exePath);
string configPath = Path.Combine(Path.GetDirectoryName(exePath), exeName + ".config");
return configPath;
}
/// <summary>
/// 설정 파일을 로드합니다
/// </summary>
public static AppSettings Load()
{
try
{
string configPath = GetConfigFilePath();
if (File.Exists(configPath))
{
string json = File.ReadAllText(configPath);
return JsonConvert.DeserializeObject<AppSettings>(json);
}
}
catch (Exception)
{
// 로드 실패 시 기본값 반환
}
return new AppSettings();
}
/// <summary>
/// 설정을 파일에 저장합니다
/// </summary>
public void Save()
{
try
{
string configPath = GetConfigFilePath();
string json = JsonConvert.SerializeObject(this, Formatting.Indented);
File.WriteAllText(configPath, json);
}
catch (Exception)
{
// 저장 실패 시 무시
}
}
}
}

View File

@@ -0,0 +1,162 @@
# Test_ACS 변경 이력
## v1.4.0 (2025-01-12)
### 설정 파일 저장 방식 변경
- **실행 폴더에 설정 파일 저장**
- 이전: 사용자 프로파일 폴더 (`%LOCALAPPDATA%`)
- 변경: 실행 파일과 같은 폴더에 `Test_ACS.config` 저장
- 형식: JSON 형식으로 저장 (가독성 향상)
### 코드 개선
- `AppSettings` 클래스 신규 생성
- JSON 직렬화/역직렬화 지원
- 실행 파일 경로 자동 감지
- 설정 파일 자동 생성 및 로드
- Newtonsoft.Json 패키지 참조 추가
### 사용성 개선
- 설정 파일을 직접 편집 가능 (JSON 형식)
- 프로그램 이동 시에도 설정 파일 함께 이동
- 여러 버전 동시 사용 가능 (각 폴더별 독립 설정)
---
## v1.3.0 (2025-01-12)
### 신규 기능
- **AGV 상태 실시간 표시**
- AGV 상태 그룹박스 추가
- 8가지 AGV 상태 정보 표시:
- 모드 (수동/자동)
- 실행상태 (정지/실행/에러)
- 방향 (직진/좌회전/우회전/마크정지)
- 도달완료 (OFF/ON)
- 충전 상태 (OFF/ON)
- 카트 감지 (없음/있음/알 수 없음)
- 리프트 위치 (하강/상승/알 수 없음)
- 현재 RFID 태그 번호 (6자리)
- 상태별 색상 표시로 직관적 모니터링
- AGV Status 메시지(cmd=3) 자동 수신 및 업데이트
### UI 개선
- AGV 상태 그룹박스 추가 (2열 x 4행 레이아웃)
- 메인 폼 크기 확장 (539 → 588 픽셀)
- 상태별 색상 구분:
- 수동(파란색) / 자동(녹색)
- 정지(회색) / 실행(녹색) / 에러(빨간색)
- 충전중(주황색)
### 프로토콜 업데이트
- ENIGProtocol ReadMe.MD 수정
- AGV Status 프로토콜 정의 명확화 (총 13바이트)
- LastTag 필드 크기 수정 (4바이트 → 6바이트)
- CurrentPath 필드 제거 (실제 구현과 일치)
### 코드 개선
- `UpdateAGVStatus()` 메서드 구현
- Status 메시지 수신 처리 로직 추가
- 스레드 안전성 보장 (InvokeRequired 체크)
- 데이터 유효성 검사 추가
---
## v1.2.0 (2025-01-12)
### 신규 기능
- **설정 자동 저장/불러오기 기능**
- 마지막 사용 COM 포트 자동 저장
- 마지막 사용 보레이트 자동 저장
- 마지막 입력 RFID 번호 자동 저장
- 마지막 입력 별칭 자동 저장
- 마지막 선택 AGV (11/12) 자동 저장
- 프로그램 종료 시 자동 저장
- 프로그램 시작 시 자동 불러오기
### 구현 상세
- **Settings.settings 파일 확장**
- LastPort (string)
- LastBaudRate (string)
- LastRFID (string)
- LastAlias (string)
- LastAGV (int)
- **자동 저장 이벤트**
- COM 포트 선택 변경 시
- 보레이트 텍스트 변경 시
- RFID 번호 텍스트 변경 시
- 별칭 텍스트 변경 시
- AGV 선택 변경 시
- 프로그램 종료 시 (OnFormClosing)
- **자동 불러오기**
- 프로그램 시작 시 LoadSettings() 호출
- 포트 목록 로드 후 마지막 설정 적용
### 사용자 경험 개선
- 프로그램 재실행 시 이전 설정 자동 복원
- 설정 저장/불러오기 상태 로그 표시
- 에러 발생 시 로그에 에러 메시지 표시
---
## v1.1.0 (2025-01-12)
### 신규 기능
- **GotoAlias 명령어 추가**
- 별칭(Alias)을 사용한 AGV 이동 명령 지원
- RFID 번호 대신 의미 있는 이름으로 목적지 지정 가능
- 예: "CHARGER1", "BUFFER2", "STATION_A" 등
### UI 개선
- **별칭 입력 필드 추가**
- 위치: RFID 입력 필드 아래
- 기본값: "CHARGER1"
- **GotoAlias 버튼 추가**
- 위치: Goto 버튼 우측
- 버튼명: "별칭 이동 (GotoAlias)"
- **레이아웃 최적화**
- RFID 번호 입력: 라인 1
- 별칭 입력: 라인 2
- SetCurrent / GotoAlias 버튼: 라인 3
- Goto / GotoAlias 버튼: 라인 4
- Stop / Reset 버튼: 라인 5
- MarkStop 버튼 및 체크박스: 라인 6
- 수동 제어 / 리프트 제어: 하단 그룹
### 프로토콜 변경
- **AGVCommandHE Enum 확장**
- GotoAlias = 107 추가
- 데이터 형식: TargetID(2 hex) + Alias(ASCII string)
### 코드 개선
- `btnGotoAlias_Click` 이벤트 핸들러 구현
- 별칭 유효성 검사 (빈 문자열 체크)
- ASCII → HEX 변환 로직 추가
### 문서 업데이트
- README.md에 GotoAlias 사용법 추가
- 프로토콜 명령어 표에 GotoAlias 추가
- 버전 이력 업데이트
---
## v1.0.0 (2025-01-11)
### 초기 릴리즈
- ACS 시뮬레이터 기본 구현
- ENIGProtocol 프로젝트 참조
- RS232 통신 지원
- 7가지 기본 ACS 명령어:
- SetCurrent (현재 위치 설정)
- Goto (RFID 이동)
- Stop (정지)
- Reset (에러 리셋)
- Manual (수동 제어)
- MarkStop (마크센서 정지)
- LiftControl (리프트 제어)
- AGV 선택 기능 (AGV1/AGV2)
- 로그 시스템 (TX/RX/Info 탭)
- COM 포트 관리 (연결/해제/새로고침)

View File

@@ -0,0 +1,828 @@
namespace Test_ACS
{
partial class MainForm
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.grpConnection = new System.Windows.Forms.GroupBox();
this.btnRefresh = new System.Windows.Forms.Button();
this.btnConnect = new System.Windows.Forms.Button();
this.txtBaudRate = new System.Windows.Forms.TextBox();
this.cmbPort = new System.Windows.Forms.ComboBox();
this.lblBaudRate = new System.Windows.Forms.Label();
this.lblPort = new System.Windows.Forms.Label();
this.grpAGV = new System.Windows.Forms.GroupBox();
this.rbAGV2 = new System.Windows.Forms.RadioButton();
this.rbAGV1 = new System.Windows.Forms.RadioButton();
this.grpCommands = new System.Windows.Forms.GroupBox();
this.grpLift = new System.Windows.Forms.GroupBox();
this.btnLiftStop = new System.Windows.Forms.Button();
this.btnLiftDown = new System.Windows.Forms.Button();
this.btnLiftUp = new System.Windows.Forms.Button();
this.grpManual = new System.Windows.Forms.GroupBox();
this.btnManual = new System.Windows.Forms.Button();
this.numRuntime = new System.Windows.Forms.NumericUpDown();
this.lblRuntime = new System.Windows.Forms.Label();
this.cmbSpeed = new System.Windows.Forms.ComboBox();
this.lblSpeed = new System.Windows.Forms.Label();
this.cmbDirection = new System.Windows.Forms.ComboBox();
this.lblDirection = new System.Windows.Forms.Label();
this.chkMarkStop = new System.Windows.Forms.CheckBox();
this.btnMarkStop = new System.Windows.Forms.Button();
this.btnReset = new System.Windows.Forms.Button();
this.btnStop = new System.Windows.Forms.Button();
this.btnGotoAlias = new System.Windows.Forms.Button();
this.btnGoto = new System.Windows.Forms.Button();
this.btnSetCurrent = new System.Windows.Forms.Button();
this.txtAlias = new System.Windows.Forms.TextBox();
this.lblAlias = new System.Windows.Forms.Label();
this.txtRFID = new System.Windows.Forms.TextBox();
this.lblRFID = new System.Windows.Forms.Label();
this.grpLogs = new System.Windows.Forms.GroupBox();
this.tabLogs = new System.Windows.Forms.TabControl();
this.tabRX = new System.Windows.Forms.TabPage();
this.txtRxLog = new System.Windows.Forms.TextBox();
this.tabTX = new System.Windows.Forms.TabPage();
this.txtTxLog = new System.Windows.Forms.TextBox();
this.tabInfo = new System.Windows.Forms.TabPage();
this.txtInfoLog = new System.Windows.Forms.TextBox();
this.grpAGVStatus = new System.Windows.Forms.GroupBox();
this.lblLastTagValue = new System.Windows.Forms.Label();
this.lblLastTag = new System.Windows.Forms.Label();
this.lblLiftStValue = new System.Windows.Forms.Label();
this.lblLiftSt = new System.Windows.Forms.Label();
this.lblCartStValue = new System.Windows.Forms.Label();
this.lblCartSt = new System.Windows.Forms.Label();
this.lblChargeStValue = new System.Windows.Forms.Label();
this.lblChargeSt = new System.Windows.Forms.Label();
this.lblInpositionValue = new System.Windows.Forms.Label();
this.lblInposition = new System.Windows.Forms.Label();
this.lblDirectionValue = new System.Windows.Forms.Label();
this.lblRunStValue = new System.Windows.Forms.Label();
this.lblRunSt = new System.Windows.Forms.Label();
this.lblModeValue = new System.Windows.Forms.Label();
this.lblMode = new System.Windows.Forms.Label();
this.grpConnection.SuspendLayout();
this.grpAGV.SuspendLayout();
this.grpCommands.SuspendLayout();
this.grpLift.SuspendLayout();
this.grpManual.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.numRuntime)).BeginInit();
this.grpLogs.SuspendLayout();
this.tabLogs.SuspendLayout();
this.tabRX.SuspendLayout();
this.tabTX.SuspendLayout();
this.tabInfo.SuspendLayout();
this.grpAGVStatus.SuspendLayout();
this.SuspendLayout();
//
// grpConnection
//
this.grpConnection.Controls.Add(this.btnRefresh);
this.grpConnection.Controls.Add(this.btnConnect);
this.grpConnection.Controls.Add(this.txtBaudRate);
this.grpConnection.Controls.Add(this.cmbPort);
this.grpConnection.Controls.Add(this.lblBaudRate);
this.grpConnection.Controls.Add(this.lblPort);
this.grpConnection.Location = new System.Drawing.Point(12, 12);
this.grpConnection.Name = "grpConnection";
this.grpConnection.Size = new System.Drawing.Size(260, 120);
this.grpConnection.TabIndex = 0;
this.grpConnection.TabStop = false;
this.grpConnection.Text = "연결 설정";
//
// btnRefresh
//
this.btnRefresh.Location = new System.Drawing.Point(179, 22);
this.btnRefresh.Name = "btnRefresh";
this.btnRefresh.Size = new System.Drawing.Size(70, 23);
this.btnRefresh.TabIndex = 5;
this.btnRefresh.Text = "새로고침";
this.btnRefresh.UseVisualStyleBackColor = true;
this.btnRefresh.Click += new System.EventHandler(this.btnRefresh_Click);
//
// btnConnect
//
this.btnConnect.Location = new System.Drawing.Point(15, 81);
this.btnConnect.Name = "btnConnect";
this.btnConnect.Size = new System.Drawing.Size(234, 30);
this.btnConnect.TabIndex = 4;
this.btnConnect.Text = "연결";
this.btnConnect.UseVisualStyleBackColor = true;
this.btnConnect.Click += new System.EventHandler(this.btnConnect_Click);
//
// txtBaudRate
//
this.txtBaudRate.Location = new System.Drawing.Point(85, 53);
this.txtBaudRate.Name = "txtBaudRate";
this.txtBaudRate.Size = new System.Drawing.Size(164, 21);
this.txtBaudRate.TabIndex = 3;
this.txtBaudRate.Text = "9600";
this.txtBaudRate.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
this.txtBaudRate.TextChanged += new System.EventHandler(this.txtBaudRate_TextChanged);
//
// cmbPort
//
this.cmbPort.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbPort.FormattingEnabled = true;
this.cmbPort.Location = new System.Drawing.Point(85, 23);
this.cmbPort.Name = "cmbPort";
this.cmbPort.Size = new System.Drawing.Size(88, 20);
this.cmbPort.TabIndex = 2;
this.cmbPort.SelectedIndexChanged += new System.EventHandler(this.cmbPort_SelectedIndexChanged);
//
// lblBaudRate
//
this.lblBaudRate.AutoSize = true;
this.lblBaudRate.Location = new System.Drawing.Point(15, 56);
this.lblBaudRate.Name = "lblBaudRate";
this.lblBaudRate.Size = new System.Drawing.Size(57, 12);
this.lblBaudRate.TabIndex = 1;
this.lblBaudRate.Text = "보레이트:";
//
// lblPort
//
this.lblPort.AutoSize = true;
this.lblPort.Location = new System.Drawing.Point(15, 26);
this.lblPort.Name = "lblPort";
this.lblPort.Size = new System.Drawing.Size(66, 12);
this.lblPort.TabIndex = 0;
this.lblPort.Text = "COM 포트:";
//
// grpAGV
//
this.grpAGV.Controls.Add(this.rbAGV2);
this.grpAGV.Controls.Add(this.rbAGV1);
this.grpAGV.Location = new System.Drawing.Point(278, 12);
this.grpAGV.Name = "grpAGV";
this.grpAGV.Size = new System.Drawing.Size(200, 120);
this.grpAGV.TabIndex = 1;
this.grpAGV.TabStop = false;
this.grpAGV.Text = "AGV 선택";
//
// rbAGV2
//
this.rbAGV2.AutoSize = true;
this.rbAGV2.Location = new System.Drawing.Point(20, 60);
this.rbAGV2.Name = "rbAGV2";
this.rbAGV2.Size = new System.Drawing.Size(95, 16);
this.rbAGV2.TabIndex = 1;
this.rbAGV2.Text = "AGV2 (ID:12)";
this.rbAGV2.UseVisualStyleBackColor = true;
this.rbAGV2.CheckedChanged += new System.EventHandler(this.rbAGV2_CheckedChanged);
//
// rbAGV1
//
this.rbAGV1.AutoSize = true;
this.rbAGV1.Checked = true;
this.rbAGV1.Location = new System.Drawing.Point(20, 30);
this.rbAGV1.Name = "rbAGV1";
this.rbAGV1.Size = new System.Drawing.Size(95, 16);
this.rbAGV1.TabIndex = 0;
this.rbAGV1.TabStop = true;
this.rbAGV1.Text = "AGV1 (ID:11)";
this.rbAGV1.UseVisualStyleBackColor = true;
this.rbAGV1.CheckedChanged += new System.EventHandler(this.rbAGV1_CheckedChanged);
//
// grpCommands
//
this.grpCommands.Controls.Add(this.grpLift);
this.grpCommands.Controls.Add(this.grpManual);
this.grpCommands.Controls.Add(this.chkMarkStop);
this.grpCommands.Controls.Add(this.btnMarkStop);
this.grpCommands.Controls.Add(this.btnReset);
this.grpCommands.Controls.Add(this.btnStop);
this.grpCommands.Controls.Add(this.btnGotoAlias);
this.grpCommands.Controls.Add(this.btnGoto);
this.grpCommands.Controls.Add(this.btnSetCurrent);
this.grpCommands.Controls.Add(this.txtAlias);
this.grpCommands.Controls.Add(this.lblAlias);
this.grpCommands.Controls.Add(this.txtRFID);
this.grpCommands.Controls.Add(this.lblRFID);
this.grpCommands.Location = new System.Drawing.Point(12, 138);
this.grpCommands.Name = "grpCommands";
this.grpCommands.Size = new System.Drawing.Size(466, 312);
this.grpCommands.TabIndex = 2;
this.grpCommands.TabStop = false;
this.grpCommands.Text = "ACS 명령";
//
// grpLift
//
this.grpLift.Controls.Add(this.btnLiftStop);
this.grpLift.Controls.Add(this.btnLiftDown);
this.grpLift.Controls.Add(this.btnLiftUp);
this.grpLift.Location = new System.Drawing.Point(240, 202);
this.grpLift.Name = "grpLift";
this.grpLift.Size = new System.Drawing.Size(210, 100);
this.grpLift.TabIndex = 9;
this.grpLift.TabStop = false;
this.grpLift.Text = "리프트 제어";
//
// btnLiftStop
//
this.btnLiftStop.Location = new System.Drawing.Point(135, 20);
this.btnLiftStop.Name = "btnLiftStop";
this.btnLiftStop.Size = new System.Drawing.Size(60, 70);
this.btnLiftStop.TabIndex = 2;
this.btnLiftStop.Text = "정지";
this.btnLiftStop.UseVisualStyleBackColor = true;
this.btnLiftStop.Click += new System.EventHandler(this.btnLiftStop_Click);
//
// btnLiftDown
//
this.btnLiftDown.Location = new System.Drawing.Point(70, 20);
this.btnLiftDown.Name = "btnLiftDown";
this.btnLiftDown.Size = new System.Drawing.Size(60, 70);
this.btnLiftDown.TabIndex = 1;
this.btnLiftDown.Text = "하강";
this.btnLiftDown.UseVisualStyleBackColor = true;
this.btnLiftDown.Click += new System.EventHandler(this.btnLiftDown_Click);
//
// btnLiftUp
//
this.btnLiftUp.Location = new System.Drawing.Point(5, 20);
this.btnLiftUp.Name = "btnLiftUp";
this.btnLiftUp.Size = new System.Drawing.Size(60, 70);
this.btnLiftUp.TabIndex = 0;
this.btnLiftUp.Text = "상승";
this.btnLiftUp.UseVisualStyleBackColor = true;
this.btnLiftUp.Click += new System.EventHandler(this.btnLiftUp_Click);
//
// grpManual
//
this.grpManual.Controls.Add(this.btnManual);
this.grpManual.Controls.Add(this.numRuntime);
this.grpManual.Controls.Add(this.lblRuntime);
this.grpManual.Controls.Add(this.cmbSpeed);
this.grpManual.Controls.Add(this.lblSpeed);
this.grpManual.Controls.Add(this.cmbDirection);
this.grpManual.Location = new System.Drawing.Point(15, 202);
this.grpManual.Name = "grpManual";
this.grpManual.Size = new System.Drawing.Size(215, 100);
this.grpManual.TabIndex = 8;
this.grpManual.TabStop = false;
this.grpManual.Text = "수동 제어";
//
// btnManual
//
this.btnManual.Location = new System.Drawing.Point(136, 16);
this.btnManual.Name = "btnManual";
this.btnManual.Size = new System.Drawing.Size(73, 74);
this.btnManual.TabIndex = 6;
this.btnManual.Text = "수동\r\n이동\r\n실행";
this.btnManual.UseVisualStyleBackColor = true;
this.btnManual.Click += new System.EventHandler(this.btnManual_Click);
//
// numRuntime
//
this.numRuntime.Location = new System.Drawing.Point(60, 66);
this.numRuntime.Maximum = new decimal(new int[] {
255,
0,
0,
0});
this.numRuntime.Name = "numRuntime";
this.numRuntime.Size = new System.Drawing.Size(70, 21);
this.numRuntime.TabIndex = 5;
this.numRuntime.Value = new decimal(new int[] {
5,
0,
0,
0});
//
// lblRuntime
//
this.lblRuntime.AutoSize = true;
this.lblRuntime.Location = new System.Drawing.Point(3, 70);
this.lblRuntime.Name = "lblRuntime";
this.lblRuntime.Size = new System.Drawing.Size(51, 12);
this.lblRuntime.TabIndex = 4;
this.lblRuntime.Text = "시간(초)";
//
// cmbSpeed
//
this.cmbSpeed.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbSpeed.FormattingEnabled = true;
this.cmbSpeed.Items.AddRange(new object[] {
"느림",
"보통",
"빠름"});
this.cmbSpeed.Location = new System.Drawing.Point(60, 41);
this.cmbSpeed.Name = "cmbSpeed";
this.cmbSpeed.Size = new System.Drawing.Size(70, 20);
this.cmbSpeed.TabIndex = 3;
//
// lblSpeed
//
this.lblSpeed.AutoSize = true;
this.lblSpeed.Location = new System.Drawing.Point(14, 45);
this.lblSpeed.Name = "lblSpeed";
this.lblSpeed.Size = new System.Drawing.Size(29, 12);
this.lblSpeed.TabIndex = 2;
this.lblSpeed.Text = "속도";
//
// cmbDirection
//
this.cmbDirection.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbDirection.FormattingEnabled = true;
this.cmbDirection.Items.AddRange(new object[] {
"후진",
"전진",
"좌",
"우"});
this.cmbDirection.Location = new System.Drawing.Point(60, 16);
this.cmbDirection.Name = "cmbDirection";
this.cmbDirection.Size = new System.Drawing.Size(70, 20);
this.cmbDirection.TabIndex = 1;
//
// lblDirection
//
this.lblDirection.AutoSize = true;
this.lblDirection.Location = new System.Drawing.Point(10, 48);
this.lblDirection.Name = "lblDirection";
this.lblDirection.Size = new System.Drawing.Size(33, 12);
this.lblDirection.TabIndex = 4;
this.lblDirection.Text = "방향:";
//
// chkMarkStop
//
this.chkMarkStop.AutoSize = true;
this.chkMarkStop.Location = new System.Drawing.Point(240, 174);
this.chkMarkStop.Name = "chkMarkStop";
this.chkMarkStop.Size = new System.Drawing.Size(76, 16);
this.chkMarkStop.TabIndex = 7;
this.chkMarkStop.Text = "정지 설정";
this.chkMarkStop.UseVisualStyleBackColor = true;
//
// btnMarkStop
//
this.btnMarkStop.Location = new System.Drawing.Point(15, 167);
this.btnMarkStop.Name = "btnMarkStop";
this.btnMarkStop.Size = new System.Drawing.Size(215, 30);
this.btnMarkStop.TabIndex = 6;
this.btnMarkStop.Text = "마크센서 정지";
this.btnMarkStop.UseVisualStyleBackColor = true;
this.btnMarkStop.Click += new System.EventHandler(this.btnMarkStop_Click);
//
// btnReset
//
this.btnReset.Location = new System.Drawing.Point(240, 132);
this.btnReset.Name = "btnReset";
this.btnReset.Size = new System.Drawing.Size(210, 30);
this.btnReset.TabIndex = 5;
this.btnReset.Text = "에러 리셋";
this.btnReset.UseVisualStyleBackColor = true;
this.btnReset.Click += new System.EventHandler(this.btnReset_Click);
//
// btnStop
//
this.btnStop.Location = new System.Drawing.Point(15, 132);
this.btnStop.Name = "btnStop";
this.btnStop.Size = new System.Drawing.Size(215, 30);
this.btnStop.TabIndex = 4;
this.btnStop.Text = "정지";
this.btnStop.UseVisualStyleBackColor = true;
this.btnStop.Click += new System.EventHandler(this.btnStop_Click);
//
// btnGotoAlias
//
this.btnGotoAlias.Location = new System.Drawing.Point(286, 94);
this.btnGotoAlias.Name = "btnGotoAlias";
this.btnGotoAlias.Size = new System.Drawing.Size(174, 30);
this.btnGotoAlias.TabIndex = 11;
this.btnGotoAlias.Text = "별칭 이동 (GotoAlias)";
this.btnGotoAlias.UseVisualStyleBackColor = true;
this.btnGotoAlias.Click += new System.EventHandler(this.btnGotoAlias_Click);
//
// btnGoto
//
this.btnGoto.Location = new System.Drawing.Point(286, 23);
this.btnGoto.Name = "btnGoto";
this.btnGoto.Size = new System.Drawing.Size(174, 30);
this.btnGoto.TabIndex = 3;
this.btnGoto.Text = "RFID 이동 (Goto)";
this.btnGoto.UseVisualStyleBackColor = true;
this.btnGoto.Click += new System.EventHandler(this.btnGoto_Click);
//
// btnSetCurrent
//
this.btnSetCurrent.Location = new System.Drawing.Point(85, 57);
this.btnSetCurrent.Name = "btnSetCurrent";
this.btnSetCurrent.Size = new System.Drawing.Size(195, 30);
this.btnSetCurrent.TabIndex = 2;
this.btnSetCurrent.Text = "현재 위치 설정 (SetCurrent)";
this.btnSetCurrent.UseVisualStyleBackColor = true;
this.btnSetCurrent.Click += new System.EventHandler(this.btnSetCurrent_Click);
//
// txtAlias
//
this.txtAlias.Font = new System.Drawing.Font("Consolas", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.txtAlias.Location = new System.Drawing.Point(85, 96);
this.txtAlias.Name = "txtAlias";
this.txtAlias.Size = new System.Drawing.Size(195, 26);
this.txtAlias.TabIndex = 10;
this.txtAlias.Text = "CHARGER1";
this.txtAlias.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
this.txtAlias.TextChanged += new System.EventHandler(this.txtAlias_TextChanged);
//
// lblAlias
//
this.lblAlias.AutoSize = true;
this.lblAlias.Location = new System.Drawing.Point(15, 103);
this.lblAlias.Name = "lblAlias";
this.lblAlias.Size = new System.Drawing.Size(71, 12);
this.lblAlias.TabIndex = 9;
this.lblAlias.Text = "별칭(Alias):";
//
// txtRFID
//
this.txtRFID.Font = new System.Drawing.Font("Consolas", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.txtRFID.Location = new System.Drawing.Point(85, 25);
this.txtRFID.Name = "txtRFID";
this.txtRFID.Size = new System.Drawing.Size(195, 26);
this.txtRFID.TabIndex = 1;
this.txtRFID.Text = "0001";
this.txtRFID.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
this.txtRFID.TextChanged += new System.EventHandler(this.txtRFID_TextChanged);
//
// lblRFID
//
this.lblRFID.AutoSize = true;
this.lblRFID.Location = new System.Drawing.Point(15, 32);
this.lblRFID.Name = "lblRFID";
this.lblRFID.Size = new System.Drawing.Size(63, 12);
this.lblRFID.TabIndex = 0;
this.lblRFID.Text = "RFID 번호:";
//
// grpLogs
//
this.grpLogs.Controls.Add(this.tabLogs);
this.grpLogs.Location = new System.Drawing.Point(484, 12);
this.grpLogs.Name = "grpLogs";
this.grpLogs.Size = new System.Drawing.Size(520, 564);
this.grpLogs.TabIndex = 3;
this.grpLogs.TabStop = false;
this.grpLogs.Text = "로그";
//
// tabLogs
//
this.tabLogs.Controls.Add(this.tabRX);
this.tabLogs.Controls.Add(this.tabTX);
this.tabLogs.Controls.Add(this.tabInfo);
this.tabLogs.Dock = System.Windows.Forms.DockStyle.Fill;
this.tabLogs.Location = new System.Drawing.Point(3, 17);
this.tabLogs.Name = "tabLogs";
this.tabLogs.SelectedIndex = 0;
this.tabLogs.Size = new System.Drawing.Size(514, 544);
this.tabLogs.TabIndex = 0;
//
// tabRX
//
this.tabRX.Controls.Add(this.txtRxLog);
this.tabRX.Location = new System.Drawing.Point(4, 22);
this.tabRX.Name = "tabRX";
this.tabRX.Padding = new System.Windows.Forms.Padding(3);
this.tabRX.Size = new System.Drawing.Size(506, 518);
this.tabRX.TabIndex = 1;
this.tabRX.Text = "수신 (RX)";
this.tabRX.UseVisualStyleBackColor = true;
//
// txtRxLog
//
this.txtRxLog.Dock = System.Windows.Forms.DockStyle.Fill;
this.txtRxLog.Font = new System.Drawing.Font("Consolas", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.txtRxLog.Location = new System.Drawing.Point(3, 3);
this.txtRxLog.Multiline = true;
this.txtRxLog.Name = "txtRxLog";
this.txtRxLog.ReadOnly = true;
this.txtRxLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.txtRxLog.Size = new System.Drawing.Size(500, 512);
this.txtRxLog.TabIndex = 0;
//
// tabTX
//
this.tabTX.Controls.Add(this.txtTxLog);
this.tabTX.Location = new System.Drawing.Point(4, 22);
this.tabTX.Name = "tabTX";
this.tabTX.Padding = new System.Windows.Forms.Padding(3);
this.tabTX.Size = new System.Drawing.Size(506, 469);
this.tabTX.TabIndex = 0;
this.tabTX.Text = "송신 (TX)";
this.tabTX.UseVisualStyleBackColor = true;
//
// txtTxLog
//
this.txtTxLog.Dock = System.Windows.Forms.DockStyle.Fill;
this.txtTxLog.Font = new System.Drawing.Font("Consolas", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.txtTxLog.Location = new System.Drawing.Point(3, 3);
this.txtTxLog.Multiline = true;
this.txtTxLog.Name = "txtTxLog";
this.txtTxLog.ReadOnly = true;
this.txtTxLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.txtTxLog.Size = new System.Drawing.Size(500, 463);
this.txtTxLog.TabIndex = 0;
//
// tabInfo
//
this.tabInfo.Controls.Add(this.txtInfoLog);
this.tabInfo.Location = new System.Drawing.Point(4, 22);
this.tabInfo.Name = "tabInfo";
this.tabInfo.Size = new System.Drawing.Size(506, 469);
this.tabInfo.TabIndex = 2;
this.tabInfo.Text = "정보";
this.tabInfo.UseVisualStyleBackColor = true;
//
// txtInfoLog
//
this.txtInfoLog.Dock = System.Windows.Forms.DockStyle.Fill;
this.txtInfoLog.Font = new System.Drawing.Font("Consolas", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.txtInfoLog.Location = new System.Drawing.Point(0, 0);
this.txtInfoLog.Multiline = true;
this.txtInfoLog.Name = "txtInfoLog";
this.txtInfoLog.ReadOnly = true;
this.txtInfoLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.txtInfoLog.Size = new System.Drawing.Size(506, 469);
this.txtInfoLog.TabIndex = 0;
//
// grpAGVStatus
//
this.grpAGVStatus.Controls.Add(this.lblLastTagValue);
this.grpAGVStatus.Controls.Add(this.lblLastTag);
this.grpAGVStatus.Controls.Add(this.lblLiftStValue);
this.grpAGVStatus.Controls.Add(this.lblLiftSt);
this.grpAGVStatus.Controls.Add(this.lblCartStValue);
this.grpAGVStatus.Controls.Add(this.lblCartSt);
this.grpAGVStatus.Controls.Add(this.lblChargeStValue);
this.grpAGVStatus.Controls.Add(this.lblChargeSt);
this.grpAGVStatus.Controls.Add(this.lblInpositionValue);
this.grpAGVStatus.Controls.Add(this.lblInposition);
this.grpAGVStatus.Controls.Add(this.lblDirectionValue);
this.grpAGVStatus.Controls.Add(this.lblDirection);
this.grpAGVStatus.Controls.Add(this.lblRunStValue);
this.grpAGVStatus.Controls.Add(this.lblRunSt);
this.grpAGVStatus.Controls.Add(this.lblModeValue);
this.grpAGVStatus.Controls.Add(this.lblMode);
this.grpAGVStatus.Location = new System.Drawing.Point(12, 456);
this.grpAGVStatus.Name = "grpAGVStatus";
this.grpAGVStatus.Size = new System.Drawing.Size(466, 120);
this.grpAGVStatus.TabIndex = 4;
this.grpAGVStatus.TabStop = false;
this.grpAGVStatus.Text = "AGV 상태";
//
// lblLastTagValue
//
this.lblLastTagValue.AutoSize = true;
this.lblLastTagValue.Font = new System.Drawing.Font("Consolas", 9F, System.Drawing.FontStyle.Bold);
this.lblLastTagValue.Location = new System.Drawing.Point(310, 98);
this.lblLastTagValue.Name = "lblLastTagValue";
this.lblLastTagValue.Size = new System.Drawing.Size(14, 14);
this.lblLastTagValue.TabIndex = 15;
this.lblLastTagValue.Text = "-";
//
// lblLastTag
//
this.lblLastTag.AutoSize = true;
this.lblLastTag.Location = new System.Drawing.Point(240, 98);
this.lblLastTag.Name = "lblLastTag";
this.lblLastTag.Size = new System.Drawing.Size(57, 12);
this.lblLastTag.TabIndex = 14;
this.lblLastTag.Text = "현재태그:";
//
// lblLiftStValue
//
this.lblLiftStValue.AutoSize = true;
this.lblLiftStValue.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Bold);
this.lblLiftStValue.Location = new System.Drawing.Point(80, 98);
this.lblLiftStValue.Name = "lblLiftStValue";
this.lblLiftStValue.Size = new System.Drawing.Size(12, 12);
this.lblLiftStValue.TabIndex = 13;
this.lblLiftStValue.Text = "-";
//
// lblLiftSt
//
this.lblLiftSt.AutoSize = true;
this.lblLiftSt.Location = new System.Drawing.Point(10, 98);
this.lblLiftSt.Name = "lblLiftSt";
this.lblLiftSt.Size = new System.Drawing.Size(45, 12);
this.lblLiftSt.TabIndex = 12;
this.lblLiftSt.Text = "리프트:";
//
// lblCartStValue
//
this.lblCartStValue.AutoSize = true;
this.lblCartStValue.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Bold);
this.lblCartStValue.Location = new System.Drawing.Point(310, 73);
this.lblCartStValue.Name = "lblCartStValue";
this.lblCartStValue.Size = new System.Drawing.Size(12, 12);
this.lblCartStValue.TabIndex = 11;
this.lblCartStValue.Text = "-";
//
// lblCartSt
//
this.lblCartSt.AutoSize = true;
this.lblCartSt.Location = new System.Drawing.Point(240, 73);
this.lblCartSt.Name = "lblCartSt";
this.lblCartSt.Size = new System.Drawing.Size(33, 12);
this.lblCartSt.TabIndex = 10;
this.lblCartSt.Text = "카트:";
//
// lblChargeStValue
//
this.lblChargeStValue.AutoSize = true;
this.lblChargeStValue.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Bold);
this.lblChargeStValue.Location = new System.Drawing.Point(80, 73);
this.lblChargeStValue.Name = "lblChargeStValue";
this.lblChargeStValue.Size = new System.Drawing.Size(12, 12);
this.lblChargeStValue.TabIndex = 9;
this.lblChargeStValue.Text = "-";
//
// lblChargeSt
//
this.lblChargeSt.AutoSize = true;
this.lblChargeSt.Location = new System.Drawing.Point(10, 73);
this.lblChargeSt.Name = "lblChargeSt";
this.lblChargeSt.Size = new System.Drawing.Size(33, 12);
this.lblChargeSt.TabIndex = 8;
this.lblChargeSt.Text = "충전:";
//
// lblInpositionValue
//
this.lblInpositionValue.AutoSize = true;
this.lblInpositionValue.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Bold);
this.lblInpositionValue.Location = new System.Drawing.Point(310, 48);
this.lblInpositionValue.Name = "lblInpositionValue";
this.lblInpositionValue.Size = new System.Drawing.Size(12, 12);
this.lblInpositionValue.TabIndex = 7;
this.lblInpositionValue.Text = "-";
//
// lblInposition
//
this.lblInposition.AutoSize = true;
this.lblInposition.Location = new System.Drawing.Point(240, 48);
this.lblInposition.Name = "lblInposition";
this.lblInposition.Size = new System.Drawing.Size(57, 12);
this.lblInposition.TabIndex = 6;
this.lblInposition.Text = "도달완료:";
//
// lblDirectionValue
//
this.lblDirectionValue.AutoSize = true;
this.lblDirectionValue.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Bold);
this.lblDirectionValue.Location = new System.Drawing.Point(80, 48);
this.lblDirectionValue.Name = "lblDirectionValue";
this.lblDirectionValue.Size = new System.Drawing.Size(12, 12);
this.lblDirectionValue.TabIndex = 5;
this.lblDirectionValue.Text = "-";
//
// lblRunStValue
//
this.lblRunStValue.AutoSize = true;
this.lblRunStValue.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Bold);
this.lblRunStValue.Location = new System.Drawing.Point(310, 23);
this.lblRunStValue.Name = "lblRunStValue";
this.lblRunStValue.Size = new System.Drawing.Size(12, 12);
this.lblRunStValue.TabIndex = 3;
this.lblRunStValue.Text = "-";
//
// lblRunSt
//
this.lblRunSt.AutoSize = true;
this.lblRunSt.Location = new System.Drawing.Point(240, 23);
this.lblRunSt.Name = "lblRunSt";
this.lblRunSt.Size = new System.Drawing.Size(57, 12);
this.lblRunSt.TabIndex = 2;
this.lblRunSt.Text = "실행상태:";
//
// lblModeValue
//
this.lblModeValue.AutoSize = true;
this.lblModeValue.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Bold);
this.lblModeValue.Location = new System.Drawing.Point(80, 23);
this.lblModeValue.Name = "lblModeValue";
this.lblModeValue.Size = new System.Drawing.Size(12, 12);
this.lblModeValue.TabIndex = 1;
this.lblModeValue.Text = "-";
//
// lblMode
//
this.lblMode.AutoSize = true;
this.lblMode.Location = new System.Drawing.Point(10, 23);
this.lblMode.Name = "lblMode";
this.lblMode.Size = new System.Drawing.Size(33, 12);
this.lblMode.TabIndex = 0;
this.lblMode.Text = "모드:";
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1016, 588);
this.Controls.Add(this.grpAGVStatus);
this.Controls.Add(this.grpLogs);
this.Controls.Add(this.grpCommands);
this.Controls.Add(this.grpAGV);
this.Controls.Add(this.grpConnection);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.MaximizeBox = false;
this.Name = "MainForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "ACS 시뮬레이터 - ENIG AGV 테스트";
this.grpConnection.ResumeLayout(false);
this.grpConnection.PerformLayout();
this.grpAGV.ResumeLayout(false);
this.grpAGV.PerformLayout();
this.grpCommands.ResumeLayout(false);
this.grpCommands.PerformLayout();
this.grpLift.ResumeLayout(false);
this.grpManual.ResumeLayout(false);
this.grpManual.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.numRuntime)).EndInit();
this.grpLogs.ResumeLayout(false);
this.tabLogs.ResumeLayout(false);
this.tabRX.ResumeLayout(false);
this.tabRX.PerformLayout();
this.tabTX.ResumeLayout(false);
this.tabTX.PerformLayout();
this.tabInfo.ResumeLayout(false);
this.tabInfo.PerformLayout();
this.grpAGVStatus.ResumeLayout(false);
this.grpAGVStatus.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.GroupBox grpConnection;
private System.Windows.Forms.Button btnConnect;
private System.Windows.Forms.TextBox txtBaudRate;
private System.Windows.Forms.ComboBox cmbPort;
private System.Windows.Forms.Label lblBaudRate;
private System.Windows.Forms.Label lblPort;
private System.Windows.Forms.GroupBox grpAGV;
private System.Windows.Forms.RadioButton rbAGV2;
private System.Windows.Forms.RadioButton rbAGV1;
private System.Windows.Forms.GroupBox grpCommands;
private System.Windows.Forms.Button btnSetCurrent;
private System.Windows.Forms.TextBox txtRFID;
private System.Windows.Forms.Label lblRFID;
private System.Windows.Forms.Button btnGoto;
private System.Windows.Forms.Button btnStop;
private System.Windows.Forms.Button btnReset;
private System.Windows.Forms.Button btnMarkStop;
private System.Windows.Forms.CheckBox chkMarkStop;
private System.Windows.Forms.GroupBox grpManual;
private System.Windows.Forms.Button btnManual;
private System.Windows.Forms.NumericUpDown numRuntime;
private System.Windows.Forms.Label lblRuntime;
private System.Windows.Forms.ComboBox cmbSpeed;
private System.Windows.Forms.Label lblSpeed;
private System.Windows.Forms.ComboBox cmbDirection;
private System.Windows.Forms.GroupBox grpLift;
private System.Windows.Forms.Button btnLiftStop;
private System.Windows.Forms.Button btnLiftDown;
private System.Windows.Forms.Button btnLiftUp;
private System.Windows.Forms.GroupBox grpLogs;
private System.Windows.Forms.TabControl tabLogs;
private System.Windows.Forms.TabPage tabTX;
private System.Windows.Forms.TextBox txtTxLog;
private System.Windows.Forms.TabPage tabRX;
private System.Windows.Forms.TextBox txtRxLog;
private System.Windows.Forms.TabPage tabInfo;
private System.Windows.Forms.TextBox txtInfoLog;
private System.Windows.Forms.Button btnRefresh;
private System.Windows.Forms.Button btnGotoAlias;
private System.Windows.Forms.TextBox txtAlias;
private System.Windows.Forms.Label lblAlias;
private System.Windows.Forms.GroupBox grpAGVStatus;
private System.Windows.Forms.Label lblLastTagValue;
private System.Windows.Forms.Label lblLastTag;
private System.Windows.Forms.Label lblLiftStValue;
private System.Windows.Forms.Label lblLiftSt;
private System.Windows.Forms.Label lblCartStValue;
private System.Windows.Forms.Label lblCartSt;
private System.Windows.Forms.Label lblChargeStValue;
private System.Windows.Forms.Label lblChargeSt;
private System.Windows.Forms.Label lblInpositionValue;
private System.Windows.Forms.Label lblInposition;
private System.Windows.Forms.Label lblDirectionValue;
private System.Windows.Forms.Label lblDirection;
private System.Windows.Forms.Label lblRunStValue;
private System.Windows.Forms.Label lblRunSt;
private System.Windows.Forms.Label lblModeValue;
private System.Windows.Forms.Label lblMode;
}
}

View File

@@ -0,0 +1,515 @@
using System;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ENIG;
using ENIGProtocol;
namespace Test_ACS
{
public partial class MainForm : Form
{
private SerialPort serialPort;
private EEProtocol protocol;
private byte selectedAGV = 11; // 기본값: AGV1 (11)
private AppSettings settings;
public MainForm()
{
InitializeComponent();
InitializeProtocol();
LoadPortList();
InitializeComboBoxes();
}
private void InitializeComboBoxes()
{
// Direction combobox default
if (cmbDirection.Items.Count > 0) cmbDirection.SelectedIndex = 1; // 전진
// Speed combobox default
if (cmbSpeed.Items.Count > 0) cmbSpeed.SelectedIndex = 1; // 보통
}
private void InitializeProtocol()
{
protocol = new EEProtocol();
protocol.OnDataReceived += Protocol_OnDataReceived;
protocol.OnMessage += Protocol_OnMessage;
serialPort = new SerialPort();
serialPort.ReadTimeout = 2000;
serialPort.WriteTimeout = 1000;
serialPort.DataReceived += SerialPort_DataReceived;
}
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
var buffer = new byte[serialPort.BytesToRead];
serialPort.Read(buffer, 0, buffer.Length);
protocol.ProcessReceivedData(buffer);
}
private void Protocol_OnDataReceived(object sender, EEProtocol.DataEventArgs e)
{
var hexstrRaw = string.Join(" ", e.ReceivedPacket.RawData.Select(b => b.ToString("X2")));
var hexstr = string.Join(" ", e.ReceivedPacket.Data.Select(b => b.ToString("X2")));
var cmd = e.ReceivedPacket.Command.ToString("X2");
var id = e.ReceivedPacket.ID.ToString("X2");
AddLog($"RX: {hexstrRaw}\nID:{id}, CMD:{cmd}, DATA:{hexstr}", LogType.RX);
// AGV 상태 수신 처리 (cmd = 3)
if (e.ReceivedPacket.Command == (byte)ENIGProtocol.AGVCommandEH.Status)
{
UpdateAGVStatus(e.ReceivedPacket.Data);
}
}
private void Protocol_OnMessage(object sender, EEProtocol.MessageEventArgs e)
{
AddLog(e.Message, e.IsError ? LogType.Error : LogType.Info);
}
private void LoadPortList()
{
cmbPort.Items.Clear();
foreach (var port in SerialPort.GetPortNames())
{
cmbPort.Items.Add(port);
}
// 마지막 설정 불러오기
LoadSettings();
}
private void LoadSettings()
{
try
{
// 설정 파일 로드
settings = AppSettings.Load();
// 포트 설정
if (!string.IsNullOrEmpty(settings.LastPort) && cmbPort.Items.Contains(settings.LastPort))
{
cmbPort.SelectedItem = settings.LastPort;
}
else if (cmbPort.Items.Count > 0)
{
cmbPort.SelectedIndex = 0;
}
// 보레이트 설정
txtBaudRate.Text = settings.LastBaudRate;
// RFID 번호 설정
txtRFID.Text = settings.LastRFID;
// 별칭 설정
txtAlias.Text = settings.LastAlias;
// AGV 선택 설정
if (settings.LastAGV == 11)
{
rbAGV1.Checked = true;
}
else if (settings.LastAGV == 12)
{
rbAGV2.Checked = true;
}
AddLog("설정을 불러왔습니다.", LogType.Info);
}
catch (Exception ex)
{
settings = new AppSettings();
AddLog($"설정 불러오기 실패: {ex.Message}", LogType.Error);
}
}
private void SaveSettings()
{
try
{
if (settings == null)
settings = new AppSettings();
settings.LastPort = cmbPort.Text;
settings.LastBaudRate = txtBaudRate.Text;
settings.LastRFID = txtRFID.Text;
settings.LastAlias = txtAlias.Text;
settings.LastAGV = selectedAGV;
settings.Save();
}
catch (Exception ex)
{
AddLog($"설정 저장 실패: {ex.Message}", LogType.Error);
}
}
private void btnConnect_Click(object sender, EventArgs e)
{
if (serialPort.IsOpen)
{
serialPort.Close();
btnConnect.Text = "연결";
AddLog("포트 닫힘", LogType.Info);
}
else
{
try
{
serialPort.PortName = cmbPort.Text;
serialPort.BaudRate = int.Parse(txtBaudRate.Text);
serialPort.Open();
btnConnect.Text = "해제";
AddLog($"{serialPort.PortName}:{serialPort.BaudRate} 연결됨", LogType.Info);
}
catch (Exception ex)
{
AddLog($"연결 실패: {ex.Message}", LogType.Error);
}
}
}
private void btnRefresh_Click(object sender, EventArgs e)
{
LoadPortList();
}
private void cmbPort_SelectedIndexChanged(object sender, EventArgs e)
{
SaveSettings();
}
private void txtBaudRate_TextChanged(object sender, EventArgs e)
{
SaveSettings();
}
private void txtRFID_TextChanged(object sender, EventArgs e)
{
SaveSettings();
}
private void txtAlias_TextChanged(object sender, EventArgs e)
{
SaveSettings();
}
private void rbAGV1_CheckedChanged(object sender, EventArgs e)
{
if (rbAGV1.Checked)
{
selectedAGV = 11;
SaveSettings();
}
}
private void rbAGV2_CheckedChanged(object sender, EventArgs e)
{
if (rbAGV2.Checked)
{
selectedAGV = 12;
SaveSettings();
}
}
private void btnSetCurrent_Click(object sender, EventArgs e)
{
// SetCurrent: data = TargetID(2 hex) + RFID(4 hex)
var rfid = txtRFID.Text.PadLeft(4, '0');
var targetID = selectedAGV.ToString("X2");
var dataStr = targetID + rfid;
SendCommand(AGVCommandHE.SetCurrent, dataStr);
}
private void btnGoto_Click(object sender, EventArgs e)
{
// Goto: data = TargetID(2 hex) + RFID(4 hex)
var rfid = txtRFID.Text.PadLeft(4, '0');
var targetID = selectedAGV.ToString("X2");
var dataStr = targetID + rfid;
SendCommand(AGVCommandHE.Goto, dataStr);
}
private void btnGotoAlias_Click(object sender, EventArgs e)
{
// GotoAlias: data = TargetID(2 hex) + Alias(ASCII string)
var alias = txtAlias.Text.Trim();
if (string.IsNullOrEmpty(alias))
{
AddLog("별칭을 입력하세요.", LogType.Error);
return;
}
var targetID = selectedAGV.ToString("X2");
var aliasBytes = Encoding.ASCII.GetBytes(alias);
var aliasHex = string.Join("", aliasBytes.Select(b => b.ToString("X2")));
var dataStr = targetID + aliasHex;
SendCommand(AGVCommandHE.GotoAlias, dataStr);
}
private void btnStop_Click(object sender, EventArgs e)
{
// Stop: data = TargetID(2 hex)
var targetID = selectedAGV.ToString("X2");
SendCommand(AGVCommandHE.Stop, targetID);
}
private void btnReset_Click(object sender, EventArgs e)
{
// Reset: data = TargetID(2 hex)
var targetID = selectedAGV.ToString("X2");
SendCommand(AGVCommandHE.Reset, targetID);
}
private void btnManual_Click(object sender, EventArgs e)
{
// Manual: data = TargetID(2 hex) + Direction(1 byte) + Speed(1 byte) + Runtime(1 byte)
var targetID = selectedAGV.ToString("X2");
var direction = (byte)cmbDirection.SelectedIndex;
var speed = (byte)cmbSpeed.SelectedIndex;
var runtime = (byte)numRuntime.Value;
var dataBytes = new byte[] { direction, speed, runtime };
var dataStr = targetID + string.Join("", dataBytes.Select(b => b.ToString("X2")));
SendCommand(AGVCommandHE.Manual, dataStr);
}
private void btnMarkStop_Click(object sender, EventArgs e)
{
// MarkStop: data = TargetID(2 hex) + MarkStop(1 byte)
var targetID = selectedAGV.ToString("X2");
var markStop = chkMarkStop.Checked ? "01" : "00";
SendCommand(AGVCommandHE.MarkStop, targetID + markStop);
}
private void btnLiftUp_Click(object sender, EventArgs e)
{
SendLiftCommand(1); // Up
}
private void btnLiftDown_Click(object sender, EventArgs e)
{
SendLiftCommand(2); // Down
}
private void btnLiftStop_Click(object sender, EventArgs e)
{
SendLiftCommand(0); // Stop
}
private void SendLiftCommand(byte liftCmd)
{
// LiftControl: data = TargetID(2 hex) + LiftCommand(1 byte)
var targetID = selectedAGV.ToString("X2");
var dataStr = targetID + liftCmd.ToString("X2");
SendCommand(AGVCommandHE.LiftControl, dataStr);
}
private void SendCommand(AGVCommandHE command, string dataHexString)
{
if (!serialPort.IsOpen)
{
AddLog("포트가 연결되지 않았습니다.", LogType.Error);
return;
}
try
{
// Hex 문자열을 byte 배열로 변환
byte[] dataBytes = new byte[dataHexString.Length / 2];
for (int i = 0; i < dataBytes.Length; i++)
{
dataBytes[i] = Convert.ToByte(dataHexString.Substring(i * 2, 2), 16);
}
// 패킷 생성 (ACS ID = 0)
byte[] packet = protocol.CreatePacket(0, (byte)command, dataBytes);
// 전송
serialPort.Write(packet, 0, packet.Length);
var hexString = string.Join(" ", packet.Select(b => b.ToString("X2")));
AddLog($"TX: {hexString}\nCMD: {command} ({(byte)command:X2}), DATA: {dataHexString}", LogType.TX);
}
catch (Exception ex)
{
AddLog($"전송 실패: {ex.Message}", LogType.Error);
}
}
private void UpdateAGVStatus(byte[] data)
{
if (data.Length < 13)
{
AddLog($"AGV 상태 데이터 길이 오류: {data.Length} bytes", LogType.Error);
return;
}
if (InvokeRequired)
{
BeginInvoke(new Action(() => UpdateAGVStatus(data)));
return;
}
try
{
// Mode[1]: 0=manual, 1=auto
lblModeValue.Text = data[0] == 0 ? "수동" : "자동";
lblModeValue.ForeColor = data[0] == 0 ? Color.Blue : Color.Green;
// RunSt[1]: 0=stop, 1=run, 2=error
switch (data[1])
{
case 0:
lblRunStValue.Text = "정지";
lblRunStValue.ForeColor = Color.Gray;
break;
case 1:
lblRunStValue.Text = "실행";
lblRunStValue.ForeColor = Color.Green;
break;
case 2:
lblRunStValue.Text = "에러";
lblRunStValue.ForeColor = Color.Red;
break;
default:
lblRunStValue.Text = "알 수 없음";
lblRunStValue.ForeColor = Color.Black;
break;
}
// Direction[1]: 0=straight, 1=left, 2=right, 3=markstop
switch (data[2])
{
case 0:
lblDirectionValue.Text = "직진";
break;
case 1:
lblDirectionValue.Text = "좌회전";
break;
case 2:
lblDirectionValue.Text = "우회전";
break;
case 3:
lblDirectionValue.Text = "마크정지";
break;
default:
lblDirectionValue.Text = "알 수 없음";
break;
}
// Inposition[1]: 0=off, 1=on
lblInpositionValue.Text = data[3] == 0 ? "OFF" : "ON";
lblInpositionValue.ForeColor = data[3] == 0 ? Color.Gray : Color.Green;
// ChargeSt[1]: 0=off, 1=on
lblChargeStValue.Text = data[4] == 0 ? "OFF" : "ON";
lblChargeStValue.ForeColor = data[4] == 0 ? Color.Gray : Color.Orange;
// CartSt[1]: 0=off, 1=on, 2=unknown
switch (data[5])
{
case 0:
lblCartStValue.Text = "없음";
lblCartStValue.ForeColor = Color.Gray;
break;
case 1:
lblCartStValue.Text = "있음";
lblCartStValue.ForeColor = Color.Green;
break;
case 2:
lblCartStValue.Text = "알 수 없음";
lblCartStValue.ForeColor = Color.Orange;
break;
default:
lblCartStValue.Text = "오류";
lblCartStValue.ForeColor = Color.Red;
break;
}
// LiftSt[1]: 0=down, 1=up, 2=unknown
switch (data[6])
{
case 0:
lblLiftStValue.Text = "하강";
lblLiftStValue.ForeColor = Color.Blue;
break;
case 1:
lblLiftStValue.Text = "상승";
lblLiftStValue.ForeColor = Color.Green;
break;
case 2:
lblLiftStValue.Text = "알 수 없음";
lblLiftStValue.ForeColor = Color.Orange;
break;
default:
lblLiftStValue.Text = "오류";
lblLiftStValue.ForeColor = Color.Red;
break;
}
// LastTag[6]: "000000"
string lastTag = Encoding.ASCII.GetString(data, 7, 6);
lblLastTagValue.Text = lastTag;
lblLastTagValue.ForeColor = Color.Black;
}
catch (Exception ex)
{
AddLog($"AGV 상태 업데이트 실패: {ex.Message}", LogType.Error);
}
}
private enum LogType { TX, RX, Info, Error }
private void AddLog(string message, LogType type)
{
if (InvokeRequired)
{
BeginInvoke(new Action(() => AddLog(message, type)));
return;
}
var timestamp = DateTime.Now.ToString("HH:mm:ss");
var logMessage = $"[{timestamp}] {message}\r\n";
switch (type)
{
case LogType.TX:
txtTxLog.AppendText(logMessage);
txtTxLog.ScrollToCaret();
break;
case LogType.RX:
txtRxLog.AppendText(logMessage);
txtRxLog.ScrollToCaret();
break;
case LogType.Info:
txtInfoLog.AppendText(logMessage);
txtInfoLog.ScrollToCaret();
break;
case LogType.Error:
txtInfoLog.ForeColor = Color.Red;
txtInfoLog.AppendText(logMessage);
txtInfoLog.ForeColor = Color.Black;
txtInfoLog.ScrollToCaret();
break;
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
// 설정 저장
SaveSettings();
if (serialPort != null && serialPort.IsOpen)
{
serialPort.Close();
}
base.OnFormClosing(e);
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,16 @@
using System;
using System.Windows.Forms;
namespace Test_ACS
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}

View File

@@ -0,0 +1,19 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("Test_ACS")]
[assembly: AssemblyDescription("ACS Simulator for ENIG AGV Testing")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("ENIG")]
[assembly: AssemblyProduct("Test_ACS")]
[assembly: AssemblyCopyright("Copyright © 2025")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
// 런타임 버전:4.0.30319.42000
//
// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
// 이러한 변경 내용이 손실됩니다.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Test_ACS.Properties {
using System;
/// <summary>
/// 지역화된 문자열 등을 찾기 위한 강력한 형식의 리소스 클래스입니다.
/// </summary>
// 이 클래스는 ResGen 또는 Visual Studio와 같은 도구를 통해 StronglyTypedResourceBuilder
// 클래스에서 자동으로 생성되었습니다.
// 멤버를 추가하거나 제거하려면 .ResX 파일을 편집한 다음 /str 옵션을 사용하여 ResGen을
// 다시 실행하거나 VS 프로젝트를 다시 빌드하십시오.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// 이 클래스에서 사용하는 캐시된 ResourceManager 인스턴스를 반환합니다.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Test_ACS.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// 이 강력한 형식의 리소스 클래스를 사용하여 모든 리소스 조회에 대한 현재 스레드의 CurrentUICulture
/// 속성을 재정의합니다.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,86 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
// 런타임 버전:4.0.30319.42000
//
// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
// 이러한 변경 내용이 손실됩니다.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Test_ACS.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("COM1")]
public string LastPort {
get {
return ((string)(this["LastPort"]));
}
set {
this["LastPort"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("9600")]
public string LastBaudRate {
get {
return ((string)(this["LastBaudRate"]));
}
set {
this["LastBaudRate"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("0001")]
public string LastRFID {
get {
return ((string)(this["LastRFID"]));
}
set {
this["LastRFID"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("CHARGER1")]
public string LastAlias {
get {
return ((string)(this["LastAlias"]));
}
set {
this["LastAlias"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("11")]
public int LastAGV {
get {
return ((int)(this["LastAGV"]));
}
set {
this["LastAGV"] = value;
}
}
}
}

View File

@@ -0,0 +1,23 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="Test_ACS.Properties" GeneratedClassName="Settings">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings>
<Setting Name="LastPort" Type="System.String" Scope="User">
<Value Profile="(Default)">COM1</Value>
</Setting>
<Setting Name="LastBaudRate" Type="System.String" Scope="User">
<Value Profile="(Default)">9600</Value>
</Setting>
<Setting Name="LastRFID" Type="System.String" Scope="User">
<Value Profile="(Default)">0001</Value>
</Setting>
<Setting Name="LastAlias" Type="System.String" Scope="User">
<Value Profile="(Default)">CHARGER1</Value>
</Setting>
<Setting Name="LastAGV" Type="System.Int32" Scope="User">
<Value Profile="(Default)">11</Value>
</Setting>
</Settings>
</SettingsFile>

View File

@@ -0,0 +1,242 @@
# Test_ACS - ACS 시뮬레이터
ENIG AGV 프로젝트를 위한 ACS(중앙제어시스템) 시뮬레이터입니다.
## 프로젝트 개요
ACS는 AGV에게 명령을 내리는 중앙 제어 시스템입니다. 이 프로그램은 실제 ACS 대신 테스트 목적으로 AGV에 명령을 보낼 수 있는 시뮬레이터입니다.
### 통신 방식
- **프로토콜**: ENIG Protocol (ENIGProtocol 프로젝트 사용)
- **통신**: RS232 (Xbee 모듈)
- **ACS Xbee ID**: 0
- **AGV Xbee ID**:
- AGV1: 11
- AGV2: 12
## 프로젝트 구조
```
Test_ACS/
├── Test_ACS.csproj # 프로젝트 파일
├── Program.cs # 진입점
├── MainForm.cs # 메인 폼 로직
├── MainForm.Designer.cs # 메인 폼 디자이너
├── MainForm.resx # 메인 폼 리소스
├── Properties/ # 프로젝트 속성
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
└── app.config # 애플리케이션 설정
```
## 빌드 방법
1. Visual Studio 2022 또는 MSBuild 사용
2. .NET Framework 4.8 필요
3. ENIGProtocol 프로젝트 참조 필요
```bash
# Visual Studio에서 빌드
- 솔루션 탐색기에서 Test_ACS 프로젝트 선택
- 빌드 > 솔루션 빌드
# 또는 MSBuild 사용
msbuild Test_ACS.csproj /p:Configuration=Debug
```
## 사용 방법
### 1. 연결 설정
1. **COM 포트 선택**: Xbee 모듈이 연결된 COM 포트 선택
2. **보레이트 입력**: 기본값 9600 (Xbee 설정에 맞춰 조정)
3. **연결 버튼 클릭**: 포트 연결
**자동 저장**: 포트와 보레이트는 변경 시 자동으로 저장되며, 다음 실행 시 자동으로 불러옵니다.
### 2. AGV 선택
- **AGV1 (ID:11)**: 첫 번째 AGV 선택
- **AGV2 (ID:12)**: 두 번째 AGV 선택
**자동 저장**: 선택한 AGV는 자동으로 저장되며, 다음 실행 시 자동으로 선택됩니다.
### 3. 명령 전송
#### SetCurrent (현재 위치 설정)
- **RFID 번호 입력**: 예) 0001
- **SetCurrent 버튼 클릭**: AGV의 현재 위치를 지정한 RFID로 설정
- **자동 저장**: 입력한 RFID 번호는 자동으로 저장됩니다.
#### Goto (RFID 이동 명령)
- **RFID 번호 입력**: 목표 위치 RFID (예: 0001)
- **Goto 버튼 클릭**: AGV를 지정한 RFID 위치로 이동
- **자동 저장**: 입력한 RFID 번호는 자동으로 저장됩니다.
#### GotoAlias (별칭 이동 명령)
- **별칭(Alias) 입력**: 목표 위치 별칭 (예: CHARGER1, BUFFER2, STATION_A)
- **GotoAlias 버튼 클릭**: AGV를 지정한 별칭 위치로 이동
- **참고**: 별칭은 AGV에 미리 등록된 이름으로, RFID 대신 의미 있는 이름 사용
- **자동 저장**: 입력한 별칭은 자동으로 저장됩니다.
#### Stop (정지)
- **Stop 버튼 클릭**: AGV 즉시 정지
#### Reset (에러 리셋)
- **Reset 버튼 클릭**: AGV 에러 상태 초기화
#### Manual (수동 제어)
- **방향 선택**: 후진, 전진, 좌회전, 우회전
- **속도 선택**: 느림, 보통, 빠름
- **시간(초) 입력**: 이동 시간
- **수동 이동 실행 버튼 클릭**
#### MarkStop (마크센서 정지)
- **정지 설정 체크**: 마크센서 정지 활성화/비활성화
- **마크센서 정지 버튼 클릭**
#### LiftControl (리프트 제어)
- **리프트 상승**: 리프트 올리기
- **리프트 하강**: 리프트 내리기
- **리프트 정지**: 리프트 동작 중지
## AGV 상태 확인
프로그램 하단의 "AGV 상태" 그룹박스에서 실시간 AGV 상태를 확인할 수 있습니다:
- **모드**: 수동/자동
- **실행상태**: 정지/실행/에러
- **방향**: 직진/좌회전/우회전/마크정지
- **도달완료**: 목적지 도달 여부 (OFF/ON)
- **충전**: 충전 상태 (OFF/ON)
- **카트**: 카트 감지 상태 (없음/있음/알 수 없음)
- **리프트**: 리프트 위치 (하강/상승/알 수 없음)
- **현재태그**: 마지막으로 인식한 RFID 태그 번호
상태는 AGV가 주기적으로 전송하는 Status 메시지(cmd=3)를 수신하여 자동으로 업데이트됩니다.
## 로그 확인
프로그램 우측에 3개의 탭으로 로그를 확인할 수 있습니다:
- **송신 (TX)**: ACS에서 AGV로 보낸 명령
- **수신 (RX)**: AGV에서 ACS로 받은 응답
- **정보**: 연결 상태, 설정 저장/불러오기, 에러 메시지
## 설정 저장 위치
모든 설정은 실행 파일과 같은 폴더에 저장됩니다:
- **위치**: `Test_ACS.exe`와 같은 폴더의 `Test_ACS.config` 파일
- **형식**: JSON 형식으로 저장
- **저장 항목**:
- 마지막 사용 COM 포트
- 마지막 사용 보레이트
- 마지막 입력 RFID 번호
- 마지막 입력 별칭
- 마지막 선택 AGV (11 또는 12)
- **자동 저장**: 설정 변경 시 자동 저장
- **자동 불러오기**: 프로그램 시작 시 자동 불러오기
예시 설정 파일 (`Test_ACS.config`):
```json
{
"LastPort": "COM3",
"LastBaudRate": "9600",
"LastRFID": "0001",
"LastAlias": "CHARGER1",
"LastAGV": 11
}
```
## 프로토콜 상세
### 패킷 구조
```
STX (0x02) + Length + ID + Command + Data + CRC16 (2 bytes) + ETX (0x03)
```
### ACS 명령어 (AGVCommandHE)
| 명령 | 코드 | 데이터 형식 | 설명 |
|------|------|-------------|------|
| Goto | 100 | TargetID(2) + RFID(4) | RFID로 목표 위치로 이동 |
| Stop | 101 | TargetID(2) | 정지 |
| Reset | 102 | TargetID(2) | 에러 리셋 |
| SetCurrent | 103 | TargetID(2) + RFID(4) | 현재 위치 설정 |
| Manual | 104 | TargetID(2) + Direction(1) + Speed(1) + Runtime(1) | 수동 이동 |
| MarkStop | 105 | TargetID(2) + MarkStop(1) | 마크센서 정지 |
| LiftControl | 106 | TargetID(2) + LiftCommand(1) | 리프트 제어 |
| **GotoAlias** | **107** | **TargetID(2) + Alias(ASCII)** | **별칭으로 목표 위치로 이동** |
### 데이터 상세
#### Direction (Manual 명령)
- 0: 후진 (Back)
- 1: 전진 (Forward)
- 2: 좌회전 (Left)
- 3: 우회전 (Right)
#### Speed (Manual 명령)
- 0: 느림 (Slow)
- 1: 보통 (Normal)
- 2: 빠름 (Fast)
#### LiftCommand (LiftControl 명령)
- 0: 정지 (Stop)
- 1: 상승 (Up)
- 2: 하강 (Down)
## 참고 파일
- **프로토콜 정의**: `SubProject\EnigProtocol\enigprotocol\Commands.cs`
- **프로토콜 구현**: `SubProject\EnigProtocol\enigprotocol\EEProtocol.cs`
- **AGV 수신 처리**: `Project\StateMachine\_Xbee.cs`
## 개발자 노트
### 프로젝트 참조
- ENIGProtocol 프로젝트를 참조하여 프로토콜 기능 사용
- ProjectReference GUID: {499d8912-4b96-41e5-a70d-cfe797883d65}
### 주요 클래스
- `EEProtocol`: 패킷 생성 및 파싱
- `SerialPort`: RS232 통신 관리
- `MainForm`: UI 및 명령 전송 로직
### 디버깅 팁
1. 로그 탭에서 송수신 패킷의 HEX 값 확인
2. CRC 에러 발생 시 프로토콜 버전 확인
3. 응답이 없을 경우 Xbee 모듈 설정 확인 (PAN ID: 46A5, Channel: 17)
## 라이센스
ENIG AGV 프로젝트의 일부로, 내부 테스트 용도로 사용됩니다.
## 버전 이력
- v1.4.0 (2025-01-12): 설정 파일 저장 방식 변경
- 실행 폴더에 JSON 형식으로 설정 저장 (Test_ACS.config)
- 설정 파일 직접 편집 가능
- 프로그램 이동 시 설정 파일도 함께 이동
- v1.3.0 (2025-01-12): AGV 상태 실시간 표시 기능 추가
- AGV 상태 그룹박스 추가 (모드, 실행상태, 방향, 도달완료, 충전, 카트, 리프트, 현재태그)
- Status 메시지(cmd=3) 자동 수신 및 UI 업데이트
- 상태별 색상 표시로 직관적 모니터링
- 프로토콜 문서 업데이트 (LastTag 6바이트, CurrentPath 제거)
- v1.2.0 (2025-01-12): 설정 자동 저장/불러오기
- COM 포트, 보레이트, RFID, 별칭, AGV 선택 자동 저장
- 프로그램 시작 시 마지막 설정 자동 복원
- v1.1.0 (2025-01-12): GotoAlias 명령어 추가
- 별칭(Alias) 기반 이동 명령 지원
- UI 레이아웃 최적화
- v1.0.0 (2025-01-11): 초기 버전 생성
- ACS 시뮬레이터 기본 기능
- ENIGProtocol 연동
- 7가지 ACS 명령어 지원 (Goto, Stop, Reset, SetCurrent, Manual, MarkStop, LiftControl)

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>Test_ACS</RootNamespace>
<AssemblyName>Test_ACS</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\..\..\..\..\Amkor\AGV4\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\SubProject\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="AppSettings.cs" />
<Compile Include="MainForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="MainForm.Designer.cs">
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
<EmbeddedResource Include="MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="app.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\SubProject\EnigProtocol\enigprotocol\ENIGProtocol.csproj">
<Project>{499d8912-4b96-41e5-a70d-cfe797883d65}</Project>
<Name>ENIGProtocol</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="Test_ACS.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
<userSettings>
<Test_ACS.Properties.Settings>
<setting name="LastPort" serializeAs="String">
<value>COM1</value>
</setting>
<setting name="LastBaudRate" serializeAs="String">
<value>9600</value>
</setting>
<setting name="LastRFID" serializeAs="String">
<value>0001</value>
</setting>
<setting name="LastAlias" serializeAs="String">
<value>CHARGER1</value>
</setting>
<setting name="LastAGV" serializeAs="String">
<value>11</value>
</setting>
</Test_ACS.Properties.Settings>
</userSettings>
</configuration>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
</packages>