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:
515
Cs_HMI/TestProject/Test_ACS/MainForm.cs
Normal file
515
Cs_HMI/TestProject/Test_ACS/MainForm.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user