This commit is contained in:
backuppc
2025-12-12 17:27:50 +09:00
parent 07ddc0425f
commit 4e9d29d22f
44 changed files with 7173 additions and 61 deletions

View File

@@ -0,0 +1,139 @@
<?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>{9312AB43-72F6-4365-A266-E767215FA7F5}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>AGVEmulator</RootNamespace>
<AssemblyName>AGVEmulator</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</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="arControl.Net4">
<HintPath>..\arControl.Net4.dll</HintPath>
</Reference>
<Reference Include="arFrameControl">
<HintPath>..\arFrameControl.dll</HintPath>
</Reference>
<Reference Include="ArLog.Net4">
<HintPath>..\ArLog.Net4.dll</HintPath>
</Reference>
<Reference Include="ArSetting.Net4">
<HintPath>..\ArSetting.Net4.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="DevAGV.cs" />
<Compile Include="DevBMS.cs" />
<Compile Include="DevXBE.cs" />
<Compile Include="fMain.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="fMain.Designer.cs">
<DependentUpon>fMain.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RS232.cs" />
<Compile Include="RunCode\_AGV.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="RunCode\_XBEE.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="RunCode\_BMS.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="UC\SerialConn.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="UC\SerialConn.Designer.cs">
<DependentUpon>SerialConn.cs</DependentUpon>
</Compile>
<Compile Include="UC\AgvViewer.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="UC\AgvViewer.Designer.cs">
<DependentUpon>AgvViewer.cs</DependentUpon>
</Compile>
<EmbeddedResource Include="fMain.resx">
<DependentUpon>fMain.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<EmbeddedResource Include="UC\SerialConn.resx">
<DependentUpon>SerialConn.cs</DependentUpon>
</EmbeddedResource>
<None Include="app.config" />
<None Include="Sample\packetFD.bin">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Sample\packetFC.bin">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Cs_HMI\AGVLogic\AGVNavigationCore\AGVNavigationCore.csproj">
<Project>{c5f7a8b2-8d3e-4a1b-9c6e-7f4d5e2a9b1c}</Project>
<Name>AGVNavigationCore</Name>
</ProjectReference>
<ProjectReference Include="..\Cs_HMI\SubProject\EnigProtocol\enigprotocol\ENIGProtocol.csproj">
<Project>{9365803b-933d-4237-93c7-b502c855a71c}</Project>
<Name>ENIGProtocol</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

587
AGVEmulator/DevAGV.cs Normal file
View File

@@ -0,0 +1,587 @@
using arCtl;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
namespace AGVEmulator
{
class DevAGV : AR.Dev.RS232
{
//######### private variable
byte runtime = 0;
//######### public variable
public UInt16 system0 = 0;
public UInt16 system1 = 0;
public UInt16 error = 0;
public byte signal = 0;
public char sts_bunki = 'S';
public char sts_speed = 'L';
public char sts_dir = 'F';
public char sts_sensor = '1';
public enum eCommand : byte
{
LOAD = 0, //EEPROM 불러오기
SAVE, //EEPROM 저장
RESET, //초기화
PINGCHK,
SET_PINMODE, //PINMODE 설정
SET_DOUTPUT, //디지털출력설정(포트번호,값[1,0])
SET_AOUTPUT, //아날로그출력설정(포트GET_SETTING = 50, //셋팅값 요청
SET_FLAG,
SET_EEPROM,
SET_MANUALSPEED,
GET_SETTING = 50,
GUIDE_MOT = 90, //가이드커버(양쪽) 0=멈춤,1=UP,2=DN 아스키코드표 90=Z
SET_EEP_DIREV,
};
public enum DOName
{
PINO_GUIDEMOTOR_LDIR,
PINO_GUIDEMOTOR_LRUN,
PINO_GUIDEMOTOR_RDIR,
PINO_GUIDEMOTOR_RRUN,
PINO_EMPTY_26,
PINO_EMPTY_27,
PINO_EMPTY_28,
PINO_EMPTY_29,
}
public enum DIName
{
PINI_EMG,
PINI_BTN_1,
PINI_BTN_2,
PINI_BTN_3,
PINI_BTN_4,
PINI_OVERLOADL,
PINI_OVERLOADR,
PINI_EMPTY_36,
PINI_EMPTY_37,
PINI_EMPTY_38,
PINI_BTN_ZUP,
PINI_BTN_ZDN,
PINI_LIMIT_LU,
PINI_LIMIT_LD,
PINI_LIMIT_RU,
PINI_LIMIT_RD,
PINI_STOP,
}
public enum eerror
{
Emergency = 0,
Overcurrent,
Charger_run_error,
Charger_pos_error,
line_out_error = 4,
/// <summary>
/// 기동시 자석 감지 에러
/// </summary>
runerror_by_no_magent_line,
/// <summary>
/// 호출제어기 통신 오류
/// </summary>
controller_comm_error = 11,
/// <summary>
/// 도착경보기 통신 오류
/// </summary>
arrive_ctl_comm_error,
/// <summary>
/// 자동문제어기 통신 오류
/// </summary>
door_ctl_comm_error,
/// <summary>
/// 자동충전기 통신 오류
/// </summary>
charger_comm_error,
/// <summary>
/// 교차로 제어기 통신 오류
/// </summary>
cross_ctrl_comm_error,
}
public enum esignal
{
front_gate_out = 0,
rear_sensor_out,
mark_sensor_1,
mark_sensor_2,
front_left_sensor,
front_right_sensor,
front_center_sensor,
charger_align_sensor,
}
public enum esystemflag0
{
Memory_RW_State = 5,
EXT_IO_Conn_State,
RFID_Conn_State,
M5E_Module_Run_State = 8,
Front_Ultrasonic_Conn_State,
Front_Untrasonic_Sensor_State,
Side_Ultrasonic_Conn_State,
Side_Ultrasonic_Sensor_State = 12,
Front_Guide_Sensor_State,
Rear_Guide_Sensor_State,
Battery_Level_Check
}
public enum esystemflag1
{
Side_Detect_Ignore = 3,
Melody_check,
Mark2_check,
Mark1_check,
gateout_check,
Battery_charging = 8,
re_Start,
/// <summary>
/// 전방 감지 무시
/// </summary>
front_detect_ignore,
/// <summary>
/// 전방장애물감지상태
/// </summary>
front_detect_check,
/// <summary>
/// 전방감지 후 정지 상태
/// </summary>
stop_by_front_detect = 12,
/// <summary>
/// 교차로 진입 후 정지 상태
/// </summary>
stop_by_cross_in,
agv_stop,
agv_run
}
public enum evaluetype
{
system0,
system1,
error,
signal,
}
public enum estsvaluetype
{
bunki,
speed,
direction,
sensor
}
public class ValueChangedArgs : EventArgs
{
public int Idx { get; set; }
public bool Value { get; set; }
public evaluetype vtype { get; set; }
public ValueChangedArgs(int idx, bool val, evaluetype isOut)
{
this.Idx = idx;
this.Value = val;
this.vtype = isOut;
}
}
public class StsValueChangedArgs : EventArgs
{
public char Value { get; set; }
public estsvaluetype vtype { get; set; }
public StsValueChangedArgs(estsvaluetype vType, char val)
{
this.Value = val;
this.vtype = vType;
}
}
public class RequestStatusDataArgs : EventArgs
{
public UInt16 system0 { get; set; } = 0;
public UInt16 system1 { get; set; } = 0;
public char speed { get; set; } = 'L';
public char direction { get; set; } = 'F';
public char bunki { get; set; } = 'S';
public byte signal { get; set; } = 0;
public float volt { get; set; } = 0f;
public UInt16 error { get; set; } = 0;
public char sensor { get; set; } = '0';
public RequestStatusDataArgs()
{
}
}
public event EventHandler<RequestStatusDataArgs> RequestStatusData;
public event EventHandler<ValueChangedArgs> ValueChanged;
public event EventHandler<StsValueChangedArgs> StsValueChanged;
public class commandargs : EventArgs
{
public string Command { get; set; }
public commandargs(string cmd)
{
this.Command = cmd;
}
}
public event EventHandler<commandargs> Command;
public class FrameData
{
public string Checksum { get; set; }
public byte stx { get; set; }
public byte etx { get; set; }
public string cmd { get; set; }
public string data { get; set; }
public FrameData(byte[] data)
{
this.stx = data[0];
this.etx = data[data.Length - 1];
this.Checksum = System.Text.Encoding.Default.GetString(data, data.Length - 3, 2);
this.data = System.Text.Encoding.Default.GetString(data, 1, data.Length - 4);
this.cmd = this.data.Substring(0, 3);
this.data = this.data.Substring(3);
}
}
#region SetAGV/SetSTS/SetBIT/GetBIT
Boolean GetBit(ref UInt16 _value, int idx)
{
var offset = (UInt16)(1 << idx);
return (_value & offset) != 0;
}
Boolean GetBit(ref byte _value, int idx)
{
var offset = (byte)(1 << idx);
return (_value & offset) != 0;
}
bool SetBit(ref UInt16 _value, int idx, Boolean value)
{
var oldvalue = GetBit(ref _value, idx);
if (value)
{
var offset = (UInt16)(1 << idx);
_value = (UInt16)(_value | offset);
}
else
{
var offset = (UInt16)(~(1 << idx));
_value = (UInt16)(_value & offset);
}
return oldvalue != value;
}
bool SetBit(ref byte _value, int idx, Boolean value)
{
var oldvalue = GetBit(ref _value, idx);
if (value)
{
var offset = (byte)(1 << idx);
_value = (byte)(_value | offset);
}
else
{
var offset = (byte)(~(1 << idx));
_value = (byte)(_value & offset);
}
return oldvalue != value;
}
public bool GetAGV(DevAGV.esystemflag1 flag)
{
var idx = (int)flag;
return GetBit(ref system1, idx);
}
public void SetAGV(DevAGV.esystemflag0 flag, bool value)
{
var idx = (int)flag;
if (SetBit(ref system0, idx, value))
ValueChanged?.Invoke(this, new ValueChangedArgs(idx, value, evaluetype.system0));
}
public void SetAGV(DevAGV.esystemflag1 flag, bool value)
{
var idx = (int)flag;
if (SetBit(ref system1, idx, value))
ValueChanged?.Invoke(this, new ValueChangedArgs(idx, value, evaluetype.system1));
}
public void SetAGV(DevAGV.eerror flag, bool value)
{
var idx = (int)flag;
if (SetBit(ref error, idx, value))
ValueChanged?.Invoke(this, new ValueChangedArgs(idx, value, evaluetype.error));
}
public void SetAGV(DevAGV.esignal flag, bool value)
{
var idx = (int)flag;
if (SetBit(ref signal, idx, value))
ValueChanged?.Invoke(this, new ValueChangedArgs(idx, value, evaluetype.signal));
}
public void SetSTS(estsvaluetype target, char value)
{
//bool changed = false;
switch (target)
{
case estsvaluetype.sensor:
//changed = this.sts_sensor.Equals(value);
sts_sensor = value;
break;
case estsvaluetype.direction:
//changed = this.sts_dir.Equals(value);
sts_dir = value;
break;
case estsvaluetype.speed:
//changed = this.sts_speed.Equals(value);
sts_speed = value;
break;
case estsvaluetype.bunki:
//changed = this.sts_bunki.Equals(value);
sts_bunki = value;
break;
}
//if(changed)
{
StsValueChanged?.Invoke(this, new StsValueChangedArgs(target, value));
}
}
#endregion
public override bool ProcessRecvData(byte[] data)
{
//dd로 시작하고 34개의 데이터
//STS258FFFF40000000LSF0000003A
//02 53 54 53 32 35 38 46 46 46 46 34 30 30 30 30 30 30 30 4C 53 46 30 30 30 30 30 30 33 41 03
var frame = new FrameData(data);
switch (frame.cmd)
{
case "CRN": //기동명령
//sts_dir = frame.data[0];
SetSTS(estsvaluetype.direction, frame.data[0]);
SetAGV(esystemflag1.agv_stop, false);
SetAGV(esystemflag1.agv_run, true);
break;
case "CST": //중지명령
//(바로 중지한다)
if (frame.data.StartsWith("M"))
{
Command?.Invoke(this, new commandargs("stopmark"));
}
else
{
SetAGV(esystemflag1.agv_run, false);
SetAGV(esystemflag1.agv_stop, true);
}
break;
case "CBR": //분기명령
//FSL
SetSTS(estsvaluetype.direction, frame.data[0]);
SetSTS(estsvaluetype.bunki, frame.data[1]);
SetSTS(estsvaluetype.speed, frame.data[2]);
SetSTS(estsvaluetype.sensor, frame.data[3]);
break;
case "CRT"://수동제어
sts_dir = frame.data[0];
sts_bunki = frame.data[1];
sts_speed = frame.data[2];
sts_sensor = frame.data[3];
SetAGV(esystemflag1.agv_stop, false);
SetAGV(esystemflag1.agv_run, true);
break;
case "ACK": //응답ok
RaiseMessage(MessageType.Normal, $">> {frame.cmd} DATA={frame.data}");
break;
case "SFR": //reset
SetAGV(DevAGV.eerror.Emergency, false);
SetAGV(DevAGV.eerror.line_out_error, false);
SetAGV(DevAGV.eerror.Overcurrent, false);
SetAGV(DevAGV.esystemflag1.agv_run, false);
SetAGV(DevAGV.esystemflag1.agv_stop, true);
break;
case "CBT"://충전작업
SetAGV(DevAGV.esystemflag1.Battery_charging, true);
var id = frame.data.Substring(2, 2);
var cmd = frame.data[4];
var delaytime = int.Parse(frame.data.Substring(5));
if (cmd == 'I') SetAGV(DevAGV.esystemflag1.Battery_charging, true);
else SetAGV(esystemflag1.Battery_charging, false);
break;
case "SSH":
case "SSM":
case "SSL":
case "SSS":
case "SHS":
case "SLS":
case "SPK":
case "SPM":
case "SPL":
case "SPS":
case "SIK":
case "SIM":
case "SIH":
case "SIL":
case "SIS":
case "SDK":
case "SDW":
case "SDL":
case "SDS":
case "SRS":
case "SCK":
case "SSK":
case "STT":
case "SSI":
case "SMD":
case "SSC":
case "SPN":
case "SPH":
case "SCH":
case "SDH":
case "SDM":
case "SLB":
case "SGS":
SendCmd("ACK", frame.cmd);
break;
default:
RaiseMessage(MessageType.Normal, $"미처리명령 {frame.cmd} DATA={frame.data}");
break;
}
return true;
}
public void SendCmd(string cmd, string value)
{
var barr = new List<byte>();
barr.Add(0x02);
barr.AddRange(System.Text.Encoding.Default.GetBytes(cmd));
barr.AddRange(System.Text.Encoding.Default.GetBytes(value));
barr.Add((byte)'*');
barr.Add((byte)'*');
barr.Add(0x03);
var cmdstr = System.Text.Encoding.Default.GetString(barr.ToArray());
RaiseMessage(MessageType.Normal, "Tx:" + barr.ToArray().HexString());
WriteData(barr.ToArray());
}
public void SendTag(string tagno)
{
tagno = tagno.PadLeft(6, '0');
if (tagno.Length > 6) tagno = tagno.Substring(0, 6);
var barr = new List<byte>();
barr.Add(0x02);
barr.Add((byte)'T');
barr.Add((byte)'A');
barr.Add((byte)'G');
barr.AddRange(System.Text.Encoding.Default.GetBytes(tagno));
barr.Add((byte)'*');
barr.Add((byte)'*');
barr.Add(0x03);
var cmdstr = System.Text.Encoding.Default.GetString(barr.ToArray());
RaiseMessage(MessageType.Normal, "Tx:" + barr.ToArray().HexString());
WriteData(barr.ToArray());
}
public override void AutoSendData()
{
//if (_device.DtrEnable == false) return;
//STS258FFFF40000000LSF0000003A
//02 53 54 53 32 35 38 46 46 46 46 34 30 30 30 30 30 30 30 4C 53 46 30 30 30 30 30 30 33 41 03
var sample = "02 53 54 53 32 35 38 46 46 46 46 34 30 30 30 30 30 30 30 4C 53 46 30 30 30 30 30 30 33 41 03";
var barr = sample.Split(' ').ToList().Select(t => Convert.ToByte(t, 16)).ToArray();
//if (RequestStatusData != null)
//{
//var p = new RequestStatusDataArgs();
//RequestStatusData.Invoke(this, p);
var voltstr = "255";// ((int)(p.volt * 10f)).ToString().PadRight(3, '0');
var bufarr = System.Text.Encoding.Default.GetBytes(voltstr);
Array.Copy(bufarr, 0, barr, 4, bufarr.Length);
bufarr = System.Text.Encoding.Default.GetBytes(system0.ToString("X2").PadLeft(4, '0'));
Array.Copy(bufarr, 0, barr, 7, bufarr.Length);
bufarr = System.Text.Encoding.Default.GetBytes(system1.ToString("X2").PadLeft(4, '0'));
Array.Copy(bufarr, 0, barr, 11, bufarr.Length);
bufarr = System.Text.Encoding.Default.GetBytes(error.ToString("X2").PadLeft(4, '0'));
Array.Copy(bufarr, 0, barr, 15, bufarr.Length);
barr[19] = (byte)this.sts_speed;
barr[20] = (byte)this.sts_bunki;
barr[21] = (byte)this.sts_dir;
barr[22] = (byte)this.sts_sensor;
//bufarr = System.Text.Encoding.Default.GetBytes(p.sensor.ToString().PadLeft(2, '0'));
//Array.Copy(bufarr, 0, barr, 22, bufarr.Length);
bufarr = System.Text.Encoding.Default.GetBytes(signal.ToString("X2").PadLeft(2, '0'));
Array.Copy(bufarr, 0, barr, 23, bufarr.Length);
//barr[22] = (byte)'5';
barr[barr.Length - 3] = (byte)'*';
barr[barr.Length - 2] = (byte)'*';
//}
var cmdstr = System.Text.Encoding.Default.GetString(barr);
RaiseMessage(MessageType.Normal, "Tx:" + barr.ToArray().HexString());
WriteData(barr.ToArray());
}
protected override bool CustomParser(byte[] buf, out byte[] remainBuffer)
{
//DD A5 03 00 FF FD 77 0D
//remainBuffer = new byte[] { };
List<byte> remain = new List<byte>();
bool retval = false;
foreach (var b in buf)
{
if (retval)
{
remain.Add(b);
continue;
}
if (b == 0x02) //stx
{
tempBuffer.Clear();
tempBuffer.Add(b);
}
else if (b == 0x03) //etx
{
tempBuffer.Add(b);
retval = true;
}
else
{
//데이터길이가 만족한 상태
tempBuffer.Add(b);
var maxlen = 100;
if (tempBuffer.Count > maxlen)
{
RaiseMessage(MessageType.Error, $"buffer over({maxlen})");
tempBuffer.Clear();
}
}
}
remainBuffer = remain.ToArray();
return retval;
}
}
}

93
AGVEmulator/DevXBE.cs Normal file
View File

@@ -0,0 +1,93 @@
using ENIG;
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
namespace AGVEmulator
{
public class DevXBE : AR.Dev.RS232
{
private EEProtocol proto;
public event EventHandler<EEProtocol.DataEventArgs> ProtocReceived;
public DevXBE()
{
proto = new EEProtocol();
proto.OnDataReceived += Proto_OnDataReceived;
proto.OnMessage += Proto_OnMessage;
}
~DevXBE()
{
proto.OnDataReceived -= Proto_OnDataReceived;
proto.OnMessage -= Proto_OnMessage;
}
public override bool ProcessRecvData(byte[] data)
{
return true;
}
protected override bool CustomParser(byte[] buf, out byte[] remainBuffer)
{
//여기서 최초데이터를 파싱한다
remainBuffer = null;
this.proto.ProcessReceivedData(buf);
return false;
}
private void Proto_OnDataReceived(object sender, EEProtocol.DataEventArgs e)
{
var hexstrRaw = e.ReceivedPacket.RawData.HexString();
var hexstr = e.ReceivedPacket.Data.HexString();
var cmd = e.ReceivedPacket.Command.ToString("X2");
var id = e.ReceivedPacket.ID.ToString("X2");
var dataStr = System.Text.Encoding.Default.GetString(e.ReceivedPacket.Data);
RaiseMessage(MessageType.Recv, $"ID:{id},CMD:{cmd},DATA:{hexstr}");
ProtocReceived?.Invoke(this, e);
}
private void Proto_OnMessage(object sender, EEProtocol.MessageEventArgs e)
{
RaiseMessage(e.IsError, e.Message);
}
/// <summary>
/// 목적지 태그번호 전송
/// </summary>
public void SendGotoTag(byte id, uint tag)
{
var idSTR = id.ToString("X2");
var tagSTR = tag.ToString("0000");
var dataStr = $"{idSTR}{tagSTR}";
Send(ENIGProtocol.AGVCommandHE.Goto, dataStr);
}
public void SendCurrentPos(byte id, uint tag)
{
var idSTR = id.ToString("X2");
var tagSTR = tag.ToString("0000");
var dataStr = $"{idSTR}{tagSTR}";
Send(ENIGProtocol.AGVCommandHE.SetCurrent, dataStr);
}
private void Send(ENIGProtocol.AGVCommandHE Command, string datastr)
{
byte id = 0;
byte cmd = (byte)Command; //move to target
byte[] data = null;
if (datastr != null && string.IsNullOrEmpty(datastr) == false)
data = System.Text.Encoding.Default.GetBytes(datastr);
var packet = proto.CreatePacket(id, cmd, data);
if (WriteData(packet, false))
{
var hexstr = System.Text.Encoding.Default.GetString(data);
RaiseMessage(MessageType.Send, $"ID:{id},CMD:{cmd},DATA:{hexstr}");
}
}
}
}

34
AGVEmulator/Program.cs Normal file
View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace AGVEmulator
{
static class Program
{
/// <summary>
/// 해당 애플리케이션의 주 진입점입니다.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new fMain());
}
}
public static class methodext
{
public static string HexString(this byte[] buf)
{
var sb = new System.Text.StringBuilder();
foreach (var b in buf)
{
sb.Append(" " + b.ToString("X2"));
}
return sb.ToString();
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해
// 제어됩니다. 어셈블리와 관련된 정보를 수정하려면
// 이러한 특성 값을 변경하세요.
[assembly: AssemblyTitle("AGVEmulator")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("AGVEmulator")]
[assembly: AssemblyCopyright("Copyright © 2022")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// ComVisible을 false로 설정하면 이 어셈블리의 형식이 COM 구성 요소에
// 표시되지 않습니다. COM에서 이 어셈블리의 형식에 액세스하려면
// 해당 형식에 대해 ComVisible 특성을 true로 설정하세요.
[assembly: ComVisible(false)]
// 이 프로젝트가 COM에 노출되는 경우 다음 GUID는 typelib의 ID를 나타냅니다.
[assembly: Guid("9312ab43-72f6-FF65-a266-e767215fa7f5")]
// 어셈블리의 버전 정보는 다음 네 가지 값으로 구성됩니다.
//
// 주 버전
// 부 버전
// 빌드 번호
// 수정 버전
//
// 모든 값을 지정하거나 아래와 같이 '*'를 사용하여 빌드 번호 및 수정 번호를
// 기본값으로 할 수 있습니다.
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.1")]
[assembly: AssemblyFileVersion("1.0.0.1")]

View File

@@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
// 런타임 버전:4.0.30319.42000
//
// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
// 이러한 변경 내용이 손실됩니다.
// </auto-generated>
//------------------------------------------------------------------------------
namespace AGVEmulator.Properties {
using System;
/// <summary>
/// 지역화된 문자열 등을 찾기 위한 강력한 형식의 리소스 클래스입니다.
/// </summary>
// 이 클래스는 ResGen 또는 Visual Studio와 같은 도구를 통해 StronglyTypedResourceBuilder
// 클래스에서 자동으로 생성되었습니다.
// 멤버를 추가하거나 제거하려면 .ResX 파일을 편집한 다음 /str 옵션을 사용하여 ResGen을
// 다시 실행하거나 VS 프로젝트를 다시 빌드하십시오.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.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("AGVEmulator.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,117 @@
<?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.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="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</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" 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>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
// 런타임 버전:4.0.30319.42000
//
// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
// 이러한 변경 내용이 손실됩니다.
// </auto-generated>
//------------------------------------------------------------------------------
namespace AGVEmulator.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.4.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;
}
}
}
}

View File

@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

485
AGVEmulator/RS232.cs Normal file
View File

@@ -0,0 +1,485 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
namespace AR.Dev
{
public abstract partial class RS232 : IDisposable
{
protected System.IO.Ports.SerialPort _device;
protected ManualResetEvent _mre;
protected byte[] LastReceiveBuffer;
protected List<byte> tempBuffer = new List<byte>();
protected Boolean findSTX = false;
public int WriteError = 0;
public string WriteErrorMessage = string.Empty;
public int WaitTimeout { get; set; } = 1000;
public int MinRecvLength { get; set; } = 1;
/// <summary>
/// 포트이름
/// </summary>
[Description("시리얼 포트 이름")]
[Category("설정"), DisplayName("Port Name")]
public string PortName
{
get
{
if (_device == null) return string.Empty;
else return _device.PortName;
}
set
{
if (this.IsOpen)
{
Message?.Invoke(this, new MessageEventArgs("포트가 열려있어 포트이름을 변경할 수 없습니다", true));
}
else if (String.IsNullOrEmpty(value) == false)
_device.PortName = value;
else
{
Message?.Invoke(this, new MessageEventArgs("No PortName", true));
}
}
}
public int BaudRate
{
get
{
if (_device == null) return 0;
else return _device.BaudRate;
}
set
{
if (this.IsOpen)
{
Message?.Invoke(this, new MessageEventArgs("포트가 열려있어 BaudRate(를) 변경할 수 없습니다", true));
}
else if (value != 0)
_device.BaudRate = value;
else Message?.Invoke(this, new MessageEventArgs("No baud rate", true));
}
}
public string errorMessage { get; set; }
public DateTime LastConnTime { get; set; }
public DateTime LastConnTryTime { get; set; }
public DateTime lastSendTime;
/// <summary>
/// 메세지 수신시 사용하는 내부버퍼
/// </summary>
protected List<byte> _buffer = new List<byte>();
/// <summary>
/// 최종 전송 메세지
/// </summary>
public byte[] lastSendBuffer;
public byte[] LastRecvData;
public string LastRecvString
{
get
{
if (LastRecvData == null) return String.Empty;
else return System.Text.Encoding.Default.GetString(LastRecvData);
}
}
/// <summary>
/// 마지막으로 데이터를 받은 시간
/// </summary>
public DateTime lastRecvTime;
public RS232()
{
_device = new System.IO.Ports.SerialPort();
this.BaudRate = 9600;
_device.DataReceived += barcode_DataReceived;
_device.ErrorReceived += this.barcode_ErrorReceived;
_device.WriteTimeout = 1000;
_device.ReadTimeout = 1000;
_device.DtrEnable = false;
_device.RtsEnable = false;
_device.ReadBufferSize = 8192;
_device.WriteBufferSize = 8192;
errorMessage = string.Empty;
lastRecvTime = DateTime.Parse("1982-11-23");
LastConnTime = DateTime.Parse("1982-11-23");
LastConnTryTime = DateTime.Parse("1982-11-23");
lastRecvTime = DateTime.Parse("1982-11-23");
this._mre = new ManualResetEvent(true);
}
~RS232()
{
Dispose(false);
}
// Flag: Has Dispose already been called?
bool disposed = false;
// Public implementation of Dispose pattern callable by consumers.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
// Free any other managed objects here.
//
}
_device.DataReceived -= barcode_DataReceived;
_device.ErrorReceived -= this.barcode_ErrorReceived;
// Free any unmanaged objects here.
//
disposed = true;
}
public Boolean Open()
{
try
{
_device.Open();
Message?.Invoke(this, new MessageEventArgs(MessageType.Normal, $"port open:{_device.IsOpen}"));
return IsOpen;
}
catch (Exception ex)
{
errorMessage = ex.Message;
Message.Invoke(this, new MessageEventArgs(ex.Message, true));
return false;
}
}
public string GetHexString(Byte[] input)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (byte b in input)
sb.Append(" " + b.ToString("X2"));
return sb.ToString();
}
/// <summary>
/// 포트가 열려있는지 확인
/// </summary>
[Description("현재 시리얼포트가 열려있는지 확인합니다")]
[Category("정보"), DisplayName("Port Open")]
public Boolean IsOpen
{
get
{
if (_device == null) return false;
return _device.IsOpen;
}
}
public virtual void Close(Boolean PortClose = true)
{
if (_device != null && _device.IsOpen)
{
_device.DiscardInBuffer();
_device.DiscardOutBuffer();
if (PortClose) _device.Close(); //dispose에서는 포트를 직접 클리어하지 않게 해뒀다.
}
}
public Boolean RaiseRecvData()
{
return RaiseRecvData(LastReceiveBuffer.ToArray());
}
/// <summary>
/// 수신받은 메세지를 발생 시킵니다
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public virtual Boolean RaiseRecvData(byte[] Data)
{
//181206 - 최종수신 메세지 기록
lastRecvTime = DateTime.Now;
LastRecvData = Data;
try
{
Message?.Invoke(this, new MessageEventArgs(Data, true)); //recvmessage
if (ProcessRecvData(Data) == false)
{
//Message?.Invoke(this, new MessageEventArgs(Data, true)); //recvmessage
Message?.Invoke(this, new MessageEventArgs(this.errorMessage, true)); //errormessage
return false;
}
else
{
return true;
}
}
catch (Exception ex)
{
this.errorMessage = ex.Message;
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
return false;
}
}
/// <summary>
/// 수신받은 자료를 처리한다
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public abstract bool ProcessRecvData(byte[] data);
#region "Internal Events"
void barcode_ErrorReceived(object sender, System.IO.Ports.SerialErrorReceivedEventArgs e)
{
Message?.Invoke(this, new MessageEventArgs(e.ToString(), true));
}
void barcode_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
try
{
int ReadCount = _device.BytesToRead;
byte[] buffer = new byte[ReadCount];
_device.Read(buffer, 0, buffer.Length);
System.Text.StringBuilder LogMsg = new StringBuilder();
byte[] remainBuffer;
Repeat:
if (CustomParser(buffer, out remainBuffer))
{
//parser ok
LastReceiveBuffer = tempBuffer.ToArray();
RaiseRecvData();
tempBuffer.Clear();
if (remainBuffer != null && remainBuffer.Length > 0)
{
//버퍼를 변경해서 다시 전송을 해준다.
buffer = new byte[remainBuffer.Length];
Array.Copy(remainBuffer, buffer, buffer.Length);
goto Repeat; //남은 버퍼가 있다면 진행을 해준다.
}
}
}
catch (Exception ex)
{
if (IsOpen)
{
//_device.DiscardInBuffer();
//_device.DiscardOutBuffer();
}
errorMessage = ex.Message;
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
}
}
#endregion
#region "External Events"
/// <summary>
/// 오류 및 기타 일반 메세지
/// </summary>
public event EventHandler<MessageEventArgs> Message;
public void RaiseMessage(bool iserr, string message)
{
Message?.Invoke(this, new MessageEventArgs(message, iserr));
}
#endregion
#region "Event Args"
/// <summary>
/// 데이터를 수신할떄 사용함(RAW 포함)
/// </summary>
public class ReceiveDataEventArgs : EventArgs
{
private byte[] _buffer = null;
/// <summary>
/// 바이트배열의 버퍼값
/// </summary>
public byte[] Value { get { return _buffer; } }
/// <summary>
/// 버퍼(바이트배열)의 데이터를 문자로 반환합니다.
/// </summary>
public string StrValue
{
get
{
//return string.Empty;
if (_buffer == null || _buffer.Length < 1) return string.Empty;
else return System.Text.Encoding.Default.GetString(_buffer);
}
}
public ReceiveDataEventArgs(byte[] buffer)
{
_buffer = buffer;
}
}
/// <summary>
/// 메세지를 강제 발생
/// </summary>
/// <param name="mt"></param>
/// <param name="message"></param>
protected virtual void RaiseMessage(MessageType mt, string message)
{
this.Message?.Invoke(this, new MessageEventArgs(mt, message));
}
public enum MessageType
{
Normal,
Error,
Send,
Recv,
}
public class MessageEventArgs : EventArgs
{
public MessageType MsgType { get; set; }
private string _message = string.Empty;
/// <summary>
/// Recv,Send,Normal,Error 모두 지원
/// </summary>
public string Message { get { return _message; } }
private byte[] _data = null;
/// <summary>
/// Recv,Send에서만 값이 존재 합니다
/// </summary>
public byte[] Data { get { return _data; } }
public MessageEventArgs(string Message, bool isError = false)
{
if (isError) MsgType = MessageType.Error;
else MsgType = MessageType.Normal;
_message = Message;
}
public MessageEventArgs(MessageType msgtype, string Message)
{
MsgType = msgtype;
_message = Message;
_data = System.Text.Encoding.Default.GetBytes(Message);
}
public MessageEventArgs(byte[] buffer, bool isRecv = true)
{
if (isRecv) MsgType = MessageType.Recv;
else MsgType = MessageType.Send;
_data = new byte[buffer.Length];
Array.Copy(buffer, _data, Data.Length);
_message = System.Text.Encoding.Default.GetString(_data);
}
}
#endregion
public virtual void AutoSendData()
{
}
protected abstract bool CustomParser(byte[] buf, out byte[] remainBuffer);
/// <summary>
/// 데이터수신시간이 설정값보다 x 2.5를 초과하면 false 가 반환됨
/// </summary>
public Boolean IsValid
{
get
{
if (IsOpen == false) return false;
if (lastRecvTime.Year == 1982) return false;
var ts = DateTime.Now - lastRecvTime;
if (ts.TotalSeconds > 5) return false;
return true;
}
}
public bool WriteData(string cmd)
{
return WriteData(System.Text.Encoding.Default.GetBytes(cmd));
}
/// <summary>
/// 포트에 쓰기(barcode_DataReceived 이벤트로 메세지수신)
/// </summary>
public Boolean WriteData(byte[] data, bool useLog = true)
{
Boolean bRet = false;
//171205 : 타임아웃시간추가
if (!_mre.WaitOne(WaitTimeout))
{
errorMessage = $"WriteData:MRE:WaitOne:TimeOut {WaitTimeout}ms";
this.Message?.Invoke(this, new MessageEventArgs(errorMessage, true));
return false;
}
_mre.Reset();
//Array.Resize(ref data, data.Length + 2);
try
{
lastSendTime = DateTime.Now;
if (lastSendBuffer == null) lastSendBuffer = new byte[data.Length]; //171113
else Array.Resize(ref lastSendBuffer, data.Length);
Array.Copy(data, lastSendBuffer, data.Length);
for (int i = 0; i < data.Length; i++)
_device.Write(data, i, 1);
//_device.Write(data, 0, data.Length);
//171113
if (useLog) this.Message?.Invoke(this, new MessageEventArgs(data, false));
bRet = true;
WriteError = 0;
WriteErrorMessage = string.Empty;
}
catch (Exception ex)
{
// this.isinit = false;
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
bRet = false;
WriteError += 1; //연속쓰기오류횟수
WriteErrorMessage = ex.Message;
}
finally
{
_mre.Set();
}
return bRet;
}
}
}

214
AGVEmulator/RunCode/_AGV.cs Normal file
View File

@@ -0,0 +1,214 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using static AGVEmulator.DevAGV;
namespace AGVEmulator
{
public partial class fMain
{
private void Agv_Command(object sender, commandargs e)
{
switch (e.Command.ToLower())
{
case "stopmark":
agvViewer1.StopbyMark = true;
break;
}
}
private void Agv_StsValueChanged(object sender, DevAGV.StsValueChangedArgs e)
{
Console.WriteLine($"STS [{e.vtype}] VAL={e.Value}");
if (this.InvokeRequired)
{
this.BeginInvoke(new EventHandler<StsValueChangedArgs>(Agv_StsValueChanged), sender, e);
return;
}
switch (e.vtype)
{
case DevAGV.estsvaluetype.direction:
foreach (RadioButton c in this.groupBox5.Controls)
{
if (c.Text[0].Equals(e.Value))
{
c.Checked = true;
}
else
{
c.Checked = false;
}
c.Refresh();
}
groupBox5.Refresh();
break;
case DevAGV.estsvaluetype.bunki:
foreach (RadioButton c in this.groupBox2.Controls)
{
if (c.Text[0].Equals(e.Value))
{
c.Checked = true;
c.Refresh();
break;
}
}
break;
case DevAGV.estsvaluetype.speed:
foreach (RadioButton c in this.groupBox4.Controls)
{
if (c.Text[0].Equals(e.Value))
{
c.Checked = true;
c.Refresh();
break;
}
}
break;
case DevAGV.estsvaluetype.sensor:
foreach (RadioButton c in this.groupBox8.Controls)
{
if (c.Text.Equals(e.Value))
{
c.Checked = true;
c.Refresh();
break;
}
}
break;
}
}
private void Agv_ValueChanged(object sender, DevAGV.ValueChangedArgs e)
{
if (this.InvokeRequired)
{
this.Invoke(new EventHandler<ValueChangedArgs>(Agv_ValueChanged), sender, e);
return;
}
//내부값이 바뀌었다면 컨트롤을 변경해준다.
switch (e.vtype)
{
case DevAGV.evaluetype.system0:
foreach (CheckBox c in panel6.Controls)
{
var idx = int.Parse(c.Tag.ToString());
if (idx == e.Idx)
{
c.Checked = e.Value;
break;
}
}
break;
case DevAGV.evaluetype.system1:
var v = (DevAGV.esystemflag1)e.Idx;
if (e.Value)
{
if (v == esystemflag1.agv_run)
{
agvViewer1.wat.Restart();
}
if (v == esystemflag1.agv_stop)
{
agvViewer1.wat.Stop();
}
}
foreach (CheckBox c in panel7.Controls)
{
var idx = int.Parse(c.Tag.ToString());
if (idx == e.Idx)
{
c.Checked = e.Value;
break;
}
}
break;
case DevAGV.evaluetype.error:
foreach (CheckBox c in panel9.Controls)
{
var idx = int.Parse(c.Tag.ToString());
if (idx == e.Idx)
{
c.Checked = e.Value;
break;
}
}
break;
case DevAGV.evaluetype.signal:
foreach (CheckBox c in panel8.Controls)
{
var idx = int.Parse(c.Tag.ToString());
if (idx == e.Idx)
{
c.Checked = e.Value;
break;
}
}
break;
}
}
private void Agv_RequestStatusData(object sender, DevAGV.RequestStatusDataArgs e)
{
//UInt16 system0 = 0xFFFF;
//UInt16 system1 = 0xFFFF;
//UInt16 error = 0xFFFF;
//byte signal = 0xFF;
aaplycheckboxbit(ref AGV.system0, panel6);
aaplycheckboxbit(ref AGV.system1, panel7);
aaplycheckboxbit(ref AGV.error, panel9);
aaplycheckboxbit(ref AGV.signal, panel8);
if (this.agvViewer1.StopbyMark) AGV.sts_speed = 'S';
else AGV.sts_speed = GetGroupItemCheckbox(groupBox4);
AGV.sts_bunki = GetGroupItemCheckbox(groupBox2);
AGV.sts_dir = GetGroupItemCheckbox(groupBox5);
AGV.sts_sensor = GetGroupItemCheckbox(groupBox8);
//this.Invoke(new Action(() =>
//{
// e.system0 = system0;
// e.system1 = system1;
// e.error = error;
// e.signal = signal;
// e.speed = GetGroupItemCheckbox(groupBox4);
// e.bunki = GetGroupItemCheckbox(groupBox2);
// e.direction = GetGroupItemCheckbox(groupBox5);
// e.sensor = GetGroupItemCheckbox(groupBox8);
// e.volt = 23.4f;
//}));
}
private void AGV_Message(object sender, AR.Dev.RS232.MessageEventArgs e)
{
var dev = sender as AR.Dev.RS232;
if (dev is DevBMS)
{
logBMS.Add(e.Message);
}
else if (dev is DevAGV)
{
logAGV.Add(e.Message);
}
else if (dev is DevXBE)
{
if (e.MsgType == AR.Dev.RS232.MessageType.Send)
logCAL.Add(">> " + e.Data.HexString());
else if (e.MsgType == AR.Dev.RS232.MessageType.Recv)
logCAL.Add("<< " + e.Data.HexString());
else logCAL.Add(e.Message);
}
}
}
}

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AGVEmulator
{
public partial class fMain
{
private void BMS_Message(object sender, AR.Dev.RS232.MessageEventArgs e)
{
logBMS.Add(e.Message);
}
private void BMS_RequestVoltageData(object sender, DevBMS.RequestVoltageDataArgs e)
{
//cell전압값추가
for (int i = 0; i < cellvolt.Length; i++)
{
this.cellvolt[i] = (UInt16)rnd.Next(3300, 3350);
}
Array.Copy(this.cellvolt, 0, e.cellVolt, 0, 8);
this.btc1.Invoke(new Action(() =>
{
var idx = 0;
btc1.Text = (this.cellvolt[idx++] / 1000f).ToString();
btc2.Text = (this.cellvolt[idx++] / 1000f).ToString();
btc3.Text = (this.cellvolt[idx++] / 1000f).ToString();
btc4.Text = (this.cellvolt[idx++] / 1000f).ToString();
btc5.Text = (this.cellvolt[idx++] / 1000f).ToString();
btc6.Text = (this.cellvolt[idx++] / 1000f).ToString();
btc7.Text = (this.cellvolt[idx++] / 1000f).ToString();
btc8.Text = (this.cellvolt[idx++] / 1000f).ToString();
}));
}
private void Bms_RequestBatteryData(object sender, DevBMS.RequestBatteryDataArgs e)
{
if (checkBox1.Checked)
this.trackBar1.Invoke(new Action(() =>
{
this.trackBar1.Value -= 1;
trackBar1_Scroll(null, null);
}));
e.CurA = (int)BMS_CurA;
e.MaxA = (int)BMS_MaxA;
e.Remain = BMS_Remain;
e.Volt = BMS_Volt;
e.Temp1 = this.Temp1;
e.Temp2 = this.Temp2;
}
}
}

View File

@@ -0,0 +1,40 @@
using ENIG;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AGVEmulator
{
public partial class fMain
{
private void CAL_Message(object sender, AR.Dev.RS232.MessageEventArgs e)
{
if (e.MsgType == AR.Dev.RS232.MessageType.Send)
logCAL.Add(">> " + System.Text.Encoding.Default.GetString(e.Data));
else if (e.MsgType == AR.Dev.RS232.MessageType.Recv)
logCAL.Add("<< " + System.Text.Encoding.Default.GetString( e.Data));
else logCAL.Add(e.Message);
}
private void CAL_ProtocReceived(object sender, ENIG.EEProtocol.DataEventArgs e)
{
//throw new NotImplementedException();
var dev = (DeviceType)e.ReceivedPacket.ID;
if (dev == DeviceType.AGV1 || dev == DeviceType.AGV2)
{
//agv에서 들어오는 데이터
var cmd = e.ReceivedPacket.Command;
if(cmd == 3)
{
//status
}
}
}
}
}

Binary file not shown.

Binary file not shown.

37
AGVEmulator/UC/AgvViewer.Designer.cs generated Normal file
View File

@@ -0,0 +1,37 @@

namespace AGVEmulator.UC
{
partial class AgvViewer
{
/// <summary>
/// 필수 디자이너 변수입니다.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// 사용 중인 모든 리소스를 정리합니다.
/// </summary>
/// <param name="disposing">관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region
/// <summary>
/// 디자이너 지원에 필요한 메서드입니다.
/// 이 메서드의 내용을 코드 편집기로 수정하지 마세요.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
}
}

272
AGVEmulator/UC/AgvViewer.cs Normal file
View File

@@ -0,0 +1,272 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AGVEmulator.UC
{
public partial class AgvViewer : Control
{
public AgvViewer()
{
InitializeComponent();
// Set Optimized Double Buffer to reduce flickering
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
// Redraw when resized
this.SetStyle(ControlStyles.ResizeRedraw, true);
}
public class TagArgs : EventArgs
{
public string Data { get; set; }
public bool Active { get; set; }
public TagArgs(string tag,bool act)
{
this.Data = tag;
this.Active = act;
}
}
public event EventHandler<TagArgs> TagTouched;
public event EventHandler<TagArgs> MarkTouched;
public event EventHandler<TagArgs> Command;
public bool StopbyMark { get; set; }
public float mpos = 100;
public float posmax = 1200;
public float posmin = 0;
public float mspd = 10;
public System.Diagnostics.Stopwatch wat = new System.Diagnostics.Stopwatch();
public Font FontTag { get; set; }
public Font FontMrk { get; set; }
public int dir = 1;
public class ptdata
{
public float pos { get; set; } = 0f;
public string data { get; set; } = string.Empty;
public Boolean active { get; set; } = false;
}
public ptdata[] listMRK { get; set; }
public ptdata[] listTAG { get; set; }
public string lasttag { get; set; } = string.Empty;
public string lasttagdir { get; set; } = string.Empty;
public string lastmark { get; set; } = string.Empty;
public string lastmarkdir { get; set; } = string.Empty;
protected override void OnPaint(PaintEventArgs pe)
{
pe.Graphics.Clear(this.BackColor);
var r = new Rectangle(DisplayRectangle.Left + Padding.Left,
DisplayRectangle.Top + Padding.Top,
DisplayRectangle.Width - Padding.Right - Padding.Left - 1,
DisplayRectangle.Height - Padding.Top - Padding.Bottom - 1);
// pe.Graphics.FillRectangle(new SolidBrush(this.BackColor), DisplayRectangle);
pe.Graphics.DrawRectangle(Pens.Black, r);
var ptwidth = 25;
var ptheight = 35;
if (listMRK != null && listMRK.Any() && FontMrk != null)
{
foreach (var item in listMRK)
{
var x = r.Left + ((item.pos * 1f) / posmax) * r.Width;
var rr = new RectangleF(x - ptwidth, r.Top + r.Height / 2f - ptheight / 2f, ptwidth * 2, ptheight);
pe.Graphics.DrawLine(Pens.Gray, x, r.Top, x, r.Bottom);
pe.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(120, Color.Gold)), rr);
pe.Graphics.DrawRectangle(Pens.DimGray, rr.Left, rr.Top, rr.Width, rr.Height);
pe.Graphics.DrawString(item.data, FontMrk, Brushes.Gray, rr, new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center,
});
}
}
ptwidth = 15;
if (listTAG != null && listTAG.Any() && FontTag != null)
{
var lst = listTAG.Where(t => t.data.EndsWith("1"));
foreach (var item in lst)
{
var x = r.Left + ((item.pos * 1f) / posmax) * r.Width;
var rr = new RectangleF(x - ptwidth, r.Top + 5, ptwidth * 2, 15);
pe.Graphics.DrawLine(Pens.Orange, x, r.Top, x, rr.Top);
pe.Graphics.FillRectangle(Brushes.Orange, rr);
pe.Graphics.DrawRectangle(Pens.DimGray, rr.Left, rr.Top, rr.Width, rr.Height);
pe.Graphics.DrawString(item.data, FontTag, Brushes.Black, rr, new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center,
});
}
lst = listTAG.Where(t => t.data.EndsWith("0"));
foreach (var item in lst)
{
var x = r.Left + ((item.pos * 1f) / posmax) * r.Width;
var rr = new RectangleF(x - ptwidth, r.Bottom - 20, ptwidth * 2, 15);
pe.Graphics.DrawLine(Pens.Orange, x, rr.Bottom, x, r.Bottom);
pe.Graphics.FillRectangle(Brushes.Orange, rr);
pe.Graphics.DrawRectangle(Pens.DimGray, rr.Left, rr.Top, rr.Width, rr.Height);
pe.Graphics.DrawString(item.data, FontTag, Brushes.Black, rr, new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center,
});
}
}
var posX = r.Left + (mpos / posmax) * r.Width;
var posY = r.Top + r.Height / 2f;
var boxw = r.Width * 0.030f;
var boxh = r.Height * 0.15f;
var box = new RectangleF(posX - boxw, posY - boxh, boxw * 2, boxh * 2);
var box2 = new RectangleF(box.Left - 5, box.Top + 3, 10, box.Height - 6);
for (int i = 0; i < posmax; i += 100)
{
var x = r.Left + ((i * 1f) / posmax) * r.Width;
pe.Graphics.DrawLine(Pens.Black, x, r.Bottom - 3, x, r.Bottom);
if (i > 0)
pe.Graphics.DrawString($"{i / 10f}m", this.Font, Brushes.Black, x - 12, r.Bottom - 15);
}
pe.Graphics.FillRectangle(Brushes.LightSkyBlue, box);
pe.Graphics.DrawRectangle(Pens.Black, box.Left, box.Top, box.Width, box.Height);
pe.Graphics.DrawLine(new Pen(Color.Black, 4), posX, box.Top - 5, posX, box.Bottom + 5);
pe.Graphics.FillRectangle(Brushes.Gold, box2);
pe.Graphics.DrawRectangle(Pens.Black, box2.Left, box2.Top, box2.Width, box2.Height);
//pe.Graphics.DrawString((mpos / 10f).ToString("N1") + "m", this.Font, Brushes.Black, box, new StringFormat
//{
// Alignment = StringAlignment.Center,
// LineAlignment = StringAlignment.Center,
//});
if (StopbyMark)
pe.Graphics.DrawString("!MRK-STP!", this.Font, Brushes.Blue, r.Left+2, r.Top+2);
if (wat.IsRunning)
{
var newpos = mspd * (wat.ElapsedMilliseconds / 1000f);
if (dir < 0) //forward
{
if (mpos - newpos < 0)
mpos = posmax;
else mpos -= newpos;
//내위치주변에 마커가 있는지 본다
var mlist = listMRK.Where(t => t.pos <= mpos && (mpos - t.pos) < 10);
var mrk = mlist.FirstOrDefault();
if (mrk != null)
{
//대상마커가있다
if (lastmark.Equals(mrk.data) == false || lastmarkdir.Equals("F") == false)
{
lastmark = mrk.data;
lastmarkdir = "F";
mrk.active = true;
MarkTouched?.Invoke(this, new TagArgs(mrk.data,true));
if(StopbyMark)
{
Command?.Invoke(this, new TagArgs("stop",true));
StopbyMark = false;
}
}
}
else
{
if (string.IsNullOrEmpty(lastmark) == false)
{
foreach(var item in listMRK.Where(t=>t.active))
{
item.active = false;
MarkTouched?.Invoke(this, new TagArgs(item.data,false));
}
}
}
//주변태그확인
var tlist = listTAG.Where(t => t.pos <= mpos && (mpos - t.pos) < 10);
var tag = tlist.FirstOrDefault();
if (tag != null)
{
//대상마커가있다
if (lasttag.Equals(tag.data) == false || lasttagdir.Equals("F") == false)
{
lasttag = tag.data;
lasttagdir = "F";
TagTouched?.Invoke(this, new TagArgs(tag.data, true));
}
}
}
else //backward
{
if (mpos + newpos > posmax)
mpos = 0;
else mpos += newpos;
//내위치주변에 마커가 있는지 본다
var mlist = listMRK.Where(t => t.pos >= mpos && (t.pos - mpos) < 10);
var mrk = mlist.FirstOrDefault();
if (mrk != null)
{
//대상마커가있다
if (lastmark.Equals(mrk.data) == false || lastmarkdir.Equals("B") == false)
{
lastmark = mrk.data;
lastmarkdir = "B";
mrk.active = true;
MarkTouched?.Invoke(this, new TagArgs(mrk.data, true));
if (StopbyMark)
{
Command?.Invoke(this, new TagArgs("stop", true));
StopbyMark = false;
}
}
}
else
{
if (string.IsNullOrEmpty(lastmark) == false)
{
foreach (var item in listMRK.Where(t => t.active))
{
item.active = false;
MarkTouched?.Invoke(this, new TagArgs(item.data, false));
}
}
}
//주변태그확인
var tlist = listTAG.Where(t => t.pos >= mpos && (t.pos - mpos) < 10);
var tag = tlist.FirstOrDefault();
if (tag != null)
{
//대상마커가있다
if (lasttag.Equals(tag.data) == false || lasttagdir.Equals("B") == false)
{
lasttag = tag.data;
lasttagdir = "B";
TagTouched?.Invoke(this, new TagArgs(tag.data, true));
}
}
}
wat.Restart();
}
}
}
}

162
AGVEmulator/UC/SerialConn.Designer.cs generated Normal file
View File

@@ -0,0 +1,162 @@

namespace AGVEmulator
{
partial class SerialConn
{
/// <summary>
/// 필수 디자이너 변수입니다.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// 사용 중인 모든 리소스를 정리합니다.
/// </summary>
/// <param name="disposing">관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region
/// <summary>
/// 디자이너 지원에 필요한 메서드입니다.
/// 이 메서드의 내용을 코드 편집기로 수정하지 마세요.
/// </summary>
private void InitializeComponent()
{
this.cmbBaud = new System.Windows.Forms.ComboBox();
this.cmbPOrt = new System.Windows.Forms.ComboBox();
this.panel1 = new System.Windows.Forms.Panel();
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.lbrx = new System.Windows.Forms.Label();
this.lbtx = new System.Windows.Forms.Label();
this.button1 = new System.Windows.Forms.Button();
this.panel1.SuspendLayout();
this.tableLayoutPanel1.SuspendLayout();
this.SuspendLayout();
//
// cmbBaud
//
this.cmbBaud.Dock = System.Windows.Forms.DockStyle.Top;
this.cmbBaud.Font = new System.Drawing.Font("굴림", 15F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.cmbBaud.FormattingEnabled = true;
this.cmbBaud.Items.AddRange(new object[] {
"9600",
"12800",
"25600",
"38400",
"512000"});
this.cmbBaud.Location = new System.Drawing.Point(0, 28);
this.cmbBaud.Name = "cmbBaud";
this.cmbBaud.Size = new System.Drawing.Size(134, 28);
this.cmbBaud.TabIndex = 0;
this.cmbBaud.Text = "9600";
//
// cmbPOrt
//
this.cmbPOrt.Dock = System.Windows.Forms.DockStyle.Top;
this.cmbPOrt.Font = new System.Drawing.Font("굴림", 15F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.cmbPOrt.FormattingEnabled = true;
this.cmbPOrt.Location = new System.Drawing.Point(0, 0);
this.cmbPOrt.Name = "cmbPOrt";
this.cmbPOrt.Size = new System.Drawing.Size(134, 28);
this.cmbPOrt.TabIndex = 1;
//
// panel1
//
this.panel1.Controls.Add(this.tableLayoutPanel1);
this.panel1.Controls.Add(this.cmbBaud);
this.panel1.Controls.Add(this.cmbPOrt);
this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel1.Location = new System.Drawing.Point(0, 0);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(134, 96);
this.panel1.TabIndex = 2;
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 2;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel1.Controls.Add(this.lbrx, 0, 0);
this.tableLayoutPanel1.Controls.Add(this.lbtx, 1, 0);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 56);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 1;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(134, 40);
this.tableLayoutPanel1.TabIndex = 4;
//
// lbrx
//
this.lbrx.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.lbrx.Dock = System.Windows.Forms.DockStyle.Fill;
this.lbrx.Font = new System.Drawing.Font("Microsoft Sans Serif", 15F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.lbrx.Location = new System.Drawing.Point(0, 0);
this.lbrx.Margin = new System.Windows.Forms.Padding(0);
this.lbrx.Name = "lbrx";
this.lbrx.Size = new System.Drawing.Size(67, 40);
this.lbrx.TabIndex = 2;
this.lbrx.Text = "RX";
this.lbrx.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// lbtx
//
this.lbtx.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.lbtx.Dock = System.Windows.Forms.DockStyle.Fill;
this.lbtx.Font = new System.Drawing.Font("Microsoft Sans Serif", 15F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.lbtx.Location = new System.Drawing.Point(67, 0);
this.lbtx.Margin = new System.Windows.Forms.Padding(0);
this.lbtx.Name = "lbtx";
this.lbtx.Size = new System.Drawing.Size(67, 40);
this.lbtx.TabIndex = 3;
this.lbtx.Text = "TX";
this.lbtx.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// button1
//
this.button1.Dock = System.Windows.Forms.DockStyle.Right;
this.button1.FlatAppearance.BorderColor = System.Drawing.Color.DodgerBlue;
this.button1.FlatAppearance.BorderSize = 3;
this.button1.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.button1.Font = new System.Drawing.Font("맑은 고딕", 18F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.button1.Location = new System.Drawing.Point(134, 0);
this.button1.Margin = new System.Windows.Forms.Padding(0);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(82, 96);
this.button1.TabIndex = 3;
this.button1.Text = "연결";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// SerialConn
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.panel1);
this.Controls.Add(this.button1);
this.Name = "SerialConn";
this.Size = new System.Drawing.Size(216, 96);
this.panel1.ResumeLayout(false);
this.tableLayoutPanel1.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.ComboBox cmbBaud;
private System.Windows.Forms.ComboBox cmbPOrt;
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Label lbtx;
private System.Windows.Forms.Label lbrx;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
}
}

View File

@@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace AGVEmulator
{
public partial class SerialConn : UserControl
{
public AR.Dev.RS232 dev { get; set; }
public SerialConn()
{
InitializeComponent();
}
void AttachEvent()
{
dev.Message += Dev_Message;
}
void DetachEvent()
{
dev.Message -= Dev_Message;
}
private void Dev_Message(object sender, AR.Dev.RS232.MessageEventArgs e)
{
if (e.MsgType == AR.Dev.RS232.MessageType.Recv)
ToggleRX();
else if (e.MsgType == AR.Dev.RS232.MessageType.Send)
ToggleTX();
}
public void SetPortList(string[] list)
{
cmbPOrt.Items.Clear();
foreach (var item in list)
cmbPOrt.Items.Add(item);
}
public string PortName
{
get
{
return cmbPOrt.Text;
}
set { this.cmbPOrt.Text = value; }
}
public int BaudRate
{
get
{
return int.Parse(cmbBaud.Text);
}
set
{
cmbBaud.Text = value.ToString();
}
}
public void ToggleTX()
{
if (lbtx.BackColor == Color.DeepSkyBlue)
lbtx.BackColor = Color.White;
else lbtx.BackColor = Color.DeepSkyBlue;
}
public void ToggleRX()
{
if (lbrx.BackColor == Color.Lime)
lbrx.BackColor = Color.White;
else lbrx.BackColor = Color.Lime;
}
public void Disconnect()
{
if (this.dev.IsOpen)
{
DetachEvent();
dev.Close();
}
if (dev.IsOpen)
button1.BackColor = Color.Lime;
else button1.BackColor = Color.Tomato;
}
public void Connect()
{
if (this.dev.IsOpen)
{
DetachEvent();
dev.Close();
}
else
{
this.dev.PortName = cmbPOrt.Text;
this.dev.BaudRate = int.Parse(cmbBaud.Text);
AttachEvent();
try
{
dev.Open();
}
catch (Exception ex)
{
button1.BackColor = Color.Red;
}
}
if (dev.IsOpen)
button1.BackColor = Color.Lime;
else button1.BackColor = Color.Tomato;
}
private void button1_Click(object sender, EventArgs e)
{
if (this.dev == null)
{
MessageBox.Show("시리얼 개체가 연결되지 않았습니다");
return;
}
if(int.TryParse(cmbBaud.Text,out int baud)==false)
{
MessageBox.Show("Baudrate 값이 올바르지 않습니다");
return;
}
Connect();
}
}
}

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>

3
AGVEmulator/app.config Normal file
View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/></startup></configuration>

243
AGVEmulator/devBMS.cs Normal file
View File

@@ -0,0 +1,243 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AGVEmulator
{
class DevBMS : AR.Dev.RS232
{
public class RequestBatteryDataArgs : EventArgs
{
public float Volt { get; set; }
public float Remain { get; set; }
public int CurA { get; set; }
public int MaxA { get; set; }
public UInt16 Temp1 { get; set; }
public UInt16 Temp2 { get; set; }
public RequestBatteryDataArgs()
{
this.Volt = 25;
this.Remain = 79f;
this.CurA = 40;
this.MaxA = 80;
Temp1 = 0;
Temp2 = 0;
}
}
public class RequestVoltageDataArgs : EventArgs
{
public UInt16[] cellVolt { get; set; }
public RequestVoltageDataArgs()
{
cellVolt = new UInt16[] { 0, 0, 0, 0, 0, 0, 0, 0 };
}
}
public event EventHandler<RequestBatteryDataArgs> RequestBatteryData;
public event EventHandler<RequestVoltageDataArgs> RequestVoltageData;
public override bool ProcessRecvData(byte[] data)
{
//dd로 시작하고 34개의 데이터(명령이 0D로 끝나면 34이고, 77로 끝나면 34보다크다)
var sample000D = "DD 03 00 1B 0A 65 00 00 21 63 29 04 00 00 2C 92 00 00 00 00 00 00 28 51 03 08 02 0B 69 0B 66 FC 9C 77";
var sampleFC77 = "DD 03 00 1B 0A 6E 0D DD 04 00 10 0D 0A 0D 09 0D 0C 0D 09 0D 0A 0D 09 0D 09 0D 0C FF 38 77 DD 04 00 10 0D 0A 0D 09 0D 0C 0D 09 0D 0A 0D 09 0D 09 0D 0C FF 38 77";
var sampleFD77 = "DD 03 00 1B 0A 6E 00 00 1F 4A 29 04 00 4E 2C D7 00 00 00 00 00 00 28 4C 03 08 02 0B 9F 0B A9 FB A7 77 DD 03 00 1B 0A 6E 00 00 1F 4A 29 04 00 4E 2C D7 00 00 00 00 00 00 28 4C 03 08 02 0B 9F 0B AA FB A6 77";
//basic
var basicinfosmaple = "0A 65 00 00 21 63 29 04 00 00 2C 92 00 00 00 00 00 00 28 51 03 08 02 0B 69 0B 66";
var basic_payload = basicinfosmaple.Split(' ').ToList().Select(t => Convert.ToByte(t, 16)).ToArray(); //old
var barr0D = sample000D.Split(' ').ToList().Select(t => Convert.ToByte(t, 16)).ToArray(); //old
var barrFC77 = sampleFC77.Split(' ').ToList().Select(t => Convert.ToByte(t, 16)).ToArray(); //setting
var barrFD77 = sampleFD77.Split(' ').ToList().Select(t => Convert.ToByte(t, 16)).ToArray(); //normal
if (data.First() != 0xDD || data.Last() != 0x77)
{
RaiseMessage(MessageType.Error, $"stx,etx error data={data.HexString()}");
return false;
}
var sts = data[1];
var cmd = data[2];
RaiseMessage(MessageType.Normal, "Rx:" + data.HexString());
byte sendOld = 0;
if (cmd == 0x03) //get basic info
{
if (RequestBatteryData != null)
{
var p = new RequestBatteryDataArgs();
RequestBatteryData.Invoke(this, p);
//전압
var Volt = (UInt16)(p.Volt * 100);
var arr_volt = BitConverter.GetBytes(Volt).Reverse().ToArray();
Array.Copy(arr_volt, 0, basic_payload, 0, arr_volt.Length);
//잔량A
var CurA = (UInt16)p.CurA;
var arr_cura = BitConverter.GetBytes(CurA).Reverse().ToArray();
Array.Copy(arr_cura, 0, basic_payload, 4, arr_cura.Length);
//최대A
var MaxA = (UInt16)p.MaxA;
var arr_maxa = BitConverter.GetBytes(MaxA).Reverse().ToArray();
Array.Copy(arr_maxa, 0, basic_payload, 6, arr_maxa.Length);
//%
var perc = (byte)(int)p.Remain;
Array.Copy(new byte[] { perc }, 0, basic_payload, 19, 1);
//temp1
var temp1 = BitConverter.GetBytes((UInt16)(p.Temp1 + 2731)).Reverse().ToArray();
Array.Copy(temp1, 0, basic_payload, 23,2);
//temp2
var temp2 = BitConverter.GetBytes((UInt16)(p.Temp2 + 2731)).Reverse().ToArray();
Array.Copy(temp2, 0, basic_payload, 25, 2);
var datalen = (byte)basic_payload.Length;
var payload_cs = new List<byte>();
payload_cs.Add(datalen);
payload_cs.AddRange(basic_payload);
var chks = MakeCheckSum(payload_cs);
var buffer = new List<byte>();
buffer.Add(0xdd);
buffer.Add(cmd);
buffer.Add(0x00);
buffer.Add(datalen);
buffer.AddRange(basic_payload);
buffer.AddRange(chks);
buffer.Add(0x77);
////makechecksum
//UInt16 chksum = 0;
//for (int i = 3; i <= 19; i++)
//{
// chksum += barrFD77[i];
//}
//chksum = (UInt16)(0xFFFF - chksum + 1);
//Array.Copy(BitConverter.GetBytes(chksum).Reverse().ToArray(), 0, barrFD77, 20, 2);
RaiseMessage(MessageType.Normal, $"Volt:{p.Volt}v,Remain:{p.Remain}%,temp1:{p.Temp1/10f},Temp2:{p.Temp2/10f}");
var sendstr = System.Text.Encoding.Default.GetString(barr0D);
RaiseMessage(MessageType.Normal, "Tx:" + barr0D.HexString());
WriteData(buffer.ToArray());
}
}
else if (cmd == 0x04) //get cellvoltage
{
if (RequestVoltageData != null)
{
var p = new RequestVoltageDataArgs();
RequestVoltageData.Invoke(this, p);
//var sig = data.Skip(data.Length - 2).Take(1).First();
//cellvolt 240201
var payload = new byte[8 * 2];
for (int i = 0; i < 8; i++)
{
var volt = p.cellVolt[i];
var arr_cellvolt = BitConverter.GetBytes(volt).Reverse().ToArray();
Array.Copy(arr_cellvolt, 0, payload, (i * 2), arr_cellvolt.Length);
}
//makechecksum
var datalen = (byte)payload.Length;
var payload_cs = new List<byte>();
payload_cs.Add(datalen);
payload_cs.AddRange(payload);
var chksum = MakeCheckSum(payload_cs);
var buffer = new List<byte>();
buffer.Add(0xDD);
buffer.Add(cmd);
buffer.Add(0x00);
buffer.Add(datalen);
buffer.AddRange(payload);
buffer.AddRange(chksum);
buffer.Add(0x77);
WriteData(buffer.ToArray());
RaiseMessage(MessageType.Normal, "Tx:" + buffer.ToArray().HexString());
}
}
return true;
}
public byte[] MakeCheckSum(IEnumerable<byte> payload)
{
UInt16 retval = 0;
var payloadsum = payload.Sum(t => t);
retval = (UInt16)(payloadsum);
retval = (UInt16)(0xFFFF - retval);
retval += 1;
return BitConverter.GetBytes(retval).Reverse().ToArray();
}
protected override bool CustomParser(byte[] buf, out byte[] remainBuffer)
{
//DD A5 03 00 FF FD 77 0D
//remainBuffer = new byte[] { };
List<byte> remain = new List<byte>();
bool retval = false;
foreach (var b in buf)
{
if (retval)
{
remain.Add(b);
continue;
}
if (b == 0xDD) //stx
{
tempBuffer.Clear();
tempBuffer.Add(b);
}
else if (b == 0x0D || b == 0x77) //etx
{
//과거데이터 34개이다 (DD A5 03 00 FF FD 77 0D)
//대쉬보드상태일때(DD A5 03 00 FF FD 77)
//설정상태일때-셀전압표시(DD A5 04 00 FF FC 77)
tempBuffer.Add(b);
retval = true;
}
else
{
if (tempBuffer.Count > 0 && tempBuffer[0] == 0xDD)
{
tempBuffer.Add(b);
if (tempBuffer.Count > 10)
{
RaiseMessage(MessageType.Error, "buffer over");
tempBuffer.Clear();
retval = false;
}
}
else
{
//trash
}
}
}
remainBuffer = remain.ToArray();
return retval;
}
}
}

1711
AGVEmulator/fMain.Designer.cs generated Normal file

File diff suppressed because it is too large Load Diff

943
AGVEmulator/fMain.cs Normal file
View File

@@ -0,0 +1,943 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using static AGVEmulator.DevAGV;
using AGVNavigationCore.Controls;
using AGVNavigationCore.Models;
namespace AGVEmulator
{
public partial class fMain : Form
{
arUtil.Log logAGV, logBMS, logCAL;
DevBMS BMS;
DevAGV AGV;
DevXBE XBE;
// Map Control
private UnifiedAGVCanvas _agvCanvas;
private List<MapNode> _mapNodes;
private VirtualAGV _visualAgv;
// Emulator State
private MapNode _targetNode = null;
private bool _isTurning = false;
private double _targetAngle = 0;
private PointF _currentPosF; // For smooth movement
float BMS_MaxA = 80f;
float BMS_Remain = 79f;
float BMS_CurA = 70f;
float BMS_Volt = 25.4f;
UInt16[] cellvolt = new UInt16[] { 0, 0, 0, 0, 0, 0, 0, 0 };
public fMain()
{
InitializeComponent();
this.Text = $"{Application.ProductName} ver.{Application.ProductVersion}";
// logPLC = new arUtil.Log();
logAGV = new arUtil.Log();
logBMS = new arUtil.Log();
logCAL = new arUtil.Log();
// logPLC.FileNameFormat = "{yyyyMMdd}_PLC";
logAGV.FileNameFormat = "{yyyyMMdd}_AGV";
logBMS.FileNameFormat = "{yyyyMMdd}_BMS";
logCAL.FileNameFormat = "{yyyyMMdd}_CAL";
// logPLC.RaiseMsg += (s1, e1, d1) => { this.rtPLC.AddMsg(s1, e1, d1); };
logAGV.RaiseMsg += (s1, e1, d1) =>
{
if (d1.StartsWith("Tx"))
this.rtAGV.AddMsg(s1, e1, d1);
else
this.rtAGVPro.AddMsg(s1, e1, d1);
};
logBMS.RaiseMsg += (s1, e1, d1) => { this.rtBMS.AddMsg(s1, e1, d1); };
logCAL.RaiseMsg += (s1, e1, d1) => { this.rtCAL.AddMsg(s1, e1, d1); };
var logcolor = new arCtl.sLogMessageColor[] {
new arCtl.sLogMessageColor("NORM", Color.White),
new arCtl.sLogMessageColor("INF", Color.SkyBlue),
new arCtl.sLogMessageColor("WARN", Color.Tomato),
new arCtl.sLogMessageColor("ERR", Color.Red),
new arCtl.sLogMessageColor("RX", Color.Lime),
new arCtl.sLogMessageColor("TX", Color.Orange),
};
//rtPLC.ColorList = logcolor;
rtAGV.ColorList = logcolor;
rtBMS.ColorList = logcolor;
rtCAL.ColorList = logcolor;
rtAGVPro.ColorList = logcolor;
this.FormClosed += Form1_FormClosed;
BMS = new DevBMS();
AGV = new DevAGV();
XBE = new DevXBE();
this.serAGV.dev = AGV;
this.serBMS.dev = BMS;
this.serCAL.dev = XBE;
BMS.Message += BMS_Message;
AGV.Message += AGV_Message;
XBE.Message += CAL_Message;
BMS.RequestBatteryData += Bms_RequestBatteryData;
BMS.RequestVoltageData += BMS_RequestVoltageData;
XBE.ProtocReceived += CAL_ProtocReceived;
AGV.RequestStatusData += Agv_RequestStatusData;
AGV.ValueChanged += Agv_ValueChanged;
AGV.StsValueChanged += Agv_StsValueChanged;
AGV.Command += Agv_Command;
}
Random rnd;
private void Form1_Load(object sender, EventArgs e)
{
rnd = new Random(3000);
serAGV.BaudRate = 57600;
// serPLC.BaudRate = 57600;
trackBar1_Scroll(null, null);
trbT2_Scroll(null, null);
trbT1_Scroll(null, null);
timer1.Start();
//plc inout 이름 설정
List<string> titles = new List<string>();
List<bool> values = new List<bool>();
var arrs = Enum.GetNames(typeof(DevAGV.esystemflag0));
foreach (var item in arrs)
{
var data = (DevAGV.esystemflag0)Enum.Parse(typeof(DevAGV.esystemflag0), item);
var chk = new CheckBox();
chk.Text = $"[{(int)data:00}] {item}";
chk.AutoSize = true;
chk.Visible = true;
chk.Tag = (int)data;
chk.Dock = DockStyle.Top;
chk.CheckedChanged += Chk_CheckedChanged;
this.panel6.Controls.Add(chk);
chk.Checked = true; //기본값 rue
}
arrs = Enum.GetNames(typeof(DevAGV.esystemflag1));
foreach (var item in arrs)
{
var data = (DevAGV.esystemflag1)Enum.Parse(typeof(DevAGV.esystemflag1), item);
var chk = new CheckBox();
chk.Text = $"[{(int)data:00}] {item}";
chk.AutoSize = true;
chk.Visible = true;
chk.Tag = (int)data;
chk.Dock = DockStyle.Top;
chk.CheckedChanged += Chk_CheckedChanged;
this.panel7.Controls.Add(chk);
}
arrs = Enum.GetNames(typeof(DevAGV.esignal));
foreach (var item in arrs)
{
var data = (DevAGV.esignal)Enum.Parse(typeof(DevAGV.esignal), item);
var chk = new CheckBox();
chk.Text = $"[{(int)data:00}] {item}";
chk.AutoSize = true;
chk.Visible = true;
chk.Tag = (int)data;
chk.Dock = DockStyle.Top;
chk.CheckedChanged += Chk_CheckedChanged;
this.panel8.Controls.Add(chk);
}
arrs = Enum.GetNames(typeof(DevAGV.eerror));
foreach (var item in arrs)
{
var data = (DevAGV.eerror)Enum.Parse(typeof(DevAGV.eerror), item);
var chk = new CheckBox();
chk.Text = $"[{(int)data:00}] {item}";
chk.AutoSize = true;
chk.Visible = true;
chk.Tag = (int)data;
chk.Dock = DockStyle.Top;
chk.CheckedChanged += Chk_CheckedChanged;
this.panel9.Controls.Add(chk);
}
//기본 중지상태로 변환
AGV.SetAGV(DevAGV.esystemflag1.agv_stop, true);
foreach (RadioButton rad in groupBox5.Controls)
{
rad.CheckedChanged += (s1, e1) =>
{
if (rad.Checked)
{
AGV.sts_dir = rad.Text[0];
}
};
}
foreach (RadioButton rad in groupBox2.Controls)
{
rad.CheckedChanged += (s1, e1) =>
{
if (rad.Checked)
{
AGV.sts_bunki = rad.Text[0];
}
};
}
foreach (RadioButton rad in groupBox4.Controls)
{
rad.CheckedChanged += (s1, e1) =>
{
if (rad.Checked)
{
AGV.sts_speed = rad.Text[0];
}
};
}
foreach (RadioButton rad in groupBox8.Controls)
{
rad.CheckedChanged += (s1, e1) =>
{
if (rad.Checked)
{
AGV.sts_sensor = rad.Text[0];
}
};
}
MakeViewer();
InitializeMapControl();
agvViewer1.TagTouched += AgvViewer1_TagTouched;
agvViewer1.MarkTouched += AgvViewer1_MarkTouched;
agvViewer1.Command += AgvViewer1_Command;
}
void MakeViewer()
{
//태그목록
List<UC.AgvViewer.ptdata> listTAG = new List<UC.AgvViewer.ptdata>();
//var strtag = new string[] {
// "9000", "9001", //not
// "9010", "9011",
// //"9020", "9021",
// "9030", "9031",
// // "9040", "9041",
// "9050", "9051",
// // "9060", "9061",
// "9070", "9071",
// "9080", "9081",
// "9090", "9091"};
//foreach(var tag in strtag)
//{
// listTAG.Add(new UC.AgvViewer.ptdata {
// active=false,
// data = tag,
// pos =
// });
//}
//agvViewer1.listTAG = listTAG.ToArray();
//마크지점
List<UC.AgvViewer.ptdata> listMRK = new List<UC.AgvViewer.ptdata>();
var strmark = new string[] { "NOT", "QC", "CHG", "{0}", "#1", "{1}", "#2", "{2}", "#3", "{3}", "#4", "POT" };
var pos = 50;
var tagstart = 9000;
foreach (var item in strmark)
{
listMRK.Add(new UC.AgvViewer.ptdata
{
pos = pos,
data = item,
});
if (item.Equals("CHG"))
{
pos += 100;
continue;
}
else if (item.Equals("{0}")) tagstart = 9350;
else if (item.Equals("{1}")) tagstart = 9450;
else if (item.Equals("{2}")) tagstart = 9550;
else if (item.Equals("{3}")) tagstart = 9650;
else if (item.Equals("#1")) tagstart = 9400;
else if (item.Equals("#2")) tagstart = 9500;
else if (item.Equals("#3")) tagstart = 9600;
else if (item.Equals("#4")) tagstart = 9700;
//지정위치 좌우에 태그를 심는다
listTAG.Add(new UC.AgvViewer.ptdata
{
pos = pos - 20,
data = tagstart.ToString(),
});
if (item.Equals("NOT") == false && item.Equals("POT") == false && item.StartsWith("{") == false)
tagstart += 1;
listTAG.Add(new UC.AgvViewer.ptdata
{
pos = pos + 20,
data = tagstart.ToString(),
});
pos += 100;
if (item.Equals("NOT") == false && item.Equals("POT") == false && item.StartsWith("{") == false)
tagstart += 99;
else
tagstart += 100;
if (item.Equals("NOT"))
tagstart = 9300;
//else
//{
// pos += 100;
// tagstart += 10;
//}
}
agvViewer1.listMRK = listMRK.ToArray();
agvViewer1.listTAG = listTAG.ToArray();
agvViewer1.Invalidate();
}
private void AgvViewer1_Command(object sender, UC.AgvViewer.TagArgs e)
{
if (e.Data == "stop")
{
AGV.SetAGV(esystemflag1.agv_run, false);
AGV.SetAGV(esystemflag1.agv_stop, true);
logAGV.Add("시뮬로부터 자동 중지");
}
}
private void AgvViewer1_MarkTouched(object sender, UC.AgvViewer.TagArgs e)
{
// throw new NotImplementedException();
AGV.SetAGV(esignal.mark_sensor_1, e.Active);
logAGV.Add($"mark {e.Data} touch:{e.Active}");
}
private void AgvViewer1_TagTouched(object sender, UC.AgvViewer.TagArgs e)
{
logAGV.Add($"tag touch:{e.Data}");
numericUpDown1.Text = e.Data;// decimal.Parse(e.Data);
button18.PerformClick();
UpdateVisualAgvPosition(e.Data);
}
void InitializeMapControl()
{
_agvCanvas = new UnifiedAGVCanvas();
_agvCanvas.Dock = DockStyle.Fill;
_agvCanvas.Mode = UnifiedAGVCanvas.CanvasMode.Emulator; // Enable Emulator Mode
_agvCanvas.NodeRightClicked += _agvCanvas_NodeRightClicked;
this.tabPage4.Controls.Add(_agvCanvas);
// Load Map
// Try to find map file in standard location
string mapDir = Path.GetFullPath(Path.Combine(Application.StartupPath, @"..\..\..\..\Cs_HMI\Data"));
string mapPath = Path.Combine(mapDir, "NewMap.agvmap");
if (!File.Exists(mapPath))
{
if (Directory.Exists(mapDir))
{
var files = Directory.GetFiles(mapDir, "*.agvmap");
if (files.Length > 0) mapPath = files[0];
}
}
if (File.Exists(mapPath))
{
try
{
var mapresult = MapLoader.LoadMapFromFile(mapPath);
_mapNodes = mapresult.Nodes;
_agvCanvas.Nodes = _mapNodes;
_agvCanvas.FitToNodes();
// Initialize Visual AGV
if (_mapNodes.Count > 0)
{
_visualAgv = new VirtualAGV("AGV01", _mapNodes[0].Position);
_agvCanvas.AGVList = new List<IAGV> { _visualAgv };
}
}
catch (Exception ex)
{
logAGV.Add($"Map Load Error: {ex.Message}");
}
}
}
void UpdateVisualAgvPosition(string tag)
{
if (_visualAgv == null || _mapNodes == null) return;
// Find node by tag
// Assuming NodeId might be the tag or contain it
var node = _mapNodes.FirstOrDefault(n => n.NodeId == tag || n.NodeId.EndsWith(tag));
// If not found, try to parse tag as int and match
if (node == null && int.TryParse(tag, out int tagNum))
{
node = _mapNodes.FirstOrDefault(n => n.NodeId == tagNum.ToString());
}
if (node != null)
{
_visualAgv.SetPosition(node, (AGV.sts_dir == 'F' ? AgvDirection.Forward : AgvDirection.Backward));
UpdateVisualAGV();
}
}
char GetGroupItemCheckbox(GroupBox grp)
{
foreach (var ctl in grp.Controls)
{
if (ctl is RadioButton)
{
var rad = ctl as RadioButton;
if (rad.Checked) return rad.Text[0];
}
}
return '0';
}
int PLC_LeftPosition = 0;
int PLC_RightPosition = 0;
int PLC_LeftDir = 0;
int PLC_RightDir = 0;
public void SetBit(ref UInt16 _value, int idx, Boolean value)
{
if (value)
{
var offset = (UInt16)(1 << idx);
_value = (UInt16)(_value | offset);
}
else
{
var offset = (UInt16)(~(1 << idx));
_value = (UInt16)(_value & offset);
}
}
public void SetBit(ref byte _value, int idx, Boolean value)
{
if (value)
{
var offset = (byte)(1 << idx);
_value = (byte)(_value | offset);
}
else
{
var offset = (byte)(~(1 << idx));
_value = (byte)(_value & offset);
}
}
void aaplycheckboxbit(ref UInt16 v, Panel p)
{
foreach (CheckBox chk in p.Controls)
{
var idx = int.Parse(chk.Tag.ToString());
SetBit(ref v, idx, chk.Checked);
}
}
void aaplycheckboxbit(ref byte v, Panel p)
{
foreach (CheckBox chk in p.Controls)
{
var idx = int.Parse(chk.Tag.ToString());
SetBit(ref v, idx, chk.Checked);
}
}
private void button5_Click(object sender, EventArgs e)
{
AGV.WriteData("ACK");
}
private void button4_Click(object sender, EventArgs e)
{
AGV.WriteData("NAK");
}
private void trackBar1_Scroll(object sender, EventArgs e)
{
BMS_MaxA = float.Parse(label4.Text);
BMS_CurA = (float)(trackBar1.Value / 100f);
BMS_Remain = 100f * (BMS_CurA / BMS_MaxA);// (trackBar1.Value / 10f);
var minvolt = 20.2f;
var maxvolt = 26.8f;
// BMS_CurA = (BMS_MaxA * (BMS_Remain / 100f));
label3.Text = BMS_CurA.ToString("N0"); //curr amp
label5.Text = $"{BMS_Remain:N2}%";
BMS_Volt = minvolt + ((maxvolt - minvolt) * (BMS_CurA / BMS_MaxA));
label6.Text = $"{BMS_Volt:N2}v";
}
private void timer1_Tick(object sender, EventArgs e)
{
timer1.Stop();
sbAGV.ForeColor = AGV.IsOpen ? Color.ForestGreen : Color.Red;
// sbPLC.ForeColor = PLC.IsOpen ? Color.ForestGreen : Color.Red;
sbBMS.ForeColor = BMS.IsOpen ? Color.ForestGreen : Color.Red;
sbCAL.ForeColor = XBE.IsOpen ? Color.ForestGreen : Color.Red;
//if (checkBox2.Checked && PLC != null && PLC.IsOpen) PLC.AutoSendData(); //자동전송해야함
if (checkBox3.Checked && AGV != null && AGV.IsOpen) AGV.AutoSendData(); //자동전송해야함
if (BMS != null && BMS.IsOpen) BMS.AutoSendData(); //자동전송해야함
if (XBE != null && XBE.IsOpen) XBE.AutoSendData(); //자동전송해야함
if (chkSimulation.Checked && AGV.GetAGV(esystemflag1.agv_run))
{
if (radioButton15.Checked)
{
agvViewer1.dir = 1;
}
else
{
agvViewer1.dir = -1;
}
if (radioButton12.Checked) agvViewer1.mspd = 7;
else if (radioButton11.Checked) agvViewer1.mspd = 20;
else agvViewer1.mspd = 40;
agvViewer1.Invalidate();
UpdateEmulatorLogic();
}
timer1.Start();
}
private void UpdateVisualAGV()
{
if (_visualAgv != null)
{
_agvCanvas.UpdateAGVPosition(_visualAgv.AgvId, _visualAgv.CurrentPosition);
// Update direction if needed, though UnifiedAGVCanvas uses Enum
// _agvCanvas.UpdateAGVDirection(_visualAgv.AgvId, _visualAgv.CurrentDirection);
_agvCanvas.Invalidate();
}
}
private void UpdateEmulatorLogic()
{
if (_visualAgv == null || _mapNodes == null) return;
// Initialize float position if needed
if (_currentPosF.IsEmpty) _currentPosF = _visualAgv.CurrentPosition;
// Movement Logic
double speed = (agvViewer1.mspd > 0 ? agvViewer1.mspd : 10) * 0.2; // Scale speed
if (_isTurning)
{
// Turn Logic: 90 deg in 5 sec = 18 deg/sec
// Timer is likely 100ms? (Default WinForms timer) -> 1.8 deg/tick
double turnSpeed = 1.8;
double diff = _targetAngle - _visualAgv.Angle;
// Normalize diff to -180 to 180
while (diff > 180) diff -= 360;
while (diff <= -180) diff += 360;
if (Math.Abs(diff) < turnSpeed)
{
_visualAgv.Angle = _targetAngle;
_isTurning = false;
}
else
{
_visualAgv.Angle += Math.Sign(diff) * turnSpeed;
}
}
else
{
// Move Forward
double rad = _visualAgv.Angle * Math.PI / 180.0;
_currentPosF.X += (float)(speed * Math.Cos(rad));
_currentPosF.Y += (float)(speed * Math.Sin(rad));
_visualAgv.CurrentPosition = Point.Round(_currentPosF);
// Check for Nodes (RFID Trigger)
foreach (var node in _mapNodes)
{
double dist = Math.Sqrt(Math.Pow(node.Position.X - _visualAgv.CurrentPosition.X, 2) + Math.Pow(node.Position.Y - _visualAgv.CurrentPosition.Y, 2));
if (dist < 15) // Hit Node
{
// Send Tag
if (node.NodeId != numericUpDown1.Text)
{
if (int.TryParse(node.NodeId, out int tag))
{
AGV.SendTag(node.NodeId);
numericUpDown1.Text = node.NodeId;
// Snap to node
_currentPosF = node.Position;
_visualAgv.CurrentPosition = node.Position;
// Decide Next Move (Turn/Straight)
DecideNextMove(node);
}
}
}
}
}
UpdateVisualAGV();
}
private void DecideNextMove(MapNode currentNode)
{
// Simple logic:
// If AGV.sts_bunki == 'L', turn -90
// If AGV.sts_bunki == 'R', turn +90
// Else keep straight
if (AGV.sts_bunki == 'L')
{
_targetAngle = _visualAgv.Angle - 90;
_isTurning = true;
}
else if (AGV.sts_bunki == 'R')
{
_targetAngle = _visualAgv.Angle + 90;
_isTurning = true;
}
// Normalize Target Angle
while (_targetAngle >= 360) _targetAngle -= 360;
while (_targetAngle < 0) _targetAngle += 360;
}
private void _agvCanvas_NodeRightClicked(object sender, MapNode e)
{
if (e != null && _visualAgv != null)
{
_visualAgv.CurrentPosition = e.Position;
_currentPosF = e.Position;
if (int.TryParse(e.NodeId, out int tag))
{
numericUpDown1.Text = tag.ToString();
}
UpdateVisualAGV();
private void button14_Click(object sender, EventArgs e)
{
//agv 정지
//비상정지 에러 플래그
AGV.SetAGV(DevAGV.eerror.Emergency, true);
AGV.SetAGV(DevAGV.esystemflag1.agv_run, false);
AGV.SetAGV(DevAGV.esystemflag1.agv_stop, true);
}
private void button16_Click(object sender, EventArgs e)
{
AGV.SetAGV(DevAGV.esystemflag1.stop_by_front_detect, true);
}
private void button15_Click(object sender, EventArgs e)
{
AGV.SetAGV(DevAGV.esystemflag1.stop_by_front_detect, false);
}
private void button17_Click(object sender, EventArgs e)
{
//agv 정지
//비상정지 에러 플래그
AGV.SetAGV(DevAGV.eerror.Emergency, false);
AGV.SetAGV(DevAGV.eerror.line_out_error, false);
AGV.SetAGV(DevAGV.eerror.Overcurrent, false);
AGV.SetAGV(DevAGV.esystemflag1.agv_run, false);
AGV.SetAGV(DevAGV.esystemflag1.agv_stop, true);
}
private void Chk_CheckedChanged(object sender, EventArgs e)
{
var chk = sender as CheckBox;
var idx = int.Parse(chk.Tag.ToString());
switch (chk.Parent.Tag.ToString().ToLower())
{
case "s0":
var v0 = (DevAGV.esystemflag0)idx;
AGV.SetAGV(v0, chk.Checked);
break;
case "s1":
var v1 = (DevAGV.esystemflag1)idx;
AGV.SetAGV(v1, chk.Checked);
break;
case "er":
var v2 = (DevAGV.eerror)idx;
AGV.SetAGV(v2, chk.Checked);
break;
case "sg":
var v3 = (DevAGV.esignal)idx;
AGV.SetAGV(v3, chk.Checked);
break;
}
chk.BackColor = chk.Checked ? Color.Lime : SystemColors.Window;
}
private void button18_Click(object sender, EventArgs e)
{
if (int.TryParse(numericUpDown1.Text, out int tagno))
{
AGV.SendTag(tagno.ToString());
numericUpDown1.SelectAll();
numericUpDown1.Focus();
}
}
private void agvViewer1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
agvViewer1.StopbyMark = !agvViewer1.StopbyMark;
}
else if (e.Button == MouseButtons.Right)
{
MakeViewer();
}
}
private void button6_Click(object sender, EventArgs e)
{
var target = (byte)nudIDAgv.Value;
var tagno = (uint)nudTagNo.Value;
this.XBE.SendGotoTag(target, tagno);
}
private void button1_Click(object sender, EventArgs e)
{
var target = (byte)nudIDAgv.Value;
var tagno = (uint)numericUpDown2.Value;
this.XBE.SendCurrentPos(target, tagno);
}
UInt16 Temp1, Temp2;
private void trbT1_Scroll(object sender, EventArgs e)
{
//값에 /10해서 표시한다.
Temp1 = (UInt16)trbT1.Value;
label10.Text = $"{Temp1 / 10f}º";
}
private void numericUpDown1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
button18.PerformClick();
}
private void toolStripButton3_Click(object sender, EventArgs e)
{
var file = @"C:\Data\Amkor\AGV4\route\NewMap.agvmap";
LoadMapFile(file);
}
private string _currentMapFilePath;
private void LoadMapFile(string filePath)
{
try
{
var result = MapLoader.LoadMapFromFile(filePath);
if (result.Success)
{
Console.WriteLine($"Map File Load : {filePath}");
_mapNodes = result.Nodes;
_currentMapFilePath = filePath;
// RFID 자동 할당 제거 - 에디터에서 설정한 값 그대로 사용
// 시뮬레이터 캔버스에 맵 설정
this._agvCanvas.Nodes = _mapNodes;
// 맵 설정 적용 (배경색, 그리드 표시)
if (result.Settings != null)
{
_agvCanvas.BackColor = System.Drawing.Color.FromArgb(result.Settings.BackgroundColorArgb);
_agvCanvas.ShowGrid = result.Settings.ShowGrid;
}
// 맵에 맞춤
_agvCanvas.FitToNodes();
}
else
{
throw new InvalidOperationException($"맵 파일 로드 실패: {result.ErrorMessage}");
}
}
catch (Exception ex)
{
throw new InvalidOperationException($"맵 파일 로드 실패: {ex.Message}", ex);
}
}
private void toolStripButton4_Click(object sender, EventArgs e)
{
// Change rotation (180 degrees)
if (_visualAgv != null)
{
_visualAgv.Angle = (_visualAgv.Angle + 180) % 360;
UpdateVisualAGV();
}
}
private void toolStripButton5_Click(object sender, EventArgs e)
{
// Set AGV Position by RFID
string input = "";
using (Form form = new Form())
{
form.Text = "Set AGV Position";
form.Size = new Size(300, 150);
Label lbl = new Label() { Left = 20, Top = 20, Text = "Enter RFID (Node ID):", AutoSize = true };
TextBox txt = new TextBox() { Left = 20, Top = 45, Width = 240 };
Button btn = new Button() { Text = "OK", Left = 180, Width = 80, Top = 75, DialogResult = DialogResult.OK };
form.Controls.Add(lbl);
form.Controls.Add(txt);
form.Controls.Add(btn);
form.AcceptButton = btn;
form.StartPosition = FormStartPosition.CenterParent;
if (form.ShowDialog() == DialogResult.OK)
{
input = txt.Text;
}
}
if (!string.IsNullOrEmpty(input) && _mapNodes != null && _visualAgv != null)
{
var node = _mapNodes.FirstOrDefault(n => n.NodeId == input);
if (node != null)
{
_visualAgv.CurrentPosition = node.Position;
_currentPosF = node.Position;
numericUpDown1.Text = node.NodeId;
// Auto-orient: Prefer Right (0) or Up (270)
var neighbors = _mapNodes.Where(n => n != node &&
Math.Sqrt(Math.Pow(n.Position.X - node.Position.X, 2) + Math.Pow(n.Position.Y - node.Position.Y, 2)) < 150)
.ToList();
if (neighbors.Any())
{
var rightNeighbor = neighbors.FirstOrDefault(n => n.Position.X > node.Position.X && Math.Abs(n.Position.Y - node.Position.Y) < 50);
if (rightNeighbor != null)
{
_visualAgv.Angle = 0;
}
else
{
var upNeighbor = neighbors.FirstOrDefault(n => n.Position.Y < node.Position.Y && Math.Abs(n.Position.X - node.Position.X) < 50);
if (upNeighbor != null)
{
_visualAgv.Angle = 270;
}
else
{
var first = neighbors.First();
double angle = Math.Atan2(first.Position.Y - node.Position.Y, first.Position.X - node.Position.X) * 180 / Math.PI;
if (angle < 0) angle += 360;
_visualAgv.Angle = angle;
}
}
}
UpdateVisualAGV();
}
else
{
MessageBox.Show($"Node '{input}' not found.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private void trbT2_Scroll(object sender, EventArgs e)
{
Temp2 = (UInt16)trbT2.Value;
label11.Text = $"{Temp2 / 10f}º";
}
private void toolStripButton1_Click(object sender, EventArgs e)
{
serAGV.Connect();
serBMS.Connect();
serCAL.Connect();
}
private void toolStripButton2_Click(object sender, EventArgs e)
{
serAGV.Disconnect();
serBMS.Disconnect();
serCAL.Disconnect();
}
}
}

205
AGVEmulator/fMain.resx Normal file
View File

@@ -0,0 +1,205 @@
<?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>
<metadata name="timer1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>104, 17</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="toolStripButton1.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK
YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X
/aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t
I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM
cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh
6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD
lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A
HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb
1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC
nOccAdABIDXXE1nzAAAAAElFTkSuQmCC
</value>
</data>
<data name="toolStripButton2.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK
YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X
/aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t
I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM
cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh
6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD
lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A
HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb
1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC
nOccAdABIDXXE1nzAAAAAElFTkSuQmCC
</value>
</data>
<data name="toolStripButton3.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK
YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X
/aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t
I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM
cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh
6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD
lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A
HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb
1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC
nOccAdABIDXXE1nzAAAAAElFTkSuQmCC
</value>
</data>
<data name="toolStripButton4.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK
YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X
/aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t
I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM
cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh
6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD
lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A
HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb
1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC
nOccAdABIDXXE1nzAAAAAElFTkSuQmCC
</value>
</data>
<data name="toolStripButton5.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK
YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X
/aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t
I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM
cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh
6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD
lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A
HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb
1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC
nOccAdABIDXXE1nzAAAAAElFTkSuQmCC
</value>
</data>
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>210, 17</value>
</metadata>
</root>

View File

@@ -1775,6 +1775,11 @@ namespace AGVNavigationCore.Controls
/// </summary> /// </summary>
private Color GetAGVCenterColor(AGVState state) private Color GetAGVCenterColor(AGVState state)
{ {
// Stop-Mark 상태 (Error 상태와 유사하게 처리하거나 별도 처리)
// 여기서는 AGVState에 StopMark가 없으므로, 외부에서 상태를 Error로 설정하거나
// 별도의 플래그를 확인해야 함. 하지만 IAGV 인터페이스에는 플래그가 없음.
// 따라서 fMain에서 상태를 Error로 설정하는 것을 권장.
switch (state) switch (state)
{ {
case AGVState.Moving: return Color.White; case AGVState.Moving: return Color.White;
@@ -1794,7 +1799,7 @@ namespace AGVNavigationCore.Controls
{ {
case AGVState.Moving: return Color.DarkGreen; case AGVState.Moving: return Color.DarkGreen;
case AGVState.Charging: return Color.DarkBlue; case AGVState.Charging: return Color.DarkBlue;
case AGVState.Error: return Color.DarkRed; case AGVState.Error: return Color.DarkRed; // Stop-Mark 시 Error 상태 사용
case AGVState.Docking: return Color.DarkOrange; case AGVState.Docking: return Color.DarkOrange;
default: return Color.DarkGray; default: return Color.DarkGray;
} }

View File

@@ -17,6 +17,16 @@ namespace AGVNavigationCore.Controls
var worldPoint = ScreenToWorld(e.Location); var worldPoint = ScreenToWorld(e.Location);
var hitNode = GetNodeAt(worldPoint); var hitNode = GetNodeAt(worldPoint);
// 에뮬레이터 모드 처리
if (_canvasMode == CanvasMode.Emulator)
{
if (e.Button == MouseButtons.Right && hitNode != null)
{
NodeRightClicked?.Invoke(this, hitNode);
}
return;
}
// 🔥 어떤 모드에서든 노드/빈 공간 클릭 시 선택 이벤트 발생 (속성창 업데이트) // 🔥 어떤 모드에서든 노드/빈 공간 클릭 시 선택 이벤트 발생 (속성창 업데이트)
bool ctrlPressed = (ModifierKeys & Keys.Control) == Keys.Control; bool ctrlPressed = (ModifierKeys & Keys.Control) == Keys.Control;

View File

@@ -36,7 +36,8 @@ namespace AGVNavigationCore.Controls
public enum CanvasMode public enum CanvasMode
{ {
Edit, // 편집 가능 (맵 에디터) Edit, // 편집 가능 (맵 에디터)
Sync // 동기화 모드 (장비 설정 동기화) Sync, // 동기화 모드 (장비 설정 동기화)
Emulator // 에뮬레이터 모드
} }
/// <summary> /// <summary>
@@ -146,6 +147,9 @@ namespace AGVNavigationCore.Controls
// 컨텍스트 메뉴 // 컨텍스트 메뉴
private ContextMenuStrip _contextMenu; private ContextMenuStrip _contextMenu;
// 이벤트
public event EventHandler<MapNode> NodeRightClicked;
#endregion #endregion
#region Events #region Events

View File

@@ -81,6 +81,13 @@ namespace AGVNavigationCore.Models
private List<string> _detectedRfids = new List<string>(); // 감지된 RFID 목록 private List<string> _detectedRfids = new List<string>(); // 감지된 RFID 목록
private bool _isPositionConfirmed = false; // 위치 확정 여부 (RFID 2개 이상 감지) private bool _isPositionConfirmed = false; // 위치 확정 여부 (RFID 2개 이상 감지)
// 에뮬레이터용 추가 속성
public double Angle { get; set; } = 0; // 0 = Right, 90 = Down, 180 = Left, 270 = Up (Standard Math)
// But AGV Direction: Forward usually means "Front of AGV".
// Let's assume Angle is the orientation of the AGV in degrees.
public bool IsStopMarkOn { get; set; } = false;
#endregion #endregion
#region Properties #region Properties
@@ -433,6 +440,27 @@ namespace AGVNavigationCore.Models
OnError("긴급 정지가 실행되었습니다."); OnError("긴급 정지가 실행되었습니다.");
} }
/// <summary>
/// 일시 정지 (경로 유지)
/// </summary>
public void Pause()
{
_isMoving = false;
_currentSpeed = 0;
}
/// <summary>
/// 이동 재개
/// </summary>
public void Resume()
{
if (_currentPath != null && _remainingNodes != null && _remainingNodes.Count > 0)
{
_isMoving = true;
SetState(AGVState.Moving);
}
}
#endregion #endregion
#region Public Methods - ( ) #region Public Methods - ( )

View File

@@ -15,6 +15,8 @@ using Newtonsoft.Json;
using AGVNavigationCore.PathFinding.Planning; using AGVNavigationCore.PathFinding.Planning;
using AGVNavigationCore.PathFinding.Core; using AGVNavigationCore.PathFinding.Core;
using AGVSimulator.Models; using AGVSimulator.Models;
using System.IO.Ports;
using System.Text;
namespace AGVSimulator.Forms namespace AGVSimulator.Forms
{ {
@@ -25,6 +27,86 @@ namespace AGVSimulator.Forms
{ {
#region Fields #region Fields
// Emulator Fields
private SerialPort _emulatorPort;
private ComboBox _portCombo;
private Button _connectButton;
private bool _isEmulatorConnected = false;
// Emulator State Fields
private UInt16 _emu_system0 = 0;
private UInt16 _emu_system1 = 0;
private UInt16 _emu_error = 0;
private byte _emu_signal = 0;
private char _emu_sts_bunki = 'S';
private char _emu_sts_speed = 'L';
private char _emu_sts_dir = 'F';
private char _emu_sts_sensor = '1';
private string _lastSentNodeId = null;
public enum esystemflag0
{
Memory_RW_State = 5,
EXT_IO_Conn_State,
RFID_Conn_State,
M5E_Module_Run_State = 8,
Front_Ultrasonic_Conn_State,
Front_Untrasonic_Sensor_State,
Side_Ultrasonic_Conn_State,
Side_Ultrasonic_Sensor_State = 12,
Front_Guide_Sensor_State,
Rear_Guide_Sensor_State,
Battery_Level_Check
}
public enum esystemflag1
{
Side_Detect_Ignore = 3,
Melody_check,
Mark2_check,
Mark1_check,
gateout_check,
Battery_charging = 8,
re_Start,
front_detect_ignore,
front_detect_check,
stop_by_front_detect = 12,
stop_by_cross_in,
agv_stop,
agv_run
}
public enum eerror
{
Emergency = 0,
Overcurrent,
Charger_run_error,
Charger_pos_error,
line_out_error = 4,
runerror_by_no_magent_line,
controller_comm_error = 11,
arrive_ctl_comm_error,
door_ctl_comm_error,
charger_comm_error,
cross_ctrl_comm_error,
}
public enum esignal
{
front_gate_out = 0,
rear_sensor_out,
mark_sensor_1,
mark_sensor_2,
front_left_sensor,
front_right_sensor,
front_center_sensor,
charger_align_sensor,
}
public enum estsvaluetype
{
bunki,
speed,
direction,
sensor
}
private UnifiedAGVCanvas _simulatorCanvas; private UnifiedAGVCanvas _simulatorCanvas;
private List<MapNode> _mapNodes; private List<MapNode> _mapNodes;
private AGVPathfinder _advancedPathfinder; private AGVPathfinder _advancedPathfinder;
@@ -92,6 +174,9 @@ namespace AGVSimulator.Forms
// 방향 콤보박스 초기화 // 방향 콤보박스 초기화
InitializeDirectionCombo(); InitializeDirectionCombo();
// 에뮬레이터 UI 초기화
InitializeEmulatorUI();
// 초기 상태 설정 // 초기 상태 설정
UpdateUI(); UpdateUI();
@@ -716,12 +801,32 @@ namespace AGVSimulator.Forms
foreach (var agv in _agvList) foreach (var agv in _agvList)
{ {
agv.Update(100); // 100ms 간격으로 업데이트 agv.Update(100); // 100ms 간격으로 업데이트
// Emulator Tag Logic
if (_isEmulatorConnected && agv == _agvList.FirstOrDefault())
{
if (agv.CurrentNodeId != null && agv.CurrentNodeId != _lastSentNodeId)
{
var rfid = GetRfidByNodeId(agv.CurrentNodeId);
if (!string.IsNullOrEmpty(rfid))
{
SendTag(rfid);
_lastSentNodeId = agv.CurrentNodeId;
}
}
}
} }
} }
// UI 업데이트 // UI 업데이트
UpdateUI(); UpdateUI();
_simulatorCanvas.Invalidate(); // 화면 다시 그리기 _simulatorCanvas.Invalidate(); // 화면 다시 그리기
// 에뮬레이터 상태 전송
if (_isEmulatorConnected)
{
SendEmulatorStatus();
}
} }
#endregion #endregion
@@ -2005,6 +2110,412 @@ namespace AGVSimulator.Forms
MessageBoxButtons.OK, MessageBoxIcon.Error); MessageBoxButtons.OK, MessageBoxIcon.Error);
} }
} }
#region Emulator Logic
private void InitializeEmulatorUI()
{
// 에뮬레이터 제어 패널 생성
var emulatorPanel = new GroupBox();
emulatorPanel.Text = "AGV Emulator (RS232)";
emulatorPanel.Height = 60;
emulatorPanel.Dock = DockStyle.Top;
var layout = new FlowLayoutPanel();
layout.Dock = DockStyle.Fill;
layout.Padding = new Padding(5);
layout.AutoSize = true;
// 포트 선택 콤보박스
_portCombo = new ComboBox();
_portCombo.Width = 100;
_portCombo.DropDownStyle = ComboBoxStyle.DropDownList;
_portCombo.DropDown += (s, e) => {
_portCombo.Items.Clear();
_portCombo.Items.AddRange(SerialPort.GetPortNames());
};
_portCombo.Items.AddRange(SerialPort.GetPortNames());
if (_portCombo.Items.Count > 0) _portCombo.SelectedIndex = 0;
// 연결 버튼
_connectButton = new Button();
_connectButton.Text = "Connect";
_connectButton.Click += OnConnectEmulator_Click;
layout.Controls.Add(new Label { Text = "Port:", AutoSize = true, Margin = new Padding(3, 8, 3, 3) });
layout.Controls.Add(_portCombo);
layout.Controls.Add(_connectButton);
emulatorPanel.Controls.Add(layout);
// 폼의 최상단에 추가
this.Controls.Add(emulatorPanel);
emulatorPanel.BringToFront();
}
private void OnConnectEmulator_Click(object sender, EventArgs e)
{
if (_isEmulatorConnected)
{
DisconnectEmulator();
}
else
{
if (_portCombo.SelectedItem == null)
{
MessageBox.Show("포트를 선택해주세요.");
return;
}
ConnectEmulator(_portCombo.SelectedItem.ToString());
}
}
private void ConnectEmulator(string portName)
{
try
{
_emulatorPort = new SerialPort(portName, 115200, Parity.None, 8, StopBits.One);
_emulatorPort.DataReceived += OnEmulatorDataReceived;
_emulatorPort.Open();
_isEmulatorConnected = true;
_connectButton.Text = "Disconnect";
_portCombo.Enabled = false;
MessageBox.Show($"에뮬레이터 시작: {portName}");
}
catch (Exception ex)
{
MessageBox.Show($"연결 실패: {ex.Message}");
}
}
private void DisconnectEmulator()
{
try
{
if (_emulatorPort != null && _emulatorPort.IsOpen)
{
_emulatorPort.Close();
}
_isEmulatorConnected = false;
_connectButton.Text = "Connect";
_portCombo.Enabled = true;
}
catch (Exception ex)
{
MessageBox.Show($"해제 실패: {ex.Message}");
}
}
private StringBuilder _recvBuffer = new StringBuilder();
private void OnEmulatorDataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
string data = _emulatorPort.ReadExisting();
_recvBuffer.Append(data);
string buffer = _recvBuffer.ToString();
int stxIndex = buffer.IndexOf((char)0x02);
while (stxIndex >= 0)
{
int etxIndex = buffer.IndexOf((char)0x03, stxIndex);
if (etxIndex > stxIndex)
{
string packet = buffer.Substring(stxIndex + 1, etxIndex - stxIndex - 1);
ProcessEmulatorPacket(packet);
// 처리된 패킷 제거
buffer = buffer.Substring(etxIndex + 1);
stxIndex = buffer.IndexOf((char)0x02);
}
else
{
break; // ETX 아직 안옴
}
}
_recvBuffer.Clear();
_recvBuffer.Append(buffer);
}
catch (Exception ex)
{
Console.WriteLine($"Emulator Recv Error: {ex.Message}");
}
}
#region Emulator Helpers
private bool GetBit(ref UInt16 _value, int idx)
{
var offset = (UInt16)(1 << idx);
return (_value & offset) != 0;
}
private bool SetBit(ref UInt16 _value, int idx, bool value)
{
var oldvalue = GetBit(ref _value, idx);
if (value)
{
var offset = (UInt16)(1 << idx);
_value = (UInt16)(_value | offset);
}
else
{
var offset = (UInt16)(~(1 << idx));
_value = (UInt16)(_value & offset);
}
return oldvalue != value;
}
private bool SetBit(ref byte _value, int idx, bool value)
{
var offset = (byte)(1 << idx);
if (value) _value |= offset;
else _value &= (byte)~offset;
return true;
}
private void SetAGV(esystemflag0 flag, bool value)
{
SetBit(ref _emu_system0, (int)flag, value);
}
private void SetAGV(esystemflag1 flag, bool value)
{
SetBit(ref _emu_system1, (int)flag, value);
}
private void SetAGV(eerror flag, bool value)
{
SetBit(ref _emu_error, (int)flag, value);
}
private void SetAGV(esignal flag, bool value)
{
SetBit(ref _emu_signal, (int)flag, value);
}
private void SetSTS(estsvaluetype target, char value)
{
switch (target)
{
case estsvaluetype.sensor: _emu_sts_sensor = value; break;
case estsvaluetype.direction: _emu_sts_dir = value; break;
case estsvaluetype.speed: _emu_sts_speed = value; break;
case estsvaluetype.bunki: _emu_sts_bunki = value; break;
}
}
#endregion
private void ProcessEmulatorPacket(string packet)
{
// Packet: CMD(3) + DATA(...) + Checksum(2)
// But here packet is substring between STX and ETX.
// Example: STS...Checksum
if (packet.Length < 3) return;
string cmd = packet.Substring(0, 3);
string data = "";
if (packet.Length > 5) // CMD + Checksum(2)
{
data = packet.Substring(3, packet.Length - 5);
}
// AGV 제어 (첫 번째 AGV 대상)
var agv = _agvList.FirstOrDefault();
this.Invoke(new Action(() => {
switch (cmd)
{
case "CRN": // 기동명령
if (data.Length > 0) SetSTS(estsvaluetype.direction, data[0]);
SetAGV(esystemflag1.agv_stop, false);
SetAGV(esystemflag1.agv_run, true);
if (agv != null) agv.Resume();
break;
case "CST": // 중지명령
if (data.StartsWith("M"))
{
// Mark Stop
// TODO: Implement Mark Stop logic in VirtualAGV if needed
// For now, just log it
Console.WriteLine("Mark Stop Command Received");
}
else
{
SetAGV(esystemflag1.agv_run, false);
SetAGV(esystemflag1.agv_stop, true);
if (agv != null) agv.Pause();
}
break;
case "CBR": // 분기명령
// FSL1
if (data.Length >= 4)
{
SetSTS(estsvaluetype.direction, data[0]);
SetSTS(estsvaluetype.bunki, data[1]);
SetSTS(estsvaluetype.speed, data[2]);
SetSTS(estsvaluetype.sensor, data[3]);
}
break;
case "CRT": // 수동제어
if (data.Length >= 4)
{
_emu_sts_dir = data[0];
_emu_sts_bunki = data[1];
_emu_sts_speed = data[2];
_emu_sts_sensor = data[3];
SetAGV(esystemflag1.agv_stop, false);
SetAGV(esystemflag1.agv_run, true);
if (agv != null) agv.Resume();
}
break;
case "SFR": // Reset
SetAGV(eerror.Emergency, false);
SetAGV(eerror.line_out_error, false);
SetAGV(eerror.Overcurrent, false);
SetAGV(esystemflag1.agv_run, false);
SetAGV(esystemflag1.agv_stop, true);
if (agv != null) agv.Pause();
break;
case "CBT": // 충전
SetAGV(esystemflag1.Battery_charging, true);
if (data.Length >= 5)
{
var cmdChar = data[4];
if (cmdChar == 'I') SetAGV(esystemflag1.Battery_charging, true);
else SetAGV(esystemflag1.Battery_charging, false);
}
break;
case "ACK":
// Log ACK
break;
default:
// Send ACK for other commands
SendCmd("ACK", cmd);
break;
}
}));
}
private void SendCmd(string cmd, string value)
{
if (_emulatorPort == null || !_emulatorPort.IsOpen) return;
var barr = new List<byte>();
barr.Add(0x02);
barr.AddRange(System.Text.Encoding.Default.GetBytes(cmd));
barr.AddRange(System.Text.Encoding.Default.GetBytes(value));
barr.Add((byte)'*');
barr.Add((byte)'*');
barr.Add(0x03);
try
{
_emulatorPort.Write(barr.ToArray(), 0, barr.Count);
}
catch { }
}
public void SendTag(string tagno)
{
if (_emulatorPort == null || !_emulatorPort.IsOpen) return;
tagno = tagno.PadLeft(6, '0');
if (tagno.Length > 6) tagno = tagno.Substring(0, 6);
var barr = new List<byte>();
barr.Add(0x02);
barr.Add((byte)'T');
barr.Add((byte)'A');
barr.Add((byte)'G');
barr.AddRange(System.Text.Encoding.Default.GetBytes(tagno));
barr.Add((byte)'*');
barr.Add((byte)'*');
barr.Add(0x03);
try
{
_emulatorPort.Write(barr.ToArray(), 0, barr.Count);
}
catch { }
}
private void SendEmulatorStatus()
{
if (_emulatorPort == null || !_emulatorPort.IsOpen) return;
var agv = _agvList.FirstOrDefault();
// Sync state from VirtualAGV
if (agv != null)
{
// Update Battery
// Update Position/Tag?
}
// STS Packet Construction
// STS(3) + Volt(3) + Sys0(4) + Sys1(4) + Err(4) + Spd(1) + Bunki(1) + Dir(1) + Sensor(1) + Signal(2) + Checksum(2)
// Default buffer
var sample = "02 53 54 53 32 35 38 46 46 46 46 34 30 30 30 30 30 30 30 4C 53 46 30 30 30 30 30 30 33 41 03";
var barr = sample.Split(' ').Select(t => Convert.ToByte(t, 16)).ToArray();
// Volt (255 for now)
var voltstr = "255";
var bufarr = System.Text.Encoding.Default.GetBytes(voltstr);
Array.Copy(bufarr, 0, barr, 4, bufarr.Length);
// System0
bufarr = System.Text.Encoding.Default.GetBytes(_emu_system0.ToString("X2").PadLeft(4, '0'));
Array.Copy(bufarr, 0, barr, 7, bufarr.Length);
// System1
bufarr = System.Text.Encoding.Default.GetBytes(_emu_system1.ToString("X2").PadLeft(4, '0'));
Array.Copy(bufarr, 0, barr, 11, bufarr.Length);
// Error
bufarr = System.Text.Encoding.Default.GetBytes(_emu_error.ToString("X2").PadLeft(4, '0'));
Array.Copy(bufarr, 0, barr, 15, bufarr.Length);
// Status Chars
barr[19] = (byte)_emu_sts_speed;
barr[20] = (byte)_emu_sts_bunki;
barr[21] = (byte)_emu_sts_dir;
barr[22] = (byte)_emu_sts_sensor;
// Signal
bufarr = System.Text.Encoding.Default.GetBytes(_emu_signal.ToString("X2").PadLeft(2, '0'));
Array.Copy(bufarr, 0, barr, 23, bufarr.Length);
// Checksum (**)
barr[barr.Length - 3] = (byte)'*';
barr[barr.Length - 2] = (byte)'*';
try
{
_emulatorPort.Write(barr, 0, barr.Length);
}
catch { }
}
private string CalculateChecksum(string data)
{
int sum = 0;
foreach (char c in data) sum += c;
// 16진수 변환 후 뒤 2자리
string hex = sum.ToString("X");
if (hex.Length >= 2) return hex.Substring(hex.Length - 2);
return hex.PadLeft(2, '0');
}
#endregion
} }
/// <summary> /// <summary>

View File

@@ -284,6 +284,24 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PUB.cs" /> <Compile Include="PUB.cs" />
<Compile Include="CSetting.cs" /> <Compile Include="CSetting.cs" />
<Compile Include="StateMachine\Step\_SM_RUN_CLEANER_IN.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="StateMachine\Step\_SM_RUN_CLEANER_OUT.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="StateMachine\Step\_SM_RUN_LOADER_IN.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="StateMachine\Step\_SM_RUN_LOADER_OUT.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="StateMachine\Step\_SM_RUN_UNLOADER_IN.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="StateMachine\Step\_SM_RUN_UNLOADER_OUT.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="StateMachine\Step\_Util.cs"> <Compile Include="StateMachine\Step\_Util.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>

View File

@@ -159,6 +159,13 @@ namespace Project.Device
public bool BufferOutComplete { get; set; } public bool BufferOutComplete { get; set; }
public bool BufferReadyError { get; set; } public bool BufferReadyError { get; set; }
public bool LoaderInComplete { get; set; }
public bool LoaderOutComplete { get; set; }
public bool UnloaderInComplete { get; set; }
public bool UnloaderOutComplete { get; set; }
public bool CleanerInComplete { get; set; }
public bool CleanerOutComplete { get; set; }
/// <summary> /// <summary>
/// AGV상태를 Xbee 로 전송한다 /// AGV상태를 Xbee 로 전송한다

View File

@@ -171,7 +171,7 @@ namespace Project
} }
break; break;
case ERunStep.MARKSTROPB: //후진방향으로 마크스탑 case ERunStep.MARKSTOPB: //후진방향으로 마크스탑
case ERunStep.MARKSTOPF: //전진방향으로 마크스탑 case ERunStep.MARKSTOPF: //전진방향으로 마크스탑
//이동중이지 않다면 먼저 이동을 진행한다 //이동중이지 않다면 먼저 이동을 진행한다
@@ -249,6 +249,96 @@ namespace Project
} }
} }
break; break;
case ERunStep.LOADER_OUT: //로더아웃
if (_SM_RUN_LOADER_OUT(runStepisFirst, PUB.sm.GetRunSteptime))
{
PUB.Speak(Lang.);
//도킹완료상태를 업데이트한다.
PUB.XBE.LoaderInComplete = false;
PUB.XBE.LoaderOutComplete = true;
//대기상태로 전환
PUB.sm.SetNewRunStep(ERunStep.READY);
return;
}
break;
case ERunStep.LOADER_IN: //로더도킹
if (_SM_RUN_LOADER_IN(runStepisFirst, PUB.sm.GetRunSteptime))
{
PUB.Speak(Lang.);
//도킹완료상태를 업데이트한다.
PUB.XBE.LoaderInComplete = true;
//로더아웃으로 자동 진행 합니다
PUB.sm.ClearRunStep();
PUB.sm.SetNewRunStep(ERunStep.LOADER_OUT);
return;
}
break;
case ERunStep.UNLOADER_OUT: //언로더아웃
if (_SM_RUN_UNLOADER_OUT(runStepisFirst, PUB.sm.GetRunSteptime))
{
PUB.Speak(Lang.);
//도킹완료상태를 업데이트한다.
PUB.XBE.UnloaderInComplete = false;
PUB.XBE.UnloaderOutComplete = true;
//대기상태로 전환
PUB.sm.SetNewRunStep(ERunStep.READY);
return;
}
break;
case ERunStep.UNLOADER_IN: //언로더도킹
if (_SM_RUN_UNLOADER_IN(runStepisFirst, PUB.sm.GetRunSteptime))
{
PUB.Speak(Lang.);
//도킹완료상태를 업데이트한다.
PUB.XBE.UnloaderInComplete = true;
//언로더아웃으로 자동 진행 합니다
PUB.sm.ClearRunStep();
PUB.sm.SetNewRunStep(ERunStep.UNLOADER_OUT);
return;
}
break;
case ERunStep.CLEANER_OUT: //클리너아웃
if (_SM_RUN_CLEANER_OUT(runStepisFirst, PUB.sm.GetRunSteptime))
{
PUB.Speak(Lang.);
//도킹완료상태를 업데이트한다.
PUB.XBE.CleanerInComplete = false;
PUB.XBE.CleanerOutComplete = true;
//대기상태로 전환
PUB.sm.SetNewRunStep(ERunStep.READY);
return;
}
break;
case ERunStep.CLEANER_IN: //클리너도킹
if (_SM_RUN_CLEANER_IN(runStepisFirst, PUB.sm.GetRunSteptime))
{
PUB.Speak(Lang.);
//도킹완료상태를 업데이트한다.
PUB.XBE.CleanerInComplete = true;
//클리너아웃으로 자동 진행 합니다
PUB.sm.ClearRunStep();
PUB.sm.SetNewRunStep(ERunStep.CLEANER_OUT);
return;
}
break;
case ERunStep.BUFFER_OUT: //버퍼아웃 case ERunStep.BUFFER_OUT: //버퍼아웃
if (_SM_RUN_BUFFER_OUT(runStepisFirst, PUB.sm.GetRunSteptime)) if (_SM_RUN_BUFFER_OUT(runStepisFirst, PUB.sm.GetRunSteptime))
{ {

View File

@@ -22,40 +22,14 @@ namespace Project
var funcname = "_SM_RUN_BUFFER_IN"; var funcname = "_SM_RUN_BUFFER_IN";
var idx = 1; var idx = 1;
if (runStepisFirst)
{
//PUB.flag.set(EFlag.FLAG_NEXTSTOP_ALIGN, false);
VAR.BOOL[eVarBool.FLAG_NEXTSTOP_ALIGN] = false;//
//VAR.BOOL[eVarBool.FLAG_NEXTSTOP_MARK] = false;//);
}
//HW 연결오류
if (PUB.AGV.IsOpen == false)
{
PUB.Result.SetResultMessage(eResult.Hardware, eECode.AGVCONN, eNextStep.ERROR);
return false;
}
//충전 상태가 OFF되어야 동작하게한다 //충전 상태가 OFF되어야 동작하게한다
if (_SM_RUN_CHGOFF(isFirst, stepTime) == false) if (_SM_RUN_CHGOFF(isFirst, stepTime) == false) return false;
return false;
//라이더멈춤이 설정되어있다면 음성으로 알려준다 200409 //라이더멈춤이 설정되어있다면 음성으로 알려준다
if (PUB.AGV.system1.stop_by_front_detect == true) if (CheckLiderStop() == false) return false;
{
var tsSpeak = DateTime.Now - LastSpeakTime;
if (tsSpeak.TotalSeconds >= PUB.setting.alarmSoundTerm)
{
PUB.Speak(Lang.);
LastSpeakTime = DateTime.Now;
}
return false;
}
//현재 위치가 결정되어있는지 체크한다 //현재 위치가 결정되어있는지 체크한다
if (_SM_RUN_POSCHK(isFirst, stepTime) == false) if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return false;
return false;
/* /*
* 버퍼IN시퀀스 * 버퍼IN시퀀스

View File

@@ -14,37 +14,15 @@ namespace Project
public Boolean _SM_RUN_BUFFER_OUT(bool isFirst, TimeSpan stepTime) public Boolean _SM_RUN_BUFFER_OUT(bool isFirst, TimeSpan stepTime)
{ {
if (runStepisFirst)
{
}
//HW 연결오류
if (PUB.AGV.IsOpen == false)
{
PUB.Result.SetResultMessage(eResult.Hardware, eECode.AGVCONN, eNextStep.ERROR);
return false;
}
//충전 상태가 OFF되어야 동작하게한다 //충전 상태가 OFF되어야 동작하게한다
if (_SM_RUN_CHGOFF(isFirst, stepTime) == false) if (_SM_RUN_CHGOFF(isFirst, stepTime) == false) return false;
return false;
//라이더멈춤이 설정되어있다면 음성으로 알려준다 200409 //라이더멈춤이 설정되어있다면 음성으로 알려준다
if (PUB.AGV.system1.stop_by_front_detect == true) if (CheckLiderStop() == false) return false;
{
var tsSpeak = DateTime.Now - LastSpeakTime;
if (tsSpeak.TotalSeconds >= PUB.setting.alarmSoundTerm)
{
PUB.Speak(Lang.);
LastSpeakTime = DateTime.Now;
}
return false;
}
//현재 위치가 결정되어있는지 체크한다 //현재 위치가 결정되어있는지 체크한다
if (_SM_RUN_POSCHK(isFirst, stepTime) == false) if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return false;
return false;
var idx = 1; var idx = 1;
if (PUB.sm.RunStepSeq == idx++) if (PUB.sm.RunStepSeq == idx++)

View File

@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using Project.StateMachine;
using COMM;
using AR;
namespace Project
{
public partial class fMain
{
/// <summary>
/// 클리너 진입
/// </summary>
public Boolean _SM_RUN_CLEANER_IN(bool isFirst, TimeSpan stepTime)
{
var funcname = "_SM_RUN_CLEANER_IN";
var idx = 1;
//충전 상태가 OFF되어야 동작하게한다
if (_SM_RUN_CHGOFF(isFirst, stepTime) == false) return false;
//라이더멈춤이 설정되어있다면 음성으로 알려준다
if (CheckLiderStop() == false) return false;
//현재 위치가 결정되어있는지 체크한다
if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return false;
/*
* 클리너 IN 시퀀스 (버퍼 복사 - 턴 제거)
* 1. LIFT DOWN
* 2. 후진-저속-마크다운 실행
*/
if (PUB.sm.RunStepSeq == idx++)
{
PUB.log.Add("클리너진입시작");
PUB.Speak("클리너 작업을 시작합니다");
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//리프트를 내린다.
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.DN);
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//리프트다운센서를 확인한다.
var liftdown = true;
if (liftdown == false)
{
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds > 10)
{
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.STP);
PUB.log.AddE("리프트 하강이 확인되지 않습니다(10초)");
PUB.sm.SetNewRunStep(ERunStep.ERROR);
return false;
}
}
PUB.log.Add("리프트 하강 완료");
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//마크스탑셋팅
PUB.AGV.AGVMoveStop(funcname, arDev.Narumi.eStopOpt.MarkStop);
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//저속이동
var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
{
Bunki = arDev.Narumi.eBunki.Strate,
Direction = arDev.Narumi.eMoveDir.Backward,
PBSSensor = 0,
Speed = arDev.Narumi.eMoveSpd.Low,
});
if (moveset == false)
{
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds > 10)
{
PUB.AGV.AGVMoveStop(funcname);
PUB.log.AddE("AGV속도설정이 완료되지 않았습니다");
PUB.sm.SetNewRunStep(ERunStep.ERROR);
return false;
}
}
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);
PUB.AGV.AGVMoveStop(funcname, arDev.Narumi.eStopOpt.MarkStop);
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//마크스탑신호가 3초이내로 들어와야 한다
if (VAR.BOOL[eVarBool.NEXTSTOP_MARK] == false)
{
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds > 3)
{
PUB.AGV.AGVMoveStop(funcname);
PUB.log.AddE("MARK STOP신호가 확인되지 않습니다");
PUB.sm.SetNewRunStep(ERunStep.ERROR);
return false;
}
}
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//AGV가 멈출때까지 기다린다.
if (PUB.AGV.system1.agv_run == true)
{
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds > 10)
{
PUB.AGV.AGVMoveStop(funcname);
PUB.log.AddE("AGV가 멈추지 않아 강제종료 합니다");
PUB.sm.SetNewRunStep(ERunStep.ERROR);
return false;
}
return false;
}
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//완료되었다.
PUB.log.Add("클리너진입완료");
PUB.sm.UpdateRunStepSeq();
return false;
}
PUB.AddEEDB($"클리너진입완료({PUB.Result.TargetPos})");
return true;
}
}
}

View File

@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using Project.StateMachine;
using COMM;
using AR;
namespace Project
{
public partial class fMain
{
/// <summary>
/// 클리너 배출
/// </summary>
public Boolean _SM_RUN_CLEANER_OUT(bool isFirst, TimeSpan stepTime)
{
//충전 상태가 OFF되어야 동작하게한다
if (_SM_RUN_CHGOFF(isFirst, stepTime) == false) return false;
//라이더멈춤이 설정되어있다면 음성으로 알려준다
if (CheckLiderStop() == false) return false;
//현재 위치가 결정되어있는지 체크한다
if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return false;
var idx = 1;
if (PUB.sm.RunStepSeq == idx++)
{
//빈 상태로 아웃해야한다.
PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
{
Bunki = arDev.Narumi.eBunki.Strate,
Direction = arDev.Narumi.eMoveDir.Forward,
PBSSensor = 0,
Speed = arDev.Narumi.eMoveSpd.Low,
});
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//전진이동
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//마크스탑
PUB.AGV.AGVMoveStop("cleaner out", arDev.Narumi.eStopOpt.MarkStop);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//이동확인
if (PUB.AGV.system1.agv_run == false)
{
return false;
}
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//멈춤확인
if (PUB.AGV.system1.agv_run == true)
{
return false;
}
PUB.sm.UpdateRunStepSeq();
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using Project.StateMachine;
using COMM;
using AR;
namespace Project
{
public partial class fMain
{
/// <summary>
/// 로더 진입
/// </summary>
public Boolean _SM_RUN_LOADER_IN(bool isFirst, TimeSpan stepTime)
{
var funcname = "_SM_RUN_LOADER_IN";
var idx = 1;
//충전 상태가 OFF되어야 동작하게한다
if (_SM_RUN_CHGOFF(isFirst, stepTime) == false) return false;
//라이더멈춤이 설정되어있다면 음성으로 알려준다
if (CheckLiderStop() == false) return false;
//현재 위치가 결정되어있는지 체크한다
if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return false;
/*
* 로더 IN 시퀀스 (버퍼 복사 - 턴 제거)
* 1. LIFT DOWN
* 2. 후진-저속-마크다운 실행
*/
if (PUB.sm.RunStepSeq == idx++)
{
PUB.log.Add("로더진입시작");
PUB.Speak("로더 작업을 시작합니다"); // Lang resource might not exist, using string
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//리프트를 내린다.
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.DN);
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//리프트다운센서를 확인한다.
var liftdown = true; // 센서 확인 로직이 주석처리되어 있거나 하드코딩되어 있었음 (버퍼 코드 참조)
if (liftdown == false)
{
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds > 10)
{
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.STP);
PUB.log.AddE("리프트 하강이 확인되지 않습니다(10초)");
PUB.sm.SetNewRunStep(ERunStep.ERROR);
return false;
}
}
PUB.log.Add("리프트 하강 완료");
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//마크스탑셋팅
PUB.AGV.AGVMoveStop(funcname, arDev.Narumi.eStopOpt.MarkStop);
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//저속이동
var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
{
Bunki = arDev.Narumi.eBunki.Strate,
Direction = arDev.Narumi.eMoveDir.Backward,
PBSSensor = 0,
Speed = arDev.Narumi.eMoveSpd.Low,
});
if (moveset == false)
{
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds > 10)
{
PUB.AGV.AGVMoveStop(funcname);
PUB.log.AddE("AGV속도설정이 완료되지 않았습니다");
PUB.sm.SetNewRunStep(ERunStep.ERROR);
return false;
}
}
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);
PUB.AGV.AGVMoveStop(funcname, arDev.Narumi.eStopOpt.MarkStop);
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//마크스탑신호가 3초이내로 들어와야 한다
if (VAR.BOOL[eVarBool.NEXTSTOP_MARK] == false)
{
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds > 3)
{
PUB.AGV.AGVMoveStop(funcname);
PUB.log.AddE("MARK STOP신호가 확인되지 않습니다");
PUB.sm.SetNewRunStep(ERunStep.ERROR);
return false;
}
}
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//AGV가 멈출때까지 기다린다.
if (PUB.AGV.system1.agv_run == true)
{
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds > 10)
{
PUB.AGV.AGVMoveStop(funcname);
PUB.log.AddE("AGV가 멈추지 않아 강제종료 합니다");
PUB.sm.SetNewRunStep(ERunStep.ERROR);
return false;
}
return false;
}
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//완료되었다.
PUB.log.Add("로더진입완료");
PUB.sm.UpdateRunStepSeq();
return false;
}
PUB.AddEEDB($"로더진입완료({PUB.Result.TargetPos})");
return true;
}
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using Project.StateMachine;
using COMM;
using AR;
namespace Project
{
public partial class fMain
{
/// <summary>
/// 로더 배출
/// </summary>
public Boolean _SM_RUN_LOADER_OUT(bool isFirst, TimeSpan stepTime)
{
//충전 상태가 OFF되어야 동작하게한다
if (_SM_RUN_CHGOFF(isFirst, stepTime) == false) return false;
//라이더멈춤이 설정되어있다면 음성으로 알려준다
if (CheckLiderStop() == false) return false;
//현재 위치가 결정되어있는지 체크한다
if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return false;
var idx = 1;
if (PUB.sm.RunStepSeq == idx++)
{
//빈 상태로 아웃해야한다.
PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
{
Bunki = arDev.Narumi.eBunki.Strate,
Direction = arDev.Narumi.eMoveDir.Forward,
PBSSensor = 0,
Speed = arDev.Narumi.eMoveSpd.Low,
});
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//전진이동
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//마크스탑
PUB.AGV.AGVMoveStop("loader out", arDev.Narumi.eStopOpt.MarkStop);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//이동확인
if (PUB.AGV.system1.agv_run == false)
{
return false;
}
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//멈춤확인
if (PUB.AGV.system1.agv_run == true)
{
return false;
}
PUB.sm.UpdateRunStepSeq();
return false;
}
// 턴 제거됨
return true;
}
}
}

View File

@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using Project.StateMachine;
using COMM;
using AR;
namespace Project
{
public partial class fMain
{
/// <summary>
/// 언로더 진입
/// </summary>
public Boolean _SM_RUN_UNLOADER_IN(bool isFirst, TimeSpan stepTime)
{
var funcname = "_SM_RUN_UNLOADER_IN";
var idx = 1;
//충전 상태가 OFF되어야 동작하게한다
if (_SM_RUN_CHGOFF(isFirst, stepTime) == false) return false;
//라이더멈춤이 설정되어있다면 음성으로 알려준다
if (CheckLiderStop() == false) return false;
//현재 위치가 결정되어있는지 체크한다
if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return false;
/*
* 언로더 IN 시퀀스 (버퍼 복사 - 턴 제거)
* 1. LIFT DOWN
* 2. 후진-저속-마크다운 실행
*/
if (PUB.sm.RunStepSeq == idx++)
{
PUB.log.Add("언로더진입시작");
PUB.Speak("언로더 작업을 시작합니다");
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//리프트를 내린다.
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.DN);
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//리프트다운센서를 확인한다.
var liftdown = true;
if (liftdown == false)
{
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds > 10)
{
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.STP);
PUB.log.AddE("리프트 하강이 확인되지 않습니다(10초)");
PUB.sm.SetNewRunStep(ERunStep.ERROR);
return false;
}
}
PUB.log.Add("리프트 하강 완료");
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//마크스탑셋팅
PUB.AGV.AGVMoveStop(funcname, arDev.Narumi.eStopOpt.MarkStop);
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//저속이동
var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
{
Bunki = arDev.Narumi.eBunki.Strate,
Direction = arDev.Narumi.eMoveDir.Backward,
PBSSensor = 0,
Speed = arDev.Narumi.eMoveSpd.Low,
});
if (moveset == false)
{
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds > 10)
{
PUB.AGV.AGVMoveStop(funcname);
PUB.log.AddE("AGV속도설정이 완료되지 않았습니다");
PUB.sm.SetNewRunStep(ERunStep.ERROR);
return false;
}
}
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);
PUB.AGV.AGVMoveStop(funcname, arDev.Narumi.eStopOpt.MarkStop);
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//마크스탑신호가 3초이내로 들어와야 한다
if (VAR.BOOL[eVarBool.NEXTSTOP_MARK] == false)
{
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds > 3)
{
PUB.AGV.AGVMoveStop(funcname);
PUB.log.AddE("MARK STOP신호가 확인되지 않습니다");
PUB.sm.SetNewRunStep(ERunStep.ERROR);
return false;
}
}
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//AGV가 멈출때까지 기다린다.
if (PUB.AGV.system1.agv_run == true)
{
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds > 10)
{
PUB.AGV.AGVMoveStop(funcname);
PUB.log.AddE("AGV가 멈추지 않아 강제종료 합니다");
PUB.sm.SetNewRunStep(ERunStep.ERROR);
return false;
}
return false;
}
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//완료되었다.
PUB.log.Add("언로더진입완료");
PUB.sm.UpdateRunStepSeq();
return false;
}
PUB.AddEEDB($"언로더진입완료({PUB.Result.TargetPos})");
return true;
}
}
}

View File

@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using Project.StateMachine;
using COMM;
using AR;
namespace Project
{
public partial class fMain
{
/// <summary>
/// 언로더 배출
/// </summary>
public Boolean _SM_RUN_UNLOADER_OUT(bool isFirst, TimeSpan stepTime)
{
//충전 상태가 OFF되어야 동작하게한다
if (_SM_RUN_CHGOFF(isFirst, stepTime) == false) return false;
//라이더멈춤이 설정되어있다면 음성으로 알려준다
if (CheckLiderStop() == false) return false;
//현재 위치가 결정되어있는지 체크한다
if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return false;
var idx = 1;
if (PUB.sm.RunStepSeq == idx++)
{
//빈 상태로 아웃해야한다.
PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
{
Bunki = arDev.Narumi.eBunki.Strate,
Direction = arDev.Narumi.eMoveDir.Forward,
PBSSensor = 0,
Speed = arDev.Narumi.eMoveSpd.Low,
});
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//전진이동
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//마크스탑
PUB.AGV.AGVMoveStop("unloader out", arDev.Narumi.eStopOpt.MarkStop);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//이동확인
if (PUB.AGV.system1.agv_run == false)
{
return false;
}
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//멈춤확인
if (PUB.AGV.system1.agv_run == true)
{
return false;
}
PUB.sm.UpdateRunStepSeq();
return false;
}
return true;
}
}
}

View File

@@ -17,6 +17,26 @@ namespace Project
return true; return true;
} }
/// <summary>
/// 라이더멈춤이 설정되어있다면 음성으로 알려준다
/// </summary>
/// <returns></returns>
public bool CheckLiderStop()
{
if (PUB.AGV.system1.stop_by_front_detect == true)
{
var tsSpeak = DateTime.Now - LastSpeakTime;
if (tsSpeak.TotalSeconds >= PUB.setting.alarmSoundTerm)
{
PUB.Speak(Lang.);
LastSpeakTime = DateTime.Now;
}
return false;
}
return true;
}
/// <summary> /// <summary>
/// 설정된 목적지까지 이동을 완료 한 후 True를 반환합니다. /// 설정된 목적지까지 이동을 완료 한 후 True를 반환합니다.
/// 목적지 : PUB._virtualAGV.TargetNode /// 목적지 : PUB._virtualAGV.TargetNode

View File

@@ -714,6 +714,30 @@ namespace Project
} }
else UpdateStatusMessage(String.Format("상차 이동 중 ({0})", PUB.Result.TargetPos), Color.Lime, Color.Black); else UpdateStatusMessage(String.Format("상차 이동 중 ({0})", PUB.Result.TargetPos), Color.Lime, Color.Black);
} }
else if (PUB.sm.RunStep == ERunStep.LOADER_IN)
{
UpdateStatusMessage(String.Format("로더 진입 중 ({0})", PUB.Result.TargetPos), Color.Lime, Color.Black);
}
else if (PUB.sm.RunStep == ERunStep.LOADER_OUT)
{
UpdateStatusMessage(String.Format("로더 배출 중 ({0})", PUB.Result.TargetPos), Color.Lime, Color.Black);
}
else if (PUB.sm.RunStep == ERunStep.UNLOADER_IN)
{
UpdateStatusMessage(String.Format("언로더 진입 중 ({0})", PUB.Result.TargetPos), Color.Lime, Color.Black);
}
else if (PUB.sm.RunStep == ERunStep.UNLOADER_OUT)
{
UpdateStatusMessage(String.Format("언로더 배출 중 ({0})", PUB.Result.TargetPos), Color.Lime, Color.Black);
}
else if (PUB.sm.RunStep == ERunStep.CLEANER_IN)
{
UpdateStatusMessage(String.Format("클리너 진입 중 ({0})", PUB.Result.TargetPos), Color.Lime, Color.Black);
}
else if (PUB.sm.RunStep == ERunStep.CLEANER_OUT)
{
UpdateStatusMessage(String.Format("클리너 배출 중 ({0})", PUB.Result.TargetPos), Color.Lime, Color.Black);
}
else if (PUB.sm.RunStep == ERunStep.GOCHARGE) else if (PUB.sm.RunStep == ERunStep.GOCHARGE)
{ {
if (PUB.Result.result_message.isEmpty() == true) if (PUB.Result.result_message.isEmpty() == true)

View File

@@ -78,7 +78,12 @@ namespace Project.StateMachine
/// </summary> /// </summary>
BUFFER_IN, BUFFER_IN,
LOADER_IN,
LOADER_OUT,
UNLOADER_IN,
UNLOADER_OUT,
CLEANER_IN,
CLEANER_OUT,
/// <summary> /// <summary>
/// 목적지로 이동합니다 /// 목적지로 이동합니다
@@ -93,7 +98,7 @@ namespace Project.StateMachine
/// <summary> /// <summary>
/// 후진방향으로 마크스탑진행 /// 후진방향으로 마크스탑진행
/// </summary> /// </summary>
MARKSTROPB, MARKSTOPB,
/// <summary> /// <summary>
/// 에러발생 /// 에러발생